Test keys and the sandbox (dawa_test_)
Use dawa_test_ keys for development and CI without spending your quota, and keep test and production apart.
A test key starts with dawa_test_ and is meant for development, automated tests and demos. It returns the same address data as a live key, but it is never billed and does not use your account's monthly quota. In return it has some limits, which this guide walks through.
Create a test key
Go to Keys, click Create key and pick test. The key is shown once. Store it in your password manager or in the .env file you use locally.
Test keys do not count towards the key limit. The cap of 2 keys on the Hobby plan applies to live keys only, so you can have one test key per developer or per CI pipeline.
Differences between test and live
Live (dawa_live_…) | Test (dawa_test_…) | |
|---|---|---|
| Data | All endpoints | Address endpoints only (see below) |
| Metering | Per account (Hobby) or per key (paid plans) | Per client IP, with the Hobby plan's limits |
| Per-minute limit | Your plan's limit | 360 calls a minute per IP |
| Billing | According to your plan | Never |
| Leak control | Yes, flagged when used from many IPs | No |
| Domain lock (browser key) | Yes, if you set domains | Yes, if you set domains |
Per-IP metering means that two developers on separate internet connections do not share a limit, while two CI jobs behind the same NAT address do. The X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Minute-Remaining headers show the IP's numbers, not the account's. More in Limits, quotas and 429.
What a test key may call
Test keys cover address lookups: every DAWA-compatible path such as /autocomplete, /adresser, /adgangsadresser, /datavask/adresser, /postnumre, /kommuner and /replikering/….
Three groups under /api/v1 fetch data live from public registers with a shared key and are therefore closed to test keys:
/api/v1/enrich/…(BBR, DAGI, property, climate and the other enrichments)/api/v1/datavask/parse/api/v1/stats/national
Call them with a test key and you get status 403:
{
"detail": {
"error": {
"code": "sandbox_scope",
"message": "The sandbox/demo key is limited to basic address lookups. Use your own API key for enrich/datavask/stats."
}
},
"error": {
"code": "sandbox_scope",
"message": "The sandbox/demo key is limited to basic address lookups. Use your own API key for enrich/datavask/stats."
}
}The fix is to use your own live key for those calls. Enrichment works on our own demo pages because those pages send a short-lived token that only holds from them. You cannot use that token from your own code. The public demo key on /widget is a test key too and has the same limits.
If you need to test enrichment in CI, use a separate live key with low traffic and watch it under Usage.
When to use which
| Task | Key |
|---|---|
| Local development of an address field or an import | Test |
| Unit and integration tests in CI | Test |
| Demo page or prototype | Test, locked to the domain |
| Production (web shop, CRM, backend) | Live |
Calls to /api/v1/enrich/…, /api/v1/datavask/parse, /api/v1/stats/national | Live |
| Load test | Neither without an agreement. Write to us via support |
Never use a test key for production traffic. All your users share the server's IP, so they also share one IP limit at the free tier, and the per-minute limit is reached quickly.
Keep the environments apart
The simplest rule: production gets a live key from the production environment's secrets, every other environment gets a test key. Let the code check this at startup, so a wrong key is caught immediately and not in a customer's checkout.
This script shows which kind of key you have set and makes one call:
import os import requests key = os.environ["DANADRESSE_API_KEY"].strip() if key.startswith("dawa_test_"): print("Test key: metered per IP, never billed") elif key.startswith("dawa_live_"): print("Live key: metered on the account") else: raise SystemExit("Unknown key format. It must start with dawa_test_ or dawa_live_") r = requests.get( "https://api.danadresse.dk/postnumre/2900", headers={"X-Api-Key": key}, timeout=10, ) r.raise_for_status() print(r.json()["nr"], r.json()["navn"]) print("Left this month:", r.headers.get("X-RateLimit-Remaining"))
A startup guard in Python that refuses to run production with a test key, and the other way round:
import os import sys ENV = os.environ.get("APP_ENV", "development") KEY = os.environ.get("DANADRESSE_API_KEY", "").strip() if not KEY: sys.exit("DANADRESSE_API_KEY is not set") if ENV == "production" and not KEY.startswith("dawa_live_"): sys.exit("Production must use a dawa_live_ key") if ENV != "production" and KEY.startswith("dawa_live_"): sys.exit(f"{ENV} must not use the production key. Use a dawa_test_ key")
The same guard in Node:
const env = process.env.NODE_ENV ?? "development"; const key = (process.env.DANADRESSE_API_KEY ?? "").trim(); if (!key) throw new Error("DANADRESSE_API_KEY is not set"); if (env === "production" && !key.startsWith("dawa_live_")) { throw new Error("Production must use a dawa_live_ key"); } if (env !== "production" && key.startsWith("dawa_live_")) { throw new Error(`${env} must not use the production key. Use a dawa_test_ key`); } export const DANADRESSE_API_KEY = key;
In CI, store the test key as a secret and pass it on as an environment variable. Example for GitHub Actions:
jobs:
test:
runs-on: ubuntu-latest
env:
APP_ENV: test
DANADRESSE_API_KEY: ${{ secrets.DANADRESSE_TEST_KEY }}
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- run: pytestA test key in a browser
A test key can be used in frontend code on a demo page. Set Allowed domains on the key under Keys, as with a live key. Calls from other sites are then refused with 403 origin_not_allowed. See Browser keys and allowed domains.
Next steps
- Header or api_key in the URL
- Limits, quotas and 429
- Error 403: access denied if you get
sandbox_scopeororigin_not_allowed.
Related guides
Markdown source Spotted an error? Tell us API-reference (Swagger)