Adresse or adgangsadresse? The data model
Understand the difference between adresse and adgangsadresse, their ids and kvh/kvhx keys, how autocomplete's type relates to them, and which id to store.
Danish addresses exist on two levels: the adgangsadresse (access address) is the door onto the street, and the adresse is the single dwelling or unit behind that door. Both have their own id and their own endpoint. Mix them up and you get 404 or empty lists, so it pays to get the difference straight from the start.
The two concepts
| Adgangsadresse (access address) | Adresse (unit) | |
|---|---|---|
| What is it? | An entrance: street name, house number and postcode | A unit: access address plus floor and door |
| Example | Rådhuspladsen 1, 4200 Slagelse | Rådhuspladsen 1, st. th, 4200 Slagelse |
| Endpoint | /adgangsadresser, /adgangsadresser/{id} | /adresser, /adresser/{id} |
| Coordinate | Yes, adgangspunkt.koordinater | Inherits the access address's |
| Key | kvh (12 characters) | kvhx (19 characters) |
A stairwell in a block of flats is one access address with many addresses, one per flat. A detached house is typically one access address with one address whose etage (floor) and dør (door) are null. Data about the building (BBR, cadastre, municipality, parish, coordinate) belongs to the access address. Data about the single dwelling (for example the BBR unit) belongs to the address.
The ids
Both ids are UUIDs, and they are different. An address response contains the access address as an embedded object, so you get both ids in one call:
{
"id": "0a3f50ae-b4ee-32b8-e044-0003ba298018",
"kvhx": "03301407___1_st__th",
"etage": "st",
"dør": "th",
"adressebetegnelse": "Rådhuspladsen 1, st. th, 4200 Slagelse",
"adgangsadresse": {
"id": "0a3f5084-62c3-32b8-e044-0003ba298018",
"kvh": "03301407___1",
"husnr": "1",
"adressebetegnelse": "Rådhuspladsen 1, 4200 Slagelse",
"adgangspunkt": {"koordinater": [11.35623868, 55.40464424]}
}
}The response is shortened. In struktur=flad, struktur=mini and in the autocomplete suggestions' data, the access address's id is called adgangsadresseid.
Fetch an address and every other address behind the same door:
import os import requests API = "https://api.danadresse.dk" headers = {"X-Api-Key": os.environ["DANADRESSE_API_KEY"]} r = requests.get(f"{API}/adresser/0a3f50ae-b4ee-32b8-e044-0003ba298018", headers=headers, timeout=10) r.raise_for_status() adresse = r.json() adgang = adresse["adgangsadresse"] print("adresse id: ", adresse["id"], "kvhx", adresse["kvhx"]) print("adgangsadresse id:", adgang["id"], "kvh ", adgang["kvh"]) r = requests.get(f"{API}/adresser", params={"adgangsadresseid": adgang["id"], "struktur": "mini"}, headers=headers, timeout=10) r.raise_for_status() for unit in r.json(): print(" -", unit["betegnelse"])
kvh and kvhx
Before the UUIDs, addresses were identified by codes. Many older systems still store them, and the API can look them up:
- kvh (12 characters) = municipality code (4) + street code (4) + house number (4, padded with leading
_).03301407___1is municipality0330, street1407, house number1. - kvhx (19 characters) = kvh + floor (3, padded with leading
_) + door (4, padded with leading_).03301407___1_st__this the ground floor, right-hand door.
curl -s "https://api.danadresse.dk/adgangsadresser?kvh=03301407___1" \ -H "X-Api-Key: $DANADRESSE_API_KEY"
For a specific unit, use /adresser?kvhx=03301407___1_st__th. A kvh that is not 12 characters gives 400 with the code invalid_kvh. A kvhx that is not 19 characters gives invalid_kvhx.
The street code is only unique within a municipality, and house numbers can be renumbered. Use kvh and kvhx to match old data, and store the UUID from then on.
Autocomplete and type
/autocomplete works in three steps while the user types: first street names, then house numbers (access addresses), then floors and doors (addresses). Each suggestion has a type field, and the meaning of data.id depends on it:
type in the suggestion | data.id is | Look up with |
|---|---|---|
vejnavn | no id, only data.navn | Keep typing |
adgangsadresse | the access address's id | /adgangsadresser/{id} |
adresse | the address's id. data.adgangsadresseid is the access address's | /adresser/{id} |
The type parameter locks the step:
type=adressealways returns addresses (units), even when the user has only typed street and house number.type=adgangsadressestops at the house number and never asks for floor and door.adgangsadresserOnly=truedoes the same.type=vejnavnreturns street names only.
Always read type on the chosen suggestion before using data.id. Without type in the call, the same field can hold an access address id one moment and an address id the next.
The wrong id on the wrong endpoint
The ids are not interchangeable. An access address id on /adresser/{id} gives:
{
"type": "ResourceNotFoundError",
"title": "Adresse 0a3f5084-62c3-32b8-e044-0003ba298018 not found",
"details": null,
"code": "not_found"
}The other way round the API is more forgiving: an address id on /adgangsadresser/{id} answers with the access address the unit belongs to. The response's id is then the access address's id, not the id you sent.
On lists the wrong id gives an empty list: /adresser?id=<access address id> finds nothing. Use /adresser?adgangsadresseid=<id> to find the units behind a door.
The enrichment endpoints under /api/v1/enrich/adresser/{id}/… accept both kinds of id. An address id is looked up on the access address it belongs to, so dagi, bbr and climate, for example, give the same answer for every flat behind the same door.
Which id should you store?
| You need the address for | Store |
|---|---|
| Delivery, invoicing, a specific flat | The address's id |
| Maps, distance, municipality, parish, BBR, cadastre | The access address's id |
| A customer register where you do not know which | Both, and which type the user chose |
Also store the address text (adressebetegnelse) as it was when the user chose it. Addresses can be renamed or retired, and then it helps to know what it said. More in Store the address id and status, darstatus and retired addresses.
Next steps
Related guides
Markdown source Spotted an error? Tell us API-reference (Swagger)