# Move from DAWA by changing the base URL

> Move an existing DAWA integration to Danadresse with a new base URL and a key, check it with a smoke-test script, and know the known differences.
>
> https://danadresse.dk/en/guides/migrer-fra-dawa · verified 2026-11-02
DAWA closed on 1 October 2026 at 10:00, and `dawa.aws.dk` and `api.dataforsyningen.dk` now answer with errors. Danadresse has the same paths, parameters and JSON fields, so for most integrations the migration is two changes: a new domain and an API key.

This guide shows how to make the domain configurable, how to check the switch with a small script, and where our API differs from DAWA today.

## 1. Change the domain and add a key

| Before | After |
|---|---|
| `https://dawa.aws.dk` | `https://api.danadresse.dk` |
| `https://api.dataforsyningen.dk` | `https://api.danadresse.dk` |
| no key | header `X-Api-Key: <your key>` |

The path after the domain does not change. `https://api.dataforsyningen.dk/adresser?postnr=8000` becomes `https://api.danadresse.dk/adresser?postnr=8000`. The DAWA endpoints live at the root, without `/api/v1`.

Create a key under [Keys](/dashboard/keys). Every call needs a key; without one you get `401` with the code `missing_api_key`. If you cannot send a header (for example from a spreadsheet or a `<script src>`), the key can go in the URL as `?api_key=`. See [Header or api_key in the URL](/en/guides/api-noegle-header-eller-query) for when that is acceptable.

If many files contain DAWA URLs, [migrate-cli](/en/guides/find-dawa-kald-med-migrate-cli) can find and replace them for you.

## 2. Make the base URL configurable

Do not hard-code the domain again. Read it from configuration with `https://api.danadresse.dk` as the default. Then you can point at a test proxy, a mock in your tests, or another provider without touching the code.

Python:

```python id=config-python run=syntax
import os

import requests

BASE_URL = os.environ.get("DANADRESSE_BASE_URL", "https://api.danadresse.dk").rstrip("/")

session = requests.Session()
session.headers["X-Api-Key"] = os.environ["DANADRESSE_API_KEY"]


def get_address(address_id: str) -> dict:
    r = session.get(f"{BASE_URL}/adresser/{address_id}", timeout=10)
    r.raise_for_status()
    return r.json()
```

Node 18 or newer:

```js id=config-node run=syntax
const BASE_URL = (process.env.DANADRESSE_BASE_URL ?? "https://api.danadresse.dk").replace(/\/$/, "");
const API_KEY = process.env.DANADRESSE_API_KEY;

export async function getAddress(id) {
  const res = await fetch(`${BASE_URL}/adresser/${id}`, {
    headers: { "X-Api-Key": API_KEY },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return res.json();
}
```

PHP:

```php id=config-php run=syntax
<?php
$baseUrl = rtrim(getenv('DANADRESSE_BASE_URL') ?: 'https://api.danadresse.dk', '/');
$apiKey = getenv('DANADRESSE_API_KEY');

function getAddress(string $baseUrl, string $apiKey, string $id): array
{
    $ch = curl_init("$baseUrl/adresser/" . rawurlencode($id));
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ["X-Api-Key: $apiKey"],
        CURLOPT_TIMEOUT => 10,
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($status !== 200) {
        throw new RuntimeException("HTTP $status: $body");
    }
    return json_decode($body, true);
}
```

In .NET, put the values in `appsettings.json` and the key in user secrets or an environment variable:

```json id=config-dotnet run=no
{
  "Danadresse": {
    "BaseUrl": "https://api.danadresse.dk"
  }
}
```

```csharp id=config-csharp run=no
builder.Services.AddHttpClient("danadresse", client =>
{
    client.BaseAddress = new Uri(builder.Configuration["Danadresse:BaseUrl"]!);
    client.DefaultRequestHeaders.Add("X-Api-Key",
        Environment.GetEnvironmentVariable("DANADRESSE_API_KEY"));
});
```

## 3. Run a smoke test

A smoke test calls the endpoints your code uses and checks the status and one field you read. Run it after the switch and again in CI. This script makes five calls:

```python id=smoke-test run=live expect="OK   200 /autocomplete"
import os

import requests

BASE_URL = os.environ.get("DANADRESSE_BASE_URL", "https://api.danadresse.dk").rstrip("/")
session = requests.Session()
session.headers["X-Api-Key"] = os.environ["DANADRESSE_API_KEY"]

# (path, parameters, one field your code reads)
CHECKS = [
    ("/autocomplete", {"q": "Rådhuspladsen 1", "per_side": 1}, "forslagstekst"),
    ("/adresser/0a3f50ae-b4ee-32b8-e044-0003ba298018", {}, "adressebetegnelse"),
    ("/adgangsadresser/reverse", {"x": 12.5683, "y": 55.6761}, "adressebetegnelse"),
    ("/postnumre/8000", {}, "navn"),
    ("/datavask/adresser", {"betegnelse": "Strandvejen 100, 2900 Hellerup"}, "kategori"),
]

failed = 0
for path, params, field in CHECKS:
    r = session.get(BASE_URL + path, params=params, timeout=10)
    ok = r.status_code == 200 and field in r.text
    failed += 0 if ok else 1
    print(f"{'OK  ' if ok else 'FAIL'} {r.status_code} {path}")

print("All checks passed" if failed == 0 else f"{failed} checks failed")
raise SystemExit(1 if failed else 0)
```

Expected output:

```text id=smoke-output run=no
OK   200 /autocomplete
OK   200 /adresser/0a3f50ae-b4ee-32b8-e044-0003ba298018
OK   200 /adgangsadresser/reverse
OK   200 /postnumre/8000
OK   200 /datavask/adresser
All checks passed
```

Add the parameters you use yourself (`struktur`, `srid`, `format`, filters). A parameter we do not know is ignored without an error, so also check that the response has the shape you expect – for example that a filter actually narrowed the result.

## 4. What differs from DAWA

| Area | DAWA | Danadresse |
|---|---|---|
| Key | None | Required. `401 missing_api_key` without one, `401 invalid_api_key` for a wrong or revoked key |
| Limits | None fixed | Monthly quota and a per-minute limit. See [Limits, quotas and 429](/en/guides/graenser-og-429) |
| Error format | `{"type","title","details"}` | Same fields plus `code`, e.g. `"code": "missing_api_key"`, on every error including `500`. See [The error format](/en/guides/fejlformat) |
| `href` fields | Pointed at DAWA | Point at `https://api.danadresse.dk` |
| Codes | Zero-padded (`"0101"`, `"0800"`) | The same. Input may be un-padded: `kommunekode=101` works |
| `srid` | `4326` and `25832` | The same. Other values give `400 QueryParameterFormatError` |
| `/replikering/udtraek` | Streamed JSON array | The same. Add `&ndjson` for one JSON line per object |

## 5. Known gaps

Most of DAWA's contract is covered: the endpoints (including `/historik/*`, `naboer`, `/bebyggelser`, `/stednavne2` and `/vejnavnpostnummerrelationer`), both replication forms, `format=csv|geojson|geojsonz` and JSONP. These values are empty today because we have no source for them. The current list is on [Compatibility](/en/kompatibilitet).

- **Fields on adgangsadresser:** the keys are all there, but `vejpunkt` has no coordinates (`[]`), `bebyggelser` is an empty list, `brofast` is `null`, and in `adgangspunkt` the fields `id`, `kilde`, `tekstretning` and `ændret` are `null`.
- **`bbox` and `visueltcenter`:** empty lists on several area and street endpoints, for example `/regioner`, `/vejstykker` and `/navngivneveje`.

Address lookup, autocomplete, datavask and reverse geocoding are not affected. If you rely on one of these values, tell us through [support](/en/support) with the endpoint and field you need.

## Next steps

- Used DAWA's widget? [From dawa-autocomplete2](/en/guides/migrer-dawa-autocomplete2).
- Wrote your own autocomplete? [Hand-built DAWA autocomplete](/en/guides/migrer-egen-dawa-autocomplete).
- Do you store address ids? Read [status, darstatus and retired addresses](/en/guides/status-og-darstatus).
