# Valider adressen i checkout med datavask

> Tjek kundens adresse, når formularen sendes, accepter sikre match, bekræft sandsynlige og bed om rettelse ved usikre.
>
> https://danadresse.dk/guides/datavask-i-checkout · verified 2026-11-02

En forkert leveringsadresse koster en returpakke, en ekstra fragt og en irriteret kunde. Autocomplete fanger de fleste fejl, mens kunden skriver, men ikke alle: nogle indsætter adressen fra et andet sted, nogle har slået JavaScript fra, og nogle ordrer kommer ind via API. Datavask på serveren er sikkerhedsnettet, der kører, når formularen sendes.

Endpointet er `GET /datavask/adresser` (med etage og dør) eller `GET /datavask/adgangsadresser` (kun bygningens indgang). Svaret har samme form som DAWA's datavask. Feltet `kategori` øverst i svaret er **A** for et præcist match, **B** for ét sikkert match med små stavefejl og **C** for usikkert.

## 1. Send adressen

Det enkleste er hele adressen som én tekst i `betegnelse`:

```bash id=vask-curl run=live expect="kategori"
curl -s -G "https://api.danadresse.dk/datavask/adresser" \
  --data-urlencode "betegnelse=Strandvejen 100, 1., 2900 Hellerup" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"
```

Parseren læser etage og dør (`2. tv`, `st. th`, `1.mf`), husbogstaver med små bogstaver (`12a`), et postnummer hvor som helst i teksten og aa/å-stavemåder (`Aabenraa`, `Kobenhavn`). Store bogstaver og et afsluttende punktum (`1.`, `TV`) gør ingen forskel.

Har din formular separate felter, kan du sende dem i stedet: `vejnavn`, `husnr`, `etage`, `dør`, `postnr` og `supplerendebynavn`. Resultatet er det samme.

POST virker også: `POST /datavask/adresser` med én adresse som JSON-body, enten `{"betegnelse": "…"}` eller de separate felter. Hvert kald vasker én adresse. Skal du vaske mange adresser, så se [Rens en CSV-fil fra CRM'et](/guides/rens-crm-csv).

## 2. Læs svaret

Kunden skrev "Strandvej" i stedet for "Strandvejen". Vasken finder præcis én adresse og svarer B:

```json id=vask-svar run=no
{
  "kategori": "B",
  "resultater": [
    {
      "adresse": {
        "id": "0a3f50a4-0c39-32b8-e044-0003ba298018",
        "vejnavn": "Strandvejen",
        "adresseringsvejnavn": "Strandvejen",
        "husnr": "100",
        "supplerendebynavn": null,
        "postnr": "2900",
        "postnrnavn": "Hellerup",
        "status": 1,
        "virkningstart": "2000-02-05T19:44:20.000Z",
        "virkningslut": null,
        "adgangsadresseid": "0a3f507c-0624-32b8-e044-0003ba298018",
        "etage": "1",
        "dør": null,
        "href": "https://api.danadresse.dk/adresser/0a3f50a4-0c39-32b8-e044-0003ba298018"
      },
      "aktueladresse": {
        "id": "0a3f50a4-0c39-32b8-e044-0003ba298018",
        "...": "samme felter som adresse"
      },
      "vaskeresultat": {
        "variant": {
          "vejnavn": "Strandvejen",
          "husnr": "100",
          "etage": "1",
          "dør": null,
          "supplerendebynavn": null,
          "postnr": "2900",
          "postnrnavn": "Hellerup"
        },
        "afstand": 2,
        "forskelle": {
          "vejnavn": 2,
          "husnr": 0,
          "postnr": 0,
          "postnrnavn": 0,
          "etage": 0
        },
        "parsetadresse": {
          "vejnavn": "Strandvej",
          "husnr": "100",
          "etage": "1",
          "postnr": "2900",
          "postnrnavn": "Hellerup"
        },
        "ukendtetokens": [],
        "anvendtstormodtagerpostnummer": null
      }
    }
  ]
}
```

| Felt                          | Brug                                                                                        |
| ----------------------------- | ------------------------------------------------------------------------------------------- |
| `kategori`                    | A, B eller C for hele vasken. Det er den eneste kategori i svaret                           |
| `resultater`                  | Kandidaterne, bedste først. Ved A og B er der præcis én                                     |
| `adresse.id`                  | Adressens UUID. Gem den på ordren                                                           |
| `adresse.adgangsadresseid`    | Bygningens indgang. Gem den også                                                            |
| `adresse.vejnavn` m.fl.       | Den officielle stavemåde. Brug den i stedet for kundens                                     |
| `aktueladresse`               | Den gældende version af adressen. Afviger fra `adresse`, hvis matchet er en nedlagt adresse |
| `vaskeresultat.afstand`       | Redigeringsafstand mellem input og adressen. 0 er præcist                                   |
| `vaskeresultat.forskelle`     | Afstanden pr. felt, f.eks. `vejnavn: 2`. Viser, hvad kunden skrev forkert                   |
| `vaskeresultat.ukendtetokens` | Ord i input, der ikke matchede noget, f.eks. `["tv"]`, når adressen ikke har døren "tv"     |

Svaret har ikke et samlet tekstfelt. Byg linjen selv af felterne, eller hent `adressebetegnelse` med `GET /adresser/{id}`.

## 3. Beslut: accepter, bekræft eller spørg

Brug `kategori` øverst i svaret:

| kategori         | Handling                                                                                                     |
| ---------------- | ------------------------------------------------------------------------------------------------------------ |
| A                | Accepter. Gem `id` og `adgangsadresseid`                                                                     |
| B                | Accepter, men gem den officielle tekst. Du kan vise den: "Vi leverer til Strandvejen 100, 1., 2900 Hellerup" |
| C                | Vis kandidaterne fra `resultater`, og lad kunden vælge eller rette adressen. Vælg aldrig en automatisk       |
| tom `resultater` | Bed kunden tjekke adressen. Bloker ikke nødvendigvis ordren                                                  |

Etage og dør tæller med i kategorien. Skriver kunden en etage eller dør, der ikke findes på adressen, svarer vasken C med de enheder, der findes, som kandidater.

I Python:

```python id=vask-python run=live expect="beslutning="
import os
import requests

def vask(betegnelse):
    r = requests.get(
        "https://api.danadresse.dk/datavask/adresser",
        params={"betegnelse": betegnelse},
        headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
        timeout=5,
    )
    r.raise_for_status()
    svar = r.json()
    resultater = svar["resultater"]
    if not resultater:
        return "ask", []
    if svar["kategori"] == "A":
        return "accept", resultater[:1]
    if svar["kategori"] == "B":
        return "confirm", resultater[:1]
    return "choose", resultater[:5]   # C: lad kunden vælge

beslutning, kandidater = vask("Strandvej 100, 1., 2900 Hellerup")
print(f"beslutning={beslutning}")
for k in kandidater:
    a = k["adresse"]
    print(a["id"], a["vejnavn"], a["husnr"], a["etage"], a["postnr"], k["vaskeresultat"]["forskelle"])
```

Samme logik i Node 18+:

```js id=vask-node run=live expect="beslutning="
async function vask(betegnelse) {
  const url = new URL("https://api.danadresse.dk/datavask/adresser");
  url.searchParams.set("betegnelse", betegnelse);
  const res = await fetch(url, {
    headers: { "X-Api-Key": process.env.DANADRESSE_API_KEY },
    signal: AbortSignal.timeout(5000),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const { kategori, resultater } = await res.json();
  if (resultater.length === 0) return { beslutning: "ask", kandidater: [] };
  if (kategori === "A")
    return { beslutning: "accept", kandidater: resultater.slice(0, 1) };
  if (kategori === "B")
    return { beslutning: "confirm", kandidater: resultater.slice(0, 1) };
  return { beslutning: "choose", kandidater: resultater.slice(0, 5) };
}

// Rentemestervej 8 har ingen "2. tv": vasken svarer C med de enheder, der findes
const { beslutning, kandidater } = await vask(
  "Rentemestervej 8, 2. tv, 2400 København NV",
);
console.log(`beslutning=${beslutning}`);
for (const { adresse: a } of kandidater) {
  console.log(
    `${a.vejnavn} ${a.husnr}, ${a.etage ?? "-"}. ${a["dør"] ?? ""}, ${a.postnr} ${a.postnrnavn}`,
  );
}
```

Og i PHP med curl-udvidelsen:

```php id=vask-php run=live expect="beslutning="
<?php
function vask(string $betegnelse): array {
    $ch = curl_init('https://api.danadresse.dk/datavask/adresser?'
        . http_build_query(['betegnelse' => $betegnelse]));
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('DANADRESSE_API_KEY')],
    ]);
    $svar = json_decode(curl_exec($ch), true);
    curl_close($ch);
    $res = $svar['resultater'] ?? [];
    if (!$res) return ['ask', []];
    if ($svar['kategori'] === 'A') return ['accept', array_slice($res, 0, 1)];
    if ($svar['kategori'] === 'B') return ['confirm', array_slice($res, 0, 1)];
    return ['choose', array_slice($res, 0, 5)];
}

[$beslutning, $kandidater] = vask('Raadhuspladsen 1, 1550 København V');
echo "beslutning=$beslutning\n";
foreach ($kandidater as $k) echo $k['adresse']['id'] . ' ' . $k['adresse']['vejnavn'] . "\n";
```

## 4. Gem id'erne på ordren

Når adressen er accepteret eller bekræftet, gemmer du `adresse.id` og `adresse.adgangsadresseid` sammen med teksten. Id'erne er stabile UUID'er fra Danmarks Adresseregister, så du senere kan slå adressen op igen eller berige den. Se [Gem adresse-id'et](/guides/gem-adresse-id).

## Kanttilfælde

- **Stavemåde.** "Strandvej" og "Raadhuspladsen" giver B med det officielle navn, fordi der er præcis ét sikkert match. Gem den officielle stavemåde, ikke kundens.
- **Forkert postnummer.** Et postnummer, der ikke passer til vejen, tæller som en forskel (`forskelle.postnr`) og giver C. Vis kunden kandidaterne, og lad dem rette.
- **Forkert husnummer.** Et nummer, der ikke findes på vejen, giver C med nærliggende numre som kandidater.
- **Etage og dør udeladt.** Uden etage og dør matcher vasken adressen på indgangsniveau, hvis registret har en. Spørg efter etage og dør i formularen, når du leverer til lejligheder.
- **Nye adresser.** En adresse, der lige er oprettet, kan mangle et kort stykke tid. Lad kunden fortsætte med en markering på ordren, så I kan tjekke den manuelt.
- **Timeout eller fejl.** Sæt en kort timeout (3-5 sekunder) og lad ordren gå igennem, hvis API'et ikke svarer. Valider den bagefter. En tabt ordre koster mere end en adresse, der skal rettes.
- **Nøgle.** Kald datavask fra din server med en almindelig nøgle. En browsernøgle virker også på `/datavask/adresser`, men så kan kunden springe tjekket over.

## Næste skridt

- Hjælp kunden, før de sender: [Autocomplete-widget i ren JavaScript](/guides/vanilla-autocomplete-widget) eller siden om [adresse i checkout](/checkout-adresse).
- [Gem adresse-id'et og valider igen senere](/guides/gem-adresse-id)
- [Rens en CSV-fil fra CRM'et](/guides/rens-crm-csv)
