# 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.
>
> https://danadresse.dk/en/guides/paginering · verified 2026-11-02
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

- `side` is the page number. The first page is `1`. `side=0` gives a validation error.
- `per_side` is the number of rows per page. The default is 100 on most lists. See the table for what you get without the two parameters.

```bash id=page-curl run=live expect="adressebetegnelse"
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:

```json id=per-side-too-big run=no
{
  "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:

```python id=paginate-python run=live expect="postnumre"
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:

```js id=paginate-js run=live expect="kommuner"
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 `q` have a fixed order: `/adresser` and `/adgangsadresser` by id, the other lists by code or name. On `/adresser` and `/adgangsadresser` pages therefore never overlap and never skip rows. Address data is updated continuously, so use replication for a complete extract.
- **Free text `q`.** With `q` the pages are sorted by relevance. Deep pages rarely make sense. Make the search more precise instead.
- **Circle and polygon.** With `cirkel=` or `polygon=` 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 get `400` with the code `area_too_large` instead of a truncated answer. Make the area smaller, or add `postnr` or `kommunekode`.
- **Each page is a call.** 2,500 addresses with `per_side=100` is 25 calls. With `per_side=1000` it is 3. See [Limits, quotas and 429](/en/guides/graenser-og-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](/en/guides/replikering-foerste-udtraek).

`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

- [Formats: JSON, CSV, GeoJSON and NDJSON](/en/guides/formater-json-csv-geojson)
- [Replication: the first extract](/en/guides/replikering-foerste-udtraek)
- [No results?](/en/guides/ingen-resultater)
