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.

3 min Level: Beginner ✓ Code tested

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:

Shell#regioner-curl
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:

Python#kommuner-python
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>')
FieldContent
kodeFour digits as text, e.g. 0101
navnThe municipality's name
regionskode, regionsnavnThe region the municipality is in
regionThe same as an object with href
udenforkommuneinddelingtrue 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).

Python#postnumre-python
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")
FieldContent
nrFour digits as text
navnThe postal district's name, e.g. København V
kommunerThe municipalities the postcode covers, as {href, kode, navn}
stormodtagerfalse for geographic postcodes. The list only shows those unless you add stormodtagere=true. See Large-recipient postcodes
dagi_idThe 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:

Shell#postnr-autocomplete
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.

JavaScript#cache-node
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, not 101. 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|147 and /kommuner/101 give the same as 0101. 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 0101 to 0860.

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 →