Pagination with side and per_side
Fetch long lists page by page with side and per_side, know the limits per endpoint, and write a loop that stops at the last page.
Lists such as /adresser, /vejnavne and /postnumre return their results in pages. You choose the page with side and the size with per_side, exactly as in DAWA. Each page is one API call, so fewer, larger pages cost less than many small ones.
The parameters
sideis the page number. The first page is1.side=0gives a validation error.per_sideis the number of rows per page. The default is 100 on most lists. See the table for what you get without the two parameters.
curl -s "https://api.danadresse.dk/adresser?postnr=8000&side=2&per_side=2" \ -H "X-Api-Key: $DANADRESSE_API_KEY"
The response is a plain JSON list. There is no header or field with the total count, and no "next page" link. You know you are on the last page when it has fewer rows than per_side.
Limits per endpoint
| Endpoint | Without side and per_side | per_side max | side |
|---|---|---|---|
/adresser, /adgangsadresser | the first 100 | 1000 | yes |
/postnumre, /sogne, /supplerendebynavne, /supplerendebynavne2 | the whole list | 1000 | yes |
/vejnavne, /vejstykker | the whole list with kommunekode or postnr, otherwise the first 100 | 1000 | yes |
/navngivneveje, /ejerlav, /jordstykker, /matrikelnumre, /steder, /stednavne | the first 100 | 1000 | yes |
/kommuner | the whole list | 500 | yes |
/autocomplete | 20 suggestions | 100 | no |
/adresser/autocomplete, /adgangsadresser/autocomplete, /postnumre/autocomplete, /vejnavne/autocomplete, /vejstykker/autocomplete, /ejerlav/autocomplete | 20 suggestions | 1000 | no |
/kommuner/autocomplete | 20 suggestions | 500 | no |
/regioner, /retskredse, /politikredse, /opstillingskredse, /landsdele, /storkredse, /valglandsdele | the whole list | whole list | no |
As in DAWA, the small lists come back whole when you send neither side nor per_side. /postnumre gives every postcode in one call, ready for a dropdown. If you send side or per_side, the default is 100 rows per page. On the reference lists in the last row side and per_side are ignored.
If you set per_side above the limit, you get status 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"
}The loop: fetch until a short page
The recipe is the same in every language: start at page 1, fetch, and stop when the page has fewer rows than per_side. If the total is a multiple of per_side, the last page is empty, which costs one extra call.
A Python generator that yields the rows one at a time:
import os from collections.abc import Iterator import requests API = "https://api.danadresse.dk" session = requests.Session() session.headers["X-Api-Key"] = os.environ["DANADRESSE_API_KEY"] def pages(path: str, params: dict | None = None, per_side: int = 1000) -> Iterator[dict]: """Yield every row of a DAWA list, page by page.""" side = 1 while True: r = session.get( API + path, params={**(params or {}), "side": side, "per_side": per_side}, timeout=30, ) r.raise_for_status() rows = r.json() yield from rows if len(rows) < per_side: return side += 1 everything = list(pages("/postnumre")) print(f"Fetched {len(everything)} postnumre. First: {everything[0]['nr']} {everything[0]['navn']}")
An async iterator in JavaScript for Node 18+. The example fetches every municipality in pages of 50:
const API = "https://api.danadresse.dk"; export async function* pages(path, params = {}, perSide = 1000) { for (let side = 1; ; side++) { const url = new URL(API + path); for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v); url.searchParams.set("side", String(side)); url.searchParams.set("per_side", String(perSide)); const res = await fetch(url, { headers: { "X-Api-Key": process.env.DANADRESSE_API_KEY }, }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`); const rows = await res.json(); yield* rows; if (rows.length < perSide) return; } } let count = 0; for await (const kommune of pages("/kommuner", {}, 50)) count++; console.log(`Fetched ${count} kommuner`);
To stop early, break out of the loop. The generator only fetches the next page when you ask for more rows.
Worth knowing
- Order. Lists without
qhave a fixed order:/adresserand/adgangsadresserby id, the other lists by code or name. On/adresserand/adgangsadresserpages therefore never overlap and never skip rows. Address data is updated continuously, so use replication for a complete extract. - Free text
q. Withqthe pages are sorted by relevance. Deep pages rarely make sense. Make the search more precise instead. - Circle and polygon. With
cirkel=orpolygon=the candidates are found first and then split into pages, nearest first for a circle. If the area covers more than 50,000 access addresses, you get400with the codearea_too_largeinstead of a truncated answer. Make the area smaller, or addpostnrorkommunekode. - Each page is a call. 2,500 addresses with
per_side=100is 25 calls. Withper_side=1000it is 3. See Limits, quotas and 429.
When you need everything: replication
Pagination is for lists a user or a job needs now: the addresses in a postcode, the streets in a municipality. If you need every address in Denmark, fetch them with /replikering/udtraek?entitet=adresse. It is one long JSON array in a single call (or NDJSON, one row per line, with &ndjson), and afterwards you can keep your data current with events. See Replication: the first extract.
format=ndjson on /adresser also gives NDJSON, but it still follows side and per_side. It is handy for writing one page straight to a file, but it does not fetch the whole data set.
Next steps
Related guides
Markdown source Spotted an error? Tell us API-reference (Swagger)