Fejlformatet: DAWA-stier og /api/v1

Læs fejlsvar fra begge dele af API'et med én hjælpefunktion, og reager på den stabile code i stedet for fejlteksten.

5 min Niveau: Øvet ✓ Kode testet

API'et har to fejlformater. De DAWA-kompatible stier (/adresser, /autocomplete, /postnumre og resten uden /api/v1) svarer i DAWA's RFC 7807-format, så eksisterende DAWA-klienter kan læse fejlene. Vores egne endpoints under /api/v1 bruger en error-konvolut. Begge formater har et felt code, som du kan bygge din fejlhåndtering på.

DAWA-stier: type, title, details og code

JSON#dawa-401
{
  "type": "UnauthorizedError",
  "title": "The provided API key is invalid or revoked.",
  "details": null,
  "code": "invalid_api_key"
}
  • type er DAWA's fejltype. Den afhænger kun af statuskoden.
  • title er en tekst til mennesker. Den kan blive omformuleret, så brug den ikke i if-sætninger.
  • details er null, et objekt eller en liste. Ved valideringsfejl er det en liste over de parametre, der var forkerte.
  • code findes ikke i DAWA. Vi har tilføjet den, så du kan skelne fx key_blocked fra invalid_api_key uden at læse teksten. Felter, en DAWA-klient ikke kender, bliver normalt ignoreret, så tilføjelsen bryder ikke eksisterende kode.
Statustype
400QueryParameterFormatError
401UnauthorizedError
403ForbiddenError
404ResourceNotFoundError
429RateLimitExceededError
500InternalServerError
410GoneError
502BadGatewayError
503ServiceUnavailableError

En valideringsfejl på en DAWA-sti har altid status 400 og code invalid_query_parameter. details viser hvilken parameter og hvorfor:

JSON#dawa-400
{
  "type": "QueryParameterFormatError",
  "title": "Invalid query parameter",
  "details": [
    {
      "type": "less_than_equal",
      "loc": ["query", "per_side"],
      "msg": "Input should be less than or equal to 1000",
      "input": "5000",
      "ctx": {"le": 1000}
    }
  ],
  "code": "invalid_query_parameter"
}

/api/v1: error-konvolutten

Fejl fra /api/v1 har objektet error med code, message og eventuelt details. Når fejlen kommer fra API'ets egen kontrol (nøgle, kvote, adgang), står det samme objekt også under detail, så klienter skrevet til FastAPI's standardformat stadig virker:

JSON#v1-403
{
  "detail": {
    "error": {
      "code": "browser_key_scope",
      "message": "Domain-locked browser keys cover address lookups only. Use a server-side key for enrich, datavask/parse and stats."
    }
  },
  "error": {
    "code": "browser_key_scope",
    "message": "Domain-locked browser keys cover address lookups only. Use a server-side key for enrich, datavask/parse and stats."
  }
}

Valideringsfejl på /api/v1 har status 422 og kun error-objektet:

JSON#v1-422
{
  "error": {
    "code": "validation_error",
    "message": "Invalid input",
    "details": [
      {"type": "missing", "loc": ["query", "betegnelse"], "msg": "Field required", "input": null}
    ]
  }
}

Læs altid error.code. Det findes i alle /api/v1-fejl, mens detail kun findes i nogle af dem.

Formatet gælder alle fejl

Formatet følger stien, uanset hvor fejlen opstår:

  • 500 svarer med type InternalServerError og code internal_error på DAWA-stierne og med {"error":{"code":"internal_error",…}} på /api/v1.
  • 403 med code forbidden kommer fra vores misbrugsfilter, før kaldet når API'et. Den har også DAWA-formatet (type ForbiddenError) på DAWA-stierne og error-konvolutten på /api/v1.
  • 404 på en sti, der ikke findes, har code route_not_found i begge formater.

Alle fejlsvar fra API'et har Access-Control-Allow-Origin: *, så JavaScript i en browser kan læse status og body i stedet for at få en CORS-fejl.

Kun 502 og 504 falder uden for. De kommer fra proxyen foran API'et og har en HTML-side som body. Din kode skal tåle et svar, der ikke er JSON.

Koderne

codeStatusBetydningHvad du gør
missing_api_key401Ingen nøgle i header eller URLSend X-Api-Key. Se Fejl 401
invalid_api_key401Nøglen findes ikke eller er slettetTjek stavning og miljøvariabel
account_suspended403Kontoen er spærretSkriv til os via support
key_blocked403Nøglen er spærret som lækketLav en ny nøgle eller lås den til domæner
origin_not_allowed403Browsernøgle brugt fra et andet domæneTilføj domænet eller brug en servernøgle
browser_key_scope403Browsernøgle på enrich/parse/statsKald fra din server med en servernøgle
sandbox_scope403Testnøgle på enrich/parse/statsBrug en live-nøgle
ip_not_allowed403IP-låst nøgle brugt fra en anden IPTilføj IP'en på nøglen
forbidden403Misbrugsfilteret har blokeret din IPSkriv til os via support
invalid_query_parameter400Forkert parameter på en DAWA-sti, fx srid ud over 4326 og 25832Læs details[].loc og msg
invalid_kvh, invalid_kvhx400Nøglen har forkert længde12 tegn for kvh, 19 for kvhx
invalid_cirkel, invalid_polygon400Forkert geometriSe Fejl 400 og 404
area_too_large400cirkel eller polygon dækker for mange adresserGør området mindre, eller tilføj postnr eller kommunekode
validation_error422Forkert input på /api/v1Læs error.details
not_found404Id'et eller koden findes ikkeTjek id-typen
route_not_found404Stien findes ikkeTjek URL'en
unknown_entity404Ukendt replikeringsentitetdetails.supported viser de gyldige
quota_exceeded429Gratis månedskvote (inkl. buffer) eller dit budgetloft er brugtVent til den 1., opgradér eller ret budgetloftet. Se details.reason
rate_limit_per_minute429For mange kald i minuttetVent Retry-After sekunder
rate_limit_per_visitor429En besøgende har kaldt for meget via browsernøgleVent Retry-After sekunder
upstream_unavailable503Et offentligt register svarer ikkePrøv igen efter Retry-After
upstream_busy503Kortvarig spærring af kolde berigelserPrøv igen efter Retry-After
internal_error500Fejl hos osPrøv igen, og skriv til os via support, hvis den bliver ved

En hjælpefunktion til begge formater

Funktionen her giver dig status, code, message, details og retry_after, uanset om fejlen kom fra en DAWA-sti, fra /api/v1 eller fra proxyen. Eksemplet slår et id op, der ikke findes:

Python#read-error-python
import os
import requests


def read_error(resp: requests.Response) -> dict:
    """Normaliser et fejlsvar fra api.danadresse.dk."""
    info = {
        "status": resp.status_code,
        "code": None,
        "message": resp.reason,
        "details": None,
        "retry_after": resp.headers.get("Retry-After"),
    }
    try:
        body = resp.json()
    except ValueError:
        info["message"] = resp.text[:200] or resp.reason  # fx HTML fra en 502
        return info
    if not isinstance(body, dict):
        return info
    err = body.get("error")
    if isinstance(err, dict):  # /api/v1
        info.update(code=err.get("code"), message=err.get("message"),
                    details=err.get("details"))
    elif "type" in body or "title" in body:  # DAWA-stier (RFC 7807)
        info.update(code=body.get("code") or body.get("type"),
                    message=body.get("title"), details=body.get("details"))
    return info


r = requests.get(
    "https://api.danadresse.dk/adresser/00000000-0000-0000-0000-000000000000",
    headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
    timeout=10,
)
if not r.ok:
    fejl = read_error(r)
    print(fejl["status"], fejl["code"], "-", fejl["message"])

Output:

Text#read-error-output
404 not_found - Adresse 00000000-0000-0000-0000-000000000000 not found

Samme funktion i JavaScript, til Node 18+ og browsere:

JavaScript#read-error-js
export async function readError(res) {
  const info = {
    status: res.status,
    code: null,
    message: res.statusText,
    details: null,
    retryAfter: res.headers.get("Retry-After"),
  };
  const text = await res.text();
  let body;
  try {
    body = JSON.parse(text);
  } catch {
    info.message = text.slice(0, 200) || res.statusText; // fx HTML fra en 502
    return info;
  }
  if (body && typeof body.error === "object" && body.error !== null) {
    info.code = body.error.code ?? null;
    info.message = body.error.message ?? info.message;
    info.details = body.error.details ?? null;
  } else if (body && ("type" in body || "title" in body)) {
    info.code = body.code ?? body.type ?? null;
    info.message = body.title ?? info.message;
    info.details = body.details ?? null;
  }
  return info;
}

const res = await fetch(
  "https://api.danadresse.dk/adresser/00000000-0000-0000-0000-000000000000",
  { headers: { "X-Api-Key": process.env.DANADRESSE_API_KEY } },
);
if (!res.ok) {
  const fejl = await readError(res);
  console.log(fejl.status, fejl.code, "-", fejl.message);
}

Retry-After er en af de headere, API'et gør synlige via CORS, så retryAfter virker også i en browser. Se Grænser, kvoter og 429.

Byg logikken på code

Et eksempel på, hvordan koderne fordeler sig i en klient:

Python#branch-on-code
RETRY = {"rate_limit_per_minute", "rate_limit_per_visitor", "upstream_unavailable",
         "upstream_busy", "internal_error"}
FIX_CONFIG = {"missing_api_key", "invalid_api_key", "key_blocked", "origin_not_allowed",
              "browser_key_scope", "sandbox_scope", "ip_not_allowed", "account_suspended"}


def classify(err: dict) -> str:
    """Returner 'retry', 'config', 'stop' eller 'bug' for et normaliseret fejlsvar."""
    if err["code"] in RETRY or err["status"] in (502, 504):
        return "retry"
    if err["code"] in FIX_CONFIG:
        return "config"
    if err["code"] == "quota_exceeded":
        return "stop"
    return "bug"
  • retry: prøv igen med ventetid. Se Grænser, kvoter og 429.
  • config: prøv ikke igen. Nøglen eller dens opsætning skal rettes.
  • stop: stop kaldene indtil kvoten nulstilles.
  • bug: forkerte parametre eller id'er i din kode. Log hele svaret.

Næste skridt

Relaterede guides

Markdown-kilde Fejl i guiden? Skriv til os API-reference (Swagger)

Prøv det med en gratis nøgle

2 000 kald/måned på gratisplanen. Intet kreditkort.

Opret konto →