# API-nøglen i headeren eller i URL'en (api_key)

> Vælg mellem X-Api-Key-headeren og ?api_key= i URL'en, og send nøglen korrekt fra curl, Python, Node og PHP.
>
> https://danadresse.dk/guides/api-noegle-header-eller-query · verified 2026-11-02
Hvert kald til `https://api.danadresse.dk` skal have en API-nøgle. Du kan sende den på to måder: i headeren `X-Api-Key` eller som query-parameteren `api_key` i URL'en. Begge virker på alle endpoints, både de DAWA-kompatible stier og `/api/v1`.

Brug headeren, når du kan. Denne guide forklarer hvorfor, og hvornår URL-varianten er det rigtige valg.

## Sådan læser API'et nøglen

Serveren kigger først efter headeren `X-Api-Key`. Kun hvis den mangler eller er tom, læses `api_key` fra URL'en. Det har to konsekvenser:

- Sender du begge, vinder headeren. En forkert nøgle i headeren giver `401 invalid_api_key`, selv om URL'en har den rigtige.
- En tom header (fx `X-Api-Key: ` fordi miljøvariablen ikke er sat) tæller som manglende, og så bruges `api_key` fra URL'en, hvis den findes. Ellers får du `401 missing_api_key`.

Header-navnet er ikke følsomt for store og små bogstaver, så `x-api-key` og `X-API-KEY` virker også. Mellemrum før og efter nøglen i headeren fjernes.

## Header: anbefalet fra servere og scripts

Med curl:

```bash id=header-curl run=live expect="Aarhus"
curl -s "https://api.danadresse.dk/postnumre/8000" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"
```

Med Python og `requests`:

```python id=header-python run=live expect="Aarhus"
import os
import requests

r = requests.get(
    "https://api.danadresse.dk/postnumre/8000",
    headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
    timeout=10,
)
r.raise_for_status()
print(r.json()["navn"])
```

Med Node 18 eller nyere:

```js id=header-node run=live expect="Aarhus"
const res = await fetch("https://api.danadresse.dk/postnumre/8000", {
  headers: { "X-Api-Key": process.env.DANADRESSE_API_KEY },
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const postnummer = await res.json();
console.log(postnummer.navn);
```

Med PHP og curl-udvidelsen:

```php id=header-php run=live expect="Aarhus"
<?php
$ch = curl_init("https://api.danadresse.dk/postnumre/8000");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => ["X-Api-Key: " . getenv("DANADRESSE_API_KEY")],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
    fwrite(STDERR, "HTTP $status: $body\n");
    exit(1);
}
echo json_decode($body, true)["navn"], "\n";
```

## Query-parameter: når du ikke kan sætte en header

`?api_key=` findes, fordi nogle steder kun lader dig angive en URL:

- et `<script src="…">`-tag, fx DAWA's gamle widget: `https://api.danadresse.dk/js/autocomplete/dawa-autocomplete2.min.js?key=dawa_live_…` (widgetten sender derefter nøglen som `api_key` på hvert kald)
- regneark og værktøjer, der kun kan hente en URL, fx `IMPORTDATA` i Google Sheets
- en hurtig test i browserens adresselinje

```bash id=query-curl run=live expect="Aarhus"
curl -s "https://api.danadresse.dk/postnumre/8000?api_key=$DANADRESSE_API_KEY"
```

I browseren er der en ekstra forskel. En brugerdefineret header som `X-Api-Key` får browseren til at sende et CORS-preflight (`OPTIONS`) før det rigtige kald. API'et svarer ja og beder browseren huske svaret i op til et døgn (nogle browsere husker det kortere). Preflightet tæller ikke som et API-kald. Med `api_key` i URL'en er kaldet en "simple request" uden preflight. Begge dele virker, så vælg ud fra, hvad dit bibliotek gør nemmest.

## Hvorfor headeren er det sikre valg

En URL bliver kopieret mange steder, som du ikke styrer:

- access-logs i proxyer, load balancers og firewalls mellem dig og API'et
- fejlrapporteringsværktøjer, der gemmer den fulde request-URL
- shell-historik, CI-logs og skærmbilleder af browserens netværksfane
- HAR-filer, som du sender til support

Vores egne applikationslogs maskerer værdien af `api_key=` (den bliver til `api_key=***`, og det samme gælder `X-Api-Key`-headeren). Du kan ikke regne med, at alle andre led på vejen gør det samme.

En header havner sjældnere i logs. Den er ikke hemmelig over for den, der kører koden. I en browser kan enhver besøgende se nøglen i netværksfanen, uanset om den ligger i header eller URL. Til kode, der kører hos dine besøgende, skal du derfor bruge en domænelåst browsernøgle, se [Browsernøgler og tilladte domæner](/guides/browser-noegler-domaener).

## Hvilken metode skal jeg bruge?

| Situation | Metode | Nøgletype |
|---|---|---|
| Backend, cron-job, ETL, CI | `X-Api-Key`-header | Servernøgle fra miljøvariabel |
| `fetch` i browseren, React, Vue | Header eller `api_key` | Domænelåst browsernøgle |
| `<script src>` til en widget | `?key=` / `api_key` i URL'en | Domænelåst browsernøgle |
| Google Sheets `IMPORTDATA`, simple værktøjer | `api_key` i URL'en | Separat nøgle, som du kan rotere |
| Udvikling og automatiske tests | Header | Testnøgle (`dawa_test_…`) |

## Masker nøglen i dine egne logs

Logger din applikation udgående URL'er, så fjern nøglen, før linjen skrives. Et logging-filter i Python, der gør det samme som vores:

```python id=redact-filter run=syntax
import logging
import re

_SECRET = re.compile(r"(?i)((?:api_key|apikey)=|x-api-key\s*[:=]\s*)([^&\s\"']+)")


class RedactApiKey(logging.Filter):
    """Erstat API-nøgler i logbeskeder med ***."""

    def filter(self, record: logging.LogRecord) -> bool:
        record.msg = _SECRET.sub(r"\1***", str(record.msg))
        if record.args:
            record.args = tuple(
                _SECRET.sub(r"\1***", a) if isinstance(a, str) else a
                for a in record.args
            )
        return True


handler = logging.StreamHandler()
handler.addFilter(RedactApiKey())
logging.basicConfig(level=logging.INFO, handlers=[handler])
logging.getLogger("demo").info("GET https://api.danadresse.dk/postnumre?api_key=dawa_live_hemmelig")
```

Filteret skriver `api_key=***` i stedet for nøglen.

## Hvis nøglen er lækket

Har en nøgle stået i en URL, der er endt i en offentlig log eller et repository, så opret en ny nøgle under [Nøgler](/dashboard/keys), skift den ind i din konfiguration og slet den gamle. En live-nøgle, der bruges fra mange forskellige IP-adresser samme dag, bliver markeret som lækket, og du får en mail. Se [Fejl 403](/guides/fejl-403) for, hvad der sker, hvis den ikke bliver skiftet.

## Næste skridt

- [Testnøgler og sandbox](/guides/testnoegler-sandbox) til udvikling og CI.
- [Browsernøgler og tilladte domæner](/guides/browser-noegler-domaener), før du lægger en nøgle i frontend-kode.
- [Fejl 401: manglende eller ugyldig nøgle](/guides/fejl-401), hvis kaldet bliver afvist.
