Error 400 and 404 – wrong parameters and unknown ids

Read details in a 400 error, catch misspelled parameters that are ignored without an error, and tell not_found, route_not_found and unknown_entity apart.

5 min Level: Beginner ✓ Code tested

A 400 means a parameter has a value the API cannot use. A 404 means the path or the id you ask for does not exist. Both count as a call on your quota, so it pays to catch them in code. The key is checked before the parameters, so if you also have a key problem you see 401 or 403 first.

400 invalid_query_parameter

On the DAWA paths a wrong parameter always gives status 400 with code invalid_query_parameter. details is a list with one entry per error:

JSON#bad-param-body
{
  "type": "QueryParameterFormatError",
  "title": "Invalid query parameter",
  "details": [
    {
      "type": "literal_error",
      "loc": ["query", "format"],
      "msg": "Input should be 'json', 'csv', 'geojson', 'geojsonz' or 'ndjson'",
      "input": "xml",
      "ctx": {"expected": "'json', 'csv', 'geojson', 'geojsonz' or 'ndjson'"}
    }
  ],
  "code": "invalid_query_parameter"
}

loc[1] is the parameter name, msg is the reason, and input is the value you sent. Write them to your log:

Python#read-400
import os

import requests

r = requests.get(
    "https://api.danadresse.dk/adresser",
    params={"postnr": "8000", "per_side": 5000},
    headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
    timeout=10,
)
if r.status_code == 400:
    body = r.json()
    print(r.status_code, body["code"])
    for err in body["details"]:
        print(f"  {err['loc'][-1]} = {err['input']!r}: {err['msg']}")

The common causes:

Symptom in detailsCauseFix
per_side, less_than_equalper_side above the limitAt most 1000 on most lists, 100 on /autocomplete. See Pagination
side, greater_than_equalside=0Pages start at 1
format or struktur, literal_errorA value that does not existSee Formats
srid, x, y, int_parsing or float_parsingText instead of a number, e.g. a comma as decimal separatorUse a dot: x=12.5683
srid, value_error, "srid skal være 4326 (WGS84) eller 25832 (ETRS89/UTM32)"Another coordinate system, e.g. srid=3857Use 4326 (default) or 25832. See Coordinates and srid
x or y, missingReverse without coordinatesSend both x and y
entitet, missing/replikering/udtraek without an entity?entitet=adresse

On /api/v1 the matching error is 422 validation_error, and the list is in error.details.

Other 400 codes

codeCauseFix
invalid_kvhkvh is not exactly 12 charactersPad the house number with leading _: 03301407___1
invalid_kvhxkvhx is not exactly 19 characterskvh + floor (3) + door (4), padded with _
invalid_cirkelcirkel is not three numbers, or the radius is outside 1-50,000 mcirkel=longitude,latitude,radius
invalid_polygonpolygon is not valid GeoJSON, or has fewer than three pointspolygon=[[[longitude,latitude],…]], URL-encoded
invalid_parametermatrikelnr without ejerlavkode, or husnrfra/husnrtil that is not a house numberdetails.parameter names the parameter. Send ejerlavkode too, or a house number such as 5 or 5A

Misspelled parameter names give no error

A parameter name the API does not know is ignored, as it was in DAWA. Known filters do filter, also on /autocomplete (postnr, kommunekode, adgangsadresseid) and together with cirkel, polygon and medtagnedlagte. But ?postnummer=8000 does not give an error. It gives the first 100 addresses in the whole country, because the filter was never applied. The same happens with ?kommune=0101 instead of kommunekode, ?pr_side= instead of per_side, and format=csv on endpoints that only return JSON.

If the result is too broad, check the names against the API description at /openapi.json. This function does it automatically before sending the call:

Python#check-params
import os

import requests

API = "https://api.danadresse.dk"
_spec = requests.get(f"{API}/openapi.json", timeout=10).json()


def unknown_params(path: str, params: dict) -> list[str]:
    """Parameter names the endpoint does not know (and would therefore ignore)."""
    known = {p["name"] for p in _spec["paths"][path]["get"].get("parameters", [])}
    return sorted(set(params) - known - {"api_key"})


def checked_get(path: str, params: dict) -> requests.Response:
    bad = unknown_params(path, params)
    if bad:
        raise ValueError(f"Unknown parameters on {path}: {bad}")
    return requests.get(API + path, params=params, timeout=10,
                        headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]})


print("Unknown:", unknown_params("/adresser", {"postnummer": "8000", "per_side": 10}))
r = checked_get("/adresser", {"postnr": "8000", "per_side": 1})
print(r.status_code, r.json()[0]["adressebetegnelse"])

Fetch the specification once at startup, not on every call.

404 not_found: the id or code does not exist

JSON#not-found-body
{
  "type": "ResourceNotFoundError",
  "title": "Adresse 0a3f507a-ec01-32b8-e044-0003ba298018 not found",
  "details": null,
  "code": "not_found"
}

Go through the list:

  1. Access-address id on an address endpoint. The ids are different. The example above is an access-address id sent to /adresser/{id}. Use /adgangsadresser/{id}, or /adresser?adgangsadresseid=… for the units behind the door. The other way round works: /adgangsadresser/{id} with an address id answers with the access address the unit belongs to. See Adresse or adgangsadresse?
  2. An id from another system. An id that does not come from the address register (for example an internal customer id or an id from a test system) does not exist here.
  3. Reverse with no address nearby. /adgangsadresser/reverse gives 404 with "title": "No address within range" when there is no address near the point. Usually x and y are swapped, or UTM coordinates were sent without srid=25832. See Coordinates and srid.
  4. Whitespace or a line break in the id. Strip whitespace before putting the id in the URL.

Codes without leading zeros are not a cause: /kommuner/101 and kommunekode=101 work and answer with 0101. Enrichments under /api/v1/enrich/adresser/{id}/… take both an access-address id and an address id.

Try it yourself with an access-address id on the address endpoint:

Shell#wrong-id-curl
curl -s "https://api.danadresse.dk/adresser/0a3f507a-ec01-32b8-e044-0003ba298018" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"

404 route_not_found: the path does not exist

route_not_found means the URL does not match any endpoint:

  • /api/v1 in front of a DAWA path. The DAWA paths live at the root: https://api.danadresse.dk/adresser, not /api/v1/adresser.
  • An old path from DAWA's documentation that does not exist here. See Compatibility for the list of supported paths.
  • Typos: /adresse instead of /adresser, /postnummer instead of /postnumre.

The format depends on the path. If it starts with a DAWA path (/adresser…), you get the DAWA format. Otherwise you get the /api/v1 format:

JSON#route-not-found
{
  "detail": {"error": {"code": "route_not_found", "message": "Not Found"}},
  "error": {"code": "route_not_found", "message": "Not Found"}
}

404 unknown_entity: wrong replication entity

/replikering/udtraek?entitet=… and /replikering/{entitet}/haendelser know a fixed set of entities. A wrong name gives unknown_entity, and details.supported lists the ones that work:

JSON#unknown-entity
{
  "type": "ResourceNotFoundError",
  "title": "Unknown replication entity 'adressen'",
  "details": {"supported": ["adgangsadresse", "adresse", "…"], "legacy_names": ["adgangsadresser", "adresser", "…"]},
  "code": "unknown_entity"
}

Entities are singular: adresse, adgangsadresse, postnummer. The old plural names such as adgangsadresser are accepted too and listed in details.legacy_names. See Replication: events.

Next steps

Related guides

Markdown source Spotted an error? Tell us API-reference (Swagger)

Try it with a free key

2,000 calls/month on the free plan. No credit card.

Create account →