Postcodes, municipalities and regions for dropdowns
Fetch the full lists of postcodes, municipalities and regions, cache them properly and avoid the usual mistakes with municipality codes.
A dropdown of municipalities, a filter by region or a postcode field that fills in the town name: the lists rarely change, so fetch them once and keep them. This guide shows the three endpoints and how to cache them.
Regions
GET /regioner returns the five regions in one list:
curl -s "https://api.danadresse.dk/regioner" \ -H "X-Api-Key: $DANADRESSE_API_KEY"
Each region has kode (1081-1085), navn and nuts2. The list is sorted by code.
Municipalities
GET /kommuner returns the municipalities sorted by name. The whole list comes in one call:
import os import requests r = requests.get( "https://api.danadresse.dk/kommuner", params={"per_side": 500}, headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]}, timeout=15, ) r.raise_for_status() kommuner = r.json() options = [(k["kode"], k["navn"]) for k in kommuner if not k["udenforkommuneinddeling"]] print(f"{len(options)} kommuner til dropdown") for kode, navn in options[:5]: print(f' <option value="{kode}">{navn}</option>')
| Field | Content |
|---|---|
kode | Four digits as text, e.g. 0101 |
navn | The municipality's name |
regionskode, regionsnavn | The region the municipality is in |
region | The same as an object with href |
udenforkommuneinddeling | true only for Christiansø (0411), which belongs to no municipality |
The list holds the 98 municipalities plus Christiansø. Filter Christiansø out with udenforkommuneinddeling if you only want real municipalities. GET /kommuner?regionskode=1084 gives the municipalities in one region.
Postcodes
There are about 1,100 postcodes, and GET /postnumre returns all of them in one response, sorted by number, like DAWA. If you would rather fetch them in chunks, use side and per_side (up to 1000).
import json import os import requests s = requests.Session() s.headers["X-Api-Key"] = os.environ["DANADRESSE_API_KEY"] r = s.get("https://api.danadresse.dk/postnumre", timeout=60) # the whole list r.raise_for_status() postnumre = [{"nr": p["nr"], "navn": p["navn"], "kommuner": [k["kode"] for k in p["kommuner"]]} for p in r.json()] with open("postnumre.json", "w", encoding="utf-8") as f: json.dump(postnumre, f, ensure_ascii=False) print(len(postnumre), "postcodes saved")
| Field | Content |
|---|---|
nr | Four digits as text |
navn | The postal district's name, e.g. København V |
kommuner | The municipalities the postcode covers, as {href, kode, navn} |
stormodtager | false for geographic postcodes. The list only shows those unless you add stormodtagere=true. See Large-recipient postcodes |
dagi_id | The DAGI id of the postcode area |
A postcode can cover several municipalities. So never derive the municipality from the postcode. Use the address (see Find the municipality, region and parish of an address).
A postcode field that fills in the town
For a single field you do not need the whole list. GET /postnumre/{nr} looks up one postcode, and GET /postnumre/autocomplete?q= suggests postcodes from the start of the number or the name:
curl -s "https://api.danadresse.dk/postnumre/autocomplete?q=Hel&per_side=5" \ -H "X-Api-Key: $DANADRESSE_API_KEY"
The response is a list of {"tekst": "2900 Hellerup", "postnummer": {…}}. GET /kommuner/autocomplete?q= works the same way for municipalities.
Cache the lists
Postcodes and municipalities change a few times a year. Fetch them once a day on the server, store them in a file, Redis or your database, and serve the dropdown from there. That saves calls and makes the form faster.
The responses from /kommuner, /regioner and /postnumre carry Cache-Control: private, max-age=86400, stale-while-revalidate=604800. A browser therefore caches them for a day. Responses under 256 KB also carry an ETag: if you send If-None-Match with it, the API answers 304 Not Modified without a body when the list has not changed. The full postcode list is larger and has no ETag.
import { readFile, writeFile } from "node:fs/promises"; const FIL = "./kommuner.json"; const MAKS_ALDER_MS = 24 * 60 * 60 * 1000; export async function kommuner() { try { const cache = JSON.parse(await readFile(FIL, "utf8")); if (Date.now() - cache.hentet < MAKS_ALDER_MS) return cache.data; } catch { // no cache yet } try { const res = await fetch("https://api.danadresse.dk/kommuner?per_side=500", { headers: { "X-Api-Key": process.env.DANADRESSE_API_KEY }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = (await res.json()).map((k) => ({ kode: k.kode, navn: k.navn })); await writeFile(FIL, JSON.stringify({ hentet: Date.now(), data })); return data; } catch (err) { // The API does not answer: use the old cache if there is one const gammel = JSON.parse(await readFile(FIL, "utf8").catch(() => "null")); if (gammel) return gammel.data; throw err; } }
If you fetch the list from the browser, use a browser key locked to your domain and keep the result in localStorage with a timestamp.
Municipality codes: the usual mistakes
- Leading zero. The code is
0101, not101. The API always returns four digits, but Excel and many JSON parsers turn the code into a number if you let them. Store it as text. - Input without the zero works. Filters and lookups accept the code without the zero and pad it:
kommunekode=101,kommunekode=101|147and/kommuner/101give the same as0101. The same goes for postcodes such as/postnumre/800. - A region code is not a municipality code. Regions have the codes 1081-1085. Municipality codes run from
0101to0860.
Next steps
Related guides
Markdown source Spotted an error? Tell us API-reference (Swagger)