# CORS errors in the browser

> Find the real cause behind a CORS error against api.danadresse.dk, which is open to every domain, and fix credentials, mixed content, proxies and key type.
>
> https://danadresse.dk/en/guides/cors-fejl · verified 2026-11-02
The API allows calls from every domain. If you still get a CORS error in the browser console, the cause is almost always something other than a missing CORS permission: a call with cookies, an `http://` address, a call that never reached the API (a network problem, a firewall, a browser extension), or a proxy that changes the headers. This guide goes through the causes in the order they usually turn up.

## What the API sends

Every response from the API carries `Access-Control-Allow-Origin: *`, errors included: 400, 401, 403 (also from the abuse filter), 404, 429, 500 and 503. Responses to a call with an `Origin` header also list the headers your code may read:

| Header | Value |
|---|---|
| `Access-Control-Allow-Origin` | `*` |
| `Access-Control-Expose-Headers` | `X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Minute-Limit, X-RateLimit-Minute-Remaining, X-RateLimit-Minute-Reset, Retry-After, X-Quota-Grace, X-Quota-Remaining, ETag` |

A preflight (`OPTIONS`) is answered with `Access-Control-Allow-Methods: GET, POST, OPTIONS`, the headers you ask for (for example `x-api-key` and `content-type`), and `Access-Control-Max-Age: 86400`. A preflight needs no key and does not count as a call.

See for yourself. The first call is a plain GET with an `Origin` header, the second a preflight:

```bash id=cors-headers run=live expect="access-control-allow-origin"
curl -s -D - -o /dev/null "https://api.danadresse.dk/postnumre/8000" \
  -H "Origin: https://shop.example.dk" \
  -H "X-Api-Key: $DANADRESSE_API_KEY" | tr 'A-Z' 'a-z' | grep '^access-control'

curl -s -D - -o /dev/null -X OPTIONS "https://api.danadresse.dk/postnumre/8000" \
  -H "Origin: https://shop.example.dk" \
  -H "Access-Control-Request-Method: GET" \
  -H "Access-Control-Request-Headers: x-api-key" | tr 'A-Z' 'a-z' | grep '^access-control'
```

## Start in the network tab

Open the developer tools' network tab and find the call:

- **A status such as 400, 401, 403, 404, 429 or 500 is visible.** Then it is not a CORS problem. Click the call and read the response. See [The error format](/en/guides/fejlformat).
- **Status 410 from `dawa.aws.dk` or `api.dataforsyningen.dk`.** The code still calls DAWA, which closed on 1 October 2026. Search the whole code base, bundled files and CMS settings included, and switch to `https://api.danadresse.dk`. See [Switch from DAWA](/en/guides/migrer-fra-dawa).
- **The call shows as `(failed)`, `CORS error` or `NS_ERROR_…` with no status.** The response did not arrive with CORS headers. Go through the checklist.

## The checklist

**1. The call sends cookies (credentials).**

- Symptom: The console says something like `The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'`.
- Cause: `fetch(url, { credentials: "include" })`, `axios` with `withCredentials: true` or jQuery with `xhrFields: { withCredentials: true }`. Browsers reject `*` together with credentials, and the API does not use cookies.
- Fix: Remove credentials from the call. The key goes in `X-Api-Key` or `api_key`.

```js id=no-credentials run=no
const API_KEY = "YOUR_BROWSER_KEY"; // locked to your domain

// Wrong: credentials: "include" causes a CORS error
// Right:
const res = await fetch("https://api.danadresse.dk/autocomplete?q=Vesterbrogade", {
  headers: { "X-Api-Key": API_KEY },
});
```

**2. Mixed content: `http://` from an `https://` page.**

- Symptom: The console says `Mixed Content`, or the network tab shows `blocked:mixed-content`.
- Cause: The API address in the code starts with `http://`. The browser blocks the call before it is sent.
- Fix: Use `https://api.danadresse.dk`.

**3. The call never reached the API.**

- Cause: Every answer from the API itself carries CORS headers, its errors included. A CORS error with no status therefore usually means the answer came from somewhere else, or never came:
  - an ad blocker or privacy extension in the browser that blocks `api.danadresse.dk`
  - a corporate firewall, proxy or VPN that cuts or rewrites the call
  - a 502 or 504 from the proxy in front of the API, for example on a timeout or during a deployment
  - a lost connection (offline, DNS)
- Fix: Try a private window without extensions, then run the same call with curl from the same network. If curl gets a response with `access-control-allow-origin`, the problem is in the browser or the network. If you get 502 or 504, see [Error 503 and timeouts](/en/guides/fejl-503-og-timeouts).

**4. Your own proxy changes the headers.**

- Symptom: The console says `The 'Access-Control-Allow-Origin' header contains multiple values '*, *'`.
- Cause: You call the API through your own server (nginx, a Next.js route, a Cloudflare Worker), and the proxy both forwards our `Access-Control-Allow-Origin` and adds its own.
- Fix: Let only one hop set CORS headers. If your page calls your own proxy on the same domain, you need no CORS headers at all. See [Next.js route handler as a proxy](/en/guides/nextjs-proxy).

```js id=proxy-strip-headers run=no
// Next.js route handler: only pass on the body and content-type
const upstream = await fetch(url, { headers: { "X-Api-Key": process.env.DANADRESSE_API_KEY } });
return new Response(upstream.body, {
  status: upstream.status,
  headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" },
});
```

**5. Old JSONP code.**

- How it works: `$.ajax({ dataType: "jsonp" })` and `<script>` tags with `callback=` work on the DAWA paths. The answer is wrapped as `fn(…);` with `Content-Type: application/javascript`, and JSONP needs no CORS at all. A script tag cannot send headers, so the key goes in the URL as `api_key=`.
- If it still fails: the callback name must be a plain JavaScript name such as `cb` or `jQuery123_456` (otherwise you get plain JSON back), `/api/v1` does not answer JSONP, and a Content Security Policy on your page must allow `https://api.danadresse.dk` in `script-src`.
- Fix: For new code, use `fetch`. See [Formats](/en/guides/formater-json-csv-geojson).

## Things that look like CORS errors but are not

**A header is `null` in JavaScript.**
The browser lets code read the headers in `Access-Control-Expose-Headers` (see the table above) and the standard headers that are always allowed. So `res.headers.get("Retry-After")` and the `X-RateLimit-*` and `X-Quota-*` headers work in the browser. Other headers, for example `X-Danadresse-Key-Status`, are sent but return `null` in browser code. Read them on the server.

**403 origin_not_allowed.**
The key is locked to domains and the page is not on the list. The response has CORS headers and a readable body. See [Error 403](/en/guides/fejl-403).

**The server key works in the browser.**
A key without a domain lock works from any page, because CORS is open. That is not a security boundary: anyone can copy the key from the page source. Use a browser key with *Allowed domains* in frontend code, and keep the server key on the server. See [Browser keys and allowed domains](/en/guides/browser-noegler-domaener).

## Next steps

- [Browser keys and allowed domains](/en/guides/browser-noegler-domaener)
- [Next.js route handler as a proxy](/en/guides/nextjs-proxy)
- [Error 403: access denied](/en/guides/fejl-403)
