The API key in a header or in the URL (api_key)
Choose between the X-Api-Key header and ?api_key= in the URL, and send the key correctly from curl, Python, Node and PHP.
Every call to https://api.danadresse.dk needs an API key. You can send it in two ways: in the X-Api-Key header or as the api_key query parameter in the URL. Both work on every endpoint, on the DAWA-compatible paths and on /api/v1.
Use the header when you can. This guide explains why, and when the URL form is the right choice.
How the API reads the key
The server looks for the X-Api-Key header first. Only if it is missing or empty does it read api_key from the URL. Two consequences:
- If you send both, the header wins. A wrong key in the header gives
401 invalid_api_key, even if the URL holds the right one. - An empty header (for example
X-Api-Key:because the environment variable is not set) counts as missing, soapi_keyfrom the URL is used if present. Otherwise you get401 missing_api_key.
The header name is case-insensitive, so x-api-key and X-API-KEY work too. Whitespace around the key in the header is stripped.
Header: recommended from servers and scripts
With curl:
curl -s "https://api.danadresse.dk/postnumre/8000" \ -H "X-Api-Key: $DANADRESSE_API_KEY"
With Python and requests:
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"])
With Node 18 or newer:
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);
With PHP and the curl extension:
<?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: when you cannot set a header
?api_key= exists because some places only let you give a URL:
- a
<script src="…">tag, for example DAWA's old widget:https://api.danadresse.dk/js/autocomplete/dawa-autocomplete2.min.js?key=dawa_live_…(the widget then sends the key asapi_keyon every call) - spreadsheets and tools that can only fetch a URL, such as
IMPORTDATAin Google Sheets - a quick test in the browser's address bar
curl -s "https://api.danadresse.dk/postnumre/8000?api_key=$DANADRESSE_API_KEY"In the browser there is one more difference. A custom header such as X-Api-Key makes the browser send a CORS preflight (OPTIONS) before the real call. The API approves it and asks the browser to remember the answer for up to a day (some browsers keep it for less). The preflight does not count as an API call. With api_key in the URL the call is a "simple request" without a preflight. Both work, so pick whatever your library makes easiest.
Why the header is the safer choice
A URL gets copied to many places you do not control:
- access logs in proxies, load balancers and firewalls between you and the API
- error-reporting tools that store the full request URL
- shell history, CI logs and screenshots of the browser's network tab
- HAR files you send to support
Our own application logs mask the value of api_key= (it becomes api_key=***, and the same goes for the X-Api-Key header). You cannot count on every other hop doing the same.
A header ends up in logs less often. It is not secret from whoever runs the code. In a browser, any visitor can see the key in the network tab, whether it sits in a header or in the URL. For code that runs on your visitors' machines, use a domain-locked browser key, see Browser keys and allowed domains.
Which method should I use?
| Situation | Method | Key type |
|---|---|---|
| Backend, cron job, ETL, CI | X-Api-Key header | Server key from an environment variable |
fetch in the browser, React, Vue | Header or api_key | Domain-locked browser key |
<script src> for a widget | ?key= / api_key in the URL | Domain-locked browser key |
Google Sheets IMPORTDATA, simple tools | api_key in the URL | A separate key you can rotate |
| Development and automated tests | Header | Test key (dawa_test_…) |
Mask the key in your own logs
If your application logs outgoing URLs, remove the key before the line is written. A Python logging filter that does the same as ours:
import logging import re _SECRET = re.compile(r"(?i)((?:api_key|apikey)=|x-api-key\s*[:=]\s*)([^&\s\"']+)") class RedactApiKey(logging.Filter): """Replace API keys in log messages with ***.""" 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_secret")
The filter writes api_key=*** instead of the key.
If the key has leaked
If a key has been in a URL that ended up in a public log or a repository, create a new key under Keys, switch your configuration to it and delete the old one. A live key used from many different IP addresses on the same day is flagged as leaked and you get an email. See Error 403 for what happens if it is not replaced.
Next steps
- Test keys and the sandbox for development and CI.
- Browser keys and allowed domains before you put a key in frontend code.
- Error 401: missing or invalid key if the call is rejected.
Related guides
Markdown source Spotted an error? Tell us API-reference (Swagger)