# 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.
>
> https://danadresse.dk/en/guides/testnoegler-sandbox · verified 2026-11-02
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](/dashboard/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](/en/guides/graenser-og-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:

```json id=sandbox-scope-body run=no
{
  "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](/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](/dashboard/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](/en/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:

```python id=key-mode-check run=live expect="Hellerup"
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:

```python id=startup-guard-python run=syntax
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:

```js id=startup-guard-node run=syntax
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:

```yaml id=github-actions run=no
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: pytest
```

## A 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](/dashboard/keys), as with a live key. Calls from other sites are then refused with `403 origin_not_allowed`. See [Browser keys and allowed domains](/en/guides/browser-noegler-domaener).

## Next steps

- [Header or api_key in the URL](/en/guides/api-noegle-header-eller-query)
- [Limits, quotas and 429](/en/guides/graenser-og-429)
- [Error 403: access denied](/en/guides/fejl-403) if you get `sandbox_scope` or `origin_not_allowed`.
