# Error 403: access denied

> Work out why a valid key is refused from the code in the 403 response, and fix the domains, key type, IP list or a blocked key.
>
> https://danadresse.dk/en/guides/fejl-403 · verified 2026-11-02
Status 403 means the API knows your key but will not run this particular call with it. There are several reasons, and the code in the response tells you which. Find the code and go straight to the matching section.

| `code` | In short |
|---|---|
| `origin_not_allowed` | Browser key used from a domain not on its list, or from a server |
| `browser_key_scope` | Browser key used on an endpoint it may not call |
| `sandbox_scope` | Test key used on an endpoint it may not call |
| `key_blocked` | The key is blocked because it looks leaked |
| `ip_not_allowed` | IP-locked key used from another IP address |
| `account_suspended` | The account is suspended |
| `forbidden` | Your IP is blocked by the abuse filter |

On the DAWA paths the code is in `code` and `type` is `ForbiddenError`. On `/api/v1` it is in `error.code`. See [The error format](/en/guides/fejlformat).

This script shows the status, code and message for a call with your key, and whether the key is flagged as possibly leaked:

```python id=diagnose-403 run=live expect="200"
import os

import requests

r = requests.get(
    "https://api.danadresse.dk/postnumre/8000",
    headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
    timeout=10,
)
try:
    body = r.json()
except ValueError:
    body = {}
err = body.get("error") if isinstance(body.get("error"), dict) else body
print(r.status_code, err.get("code") or "OK", err.get("title") or err.get("message") or "")
status = r.headers.get("X-Danadresse-Key-Status")
if status:
    print("Key status:", status)
```

## origin_not_allowed

```json id=origin-body run=no
{
  "type": "ForbiddenError",
  "title": "This API key is locked to shop.firma.dk, *.firma.dk and this request came from firma.dk. Add the domain under API-nøgler → Tilladte domæner, or use a server-side key.",
  "details": null,
  "code": "origin_not_allowed"
}
```

The key has *Allowed domains*, and the host in the call's `Origin` header (or `Referer` when `Origin` is missing) is not on the list. The message shows both the list and the host that was seen.

**The call comes from your server.**

- Cause: A server sends neither `Origin` nor `Referer`, so the message says `came from no Origin/Referer`. A domain-locked key only works from a browser.
- Fix: Create a separate key without domains for the server.

**The domain is missing a variant.**

- Cause: The list has `www.firma.dk`, but the site runs on `firma.dk`, or the other way round. `*.firma.dk` only covers subdomains, not `firma.dk` itself.
- Fix: Add both, for example `firma.dk` and `*.firma.dk`. The port does not matter, only the host name counts.

**Development on your own machine.**

- Cause: `localhost` is not allowed by itself.
- Fix: Add `localhost` to the list, or use a test key for development.

**The page sends no Origin or Referer.**

- Cause: Pages opened as `file://`, sandboxed iframes (Origin `null`) and `<script>` tags on pages with `Referrer-Policy: no-referrer` give no host to compare against.
- Fix: Serve the page from a real web address, and use `strict-origin-when-cross-origin` or something more permissive as the Referrer-Policy.

You set domains under [Keys](/dashboard/keys). See [Browser keys and allowed domains](/en/guides/browser-noegler-domaener).

## browser_key_scope

- Cause: A domain-locked key may only do address lookups. `/api/v1/enrich/…`, `/api/v1/datavask/parse` and `/api/v1/stats/national` are refused because they fetch data from public registers with a shared key.
- Fix: Make the call from your own server with a key without domains, and pass only what the page needs on to the browser.

## sandbox_scope

- Cause: A test key (`dawa_test_…`) was used on `/api/v1/enrich/…`, `/api/v1/datavask/parse` or `/api/v1/stats/national`.
- Fix: Use a live key for those endpoints. See [Test keys and the sandbox](/en/guides/testnoegler-sandbox).

## key_blocked

A live key used from many different IP addresses in one day looks like a leaked key: it sits in a public app, in frontend code or in a repository. The sequence is:

1. The first day, responses get the header `X-Danadresse-Key-Status: warn` and you get an email. Calls still work.
2. If the pattern continues after 72 hours, the key is blocked. Responses get `X-Danadresse-Key-Status: blocked` and status 403 with `key_blocked`.

```json id=blocked-body run=no
{
  "type": "ForbiddenError",
  "title": "This API key is blocked: it was used from … or more different IP addresses in one day — the pattern of a leaked key. …",
  "details": {
    "fix": "https://danadresse.dk/dashboard/keys",
    "page_host": "shop.firma.dk"
  },
  "code": "key_blocked"
}
```

`details.page_host` is included when the calls come from a web page. The key has then usually been put in frontend code.

Fix:

- If the key is used in a browser, set *Allowed domains* on it. Domain-locked keys are not covered by leak control, because each visitor has their own IP.
- If the key has leaked, create a new one under [Keys](/dashboard/keys), switch to it and delete the old one. See [Rotate keys without downtime](/en/guides/roter-noegler).
- If you run many servers behind changing IPs, write to us through [support](/en/support).

Test keys are not covered by leak control.

## ip_not_allowed

```json id=ip-body run=no
{
  "type": "ForbiddenError",
  "title": "This API key is locked to specific IP addresses and this request came from 203.0.113.7. Add the address under API-nøgler → Tilladte IP-adresser.",
  "details": {"ip": "203.0.113.7"},
  "code": "ip_not_allowed"
}
```

IP locking is available on Enterprise. `details.ip` is the address the API saw.

- The server reaches the internet through a different address than the one on the list (NAT, a new load balancer, a new cloud region).
- The server uses IPv6 and the list only has IPv4, or the other way round.
- Outgoing IPs on serverless and PaaS change often. Use a network with a fixed outgoing IP.

Add the address or network under [Keys](/dashboard/keys).

## account_suspended

- Cause: The whole account is suspended, for example because of a missing payment or abuse. Every key gets the same error, and so does login to the dashboard.
- Fix: Write to us through [support](/en/support).

## forbidden

```json id=forbidden-body run=no
{
  "type": "ForbiddenError",
  "title": "Blocked: abuse detected. Contact support if this is an error.",
  "details": null,
  "code": "forbidden"
}
```

- Cause: Our abuse filter has blocked the IP address, for example after attempts to fetch files such as `/.env` or attack patterns in the URL. The block applies to every call from that IP, whatever the key. On the DAWA paths the response has the DAWA format shown above, on `/api/v1` it is `{"error": {"code": "forbidden", …}}`. It carries `Access-Control-Allow-Origin: *`, so browser code can read the 403.
- Fix: Write to us through [support](/en/support) with the IP address and the time.

## Next steps

- [Browser keys and allowed domains](/en/guides/browser-noegler-domaener)
- [Rotate keys without downtime](/en/guides/roter-noegler)
- [CORS errors in the browser](/en/guides/cors-fejl)
