# Overvåg forbruget med X-RateLimit-headers

> Læs kvote og minutgrænse fra svarenes headers, log og alarmér ved 80 procent, og brug forbrugssiden i dashboardet.
>
> https://danadresse.dk/guides/overvaag-forbrug · verified 2026-11-02
Du vil vide, at du nærmer dig kvoten, før kaldene begynder at fejle eller koste ekstra. Hvert svar fortæller, hvor meget der er tilbage, så din egen kode kan holde øje uden ekstra kald.

Der er ikke et REST-endpoint, som returnerer forbruget pr. nøgle. Du har to kilder: headerne på hvert svar og siden [Forbrug](/dashboard/usage) i dashboardet. Bruger du vores [MCP-server](/guides/mcp-server), viser værktøjet `quota_status` også månedens forbrug. `/api/v1/stats` er noget andet. Det er offentlige tal for, hvor mange adresser, veje og postnumre vi har, og det siger intet om dit forbrug.

## Headerne

Hvert kald med en gyldig nøgle har disse headers:

| Header | Indhold |
|---|---|
| `X-RateLimit-Limit` | Månedskvoten for den pulje, kaldet tæller i |
| `X-RateLimit-Remaining` | Hvad der er tilbage af månedskvoten efter dette kald |
| `X-RateLimit-Reset` | Unix-tidsstempel (sekunder, UTC) for, hvornår måneden nulstilles: den 1. i næste måned kl. 00.00 UTC |
| `X-RateLimit-Minute-Limit` | Kald pr. minut, eller `unlimited` på Enterprise |
| `X-RateLimit-Minute-Remaining` | Hvad der er tilbage i det aktuelle minut |
| `X-RateLimit-Minute-Reset` | Unix-tidsstempel for, hvornår minuttet nulstilles |
| `X-Quota-Grace` | `true`, når en gratiskonto er over kvoten og får svar fra bufferen |
| `X-Quota-Remaining` | Kald tilbage i bufferen. Kun sammen med `X-Quota-Grace` |
| `X-Danadresse-Key-Status` | Kun til stede, hvis nøglen er markeret: `warn` eller `blocked` |

Hvilken pulje kaldet tæller i, afhænger af nøglen:

- På gratisplanen (Hobby) deler alle kontoens live-nøgler én månedskvote på 2 000 kald.
- På betalte planer har hver nøgle sin egen tæller.
- Testnøgler tælles pr. IP-adresse på gratisplanens niveau. Se [Testnøgler og sandbox](/guides/testnoegler-sandbox).
- Kald fra MCP-serveren har deres egen kvote, og så viser headerne MCP-kvoten.

På gratisplanen er der en buffer efter kvoten. Når `X-RateLimit-Remaining` er 0, får du stadig svar op til 2 400 kald, med `X-Quota-Grace: true` og `X-Quota-Remaining`. Når bufferen er brugt, svarer API'et `429 quota_exceeded` indtil næste måned. På de betalte planer kan du fortsætte over kvoten, og de ekstra kald bliver faktureret. `X-RateLimit-Remaining` bliver stående på 0 resten af måneden. Vil du have et loft over merforbruget, så sæt et budgetloft under [Betaling](/dashboard/billing#budget). Afviste kald (429) tæller ikke med i forbruget. Grænsen pr. minut er hård på alle planer undtagen Enterprise.

Se headerne fra terminalen:

```bash id=headers-curl run=live expect="x-ratelimit-remaining"
curl -s -D - -o /dev/null "https://api.danadresse.dk/postnumre/8000" \
  -H "X-Api-Key: $DANADRESSE_API_KEY" | tr '[:upper:]' '[:lower:]' | grep -E '^x-(ratelimit|quota|danadresse)'
```

## Læs headerne i din kode

Læs dem på hvert svar, du alligevel får, og send tallene videre til din logning eller dit metriksystem. Eksemplet beregner, hvor stor en del af måneden der er brugt, og advarer ved 80 procent:

```python id=read-headers run=live expect="brugt"
import os
from datetime import datetime, timezone

import requests

ALARM_VED = 0.8


def forbrug(r: requests.Response) -> dict:
    h = r.headers
    limit = h.get("X-RateLimit-Limit", "")
    rest = h.get("X-RateLimit-Remaining", "")
    if not (limit.isdigit() and rest.isdigit()):
        return {}
    limit, rest = int(limit), int(rest)
    return {
        "limit": limit,
        "rest": rest,
        "andel": (limit - rest) / limit if limit else 0.0,
        "nulstilles": datetime.fromtimestamp(int(h.get("X-RateLimit-Reset", "0")), timezone.utc),
        "minut_rest": h.get("X-RateLimit-Minute-Remaining"),
        "noeglestatus": h.get("X-Danadresse-Key-Status"),
    }


r = requests.get(
    "https://api.danadresse.dk/postnumre/8000",
    headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
    timeout=10,
)
f = forbrug(r)
print(f"brugt {f['andel']:.1%} af {f['limit']} kald, {f['rest']} tilbage, nulstilles {f['nulstilles']:%Y-%m-%d}")
print(f"tilbage i dette minut: {f['minut_rest']}")
if f["andel"] >= ALARM_VED:
    print("ADVARSEL: over 80 % af månedskvoten er brugt")
if f["noeglestatus"]:
    print(f"ADVARSEL: nøglen er markeret som {f['noeglestatus']} - se /dashboard/keys")
```

I en rigtig tjeneste skal advarslen kun sendes én gang pr. måned. Brug værdien i `X-RateLimit-Reset` som nøgle: har du allerede advaret for den værdi, så lad være igen. Her er det samme som en lille `fetch`-indpakning i Node, der sender advarslen til din egen funktion:

```js id=node-monitor run=syntax
const advaretFor = new Set();

export async function danadresseFetch(url, init = {}, advar = console.warn) {
  const res = await fetch(url, {
    ...init,
    headers: { ...init.headers, "X-Api-Key": process.env.DANADRESSE_API_KEY },
  });
  const limit = Number(res.headers.get("x-ratelimit-limit"));
  const rest = Number(res.headers.get("x-ratelimit-remaining"));
  const reset = res.headers.get("x-ratelimit-reset");
  if (limit > 0 && Number.isFinite(rest) && (limit - rest) / limit >= 0.8 && !advaretFor.has(reset)) {
    advaretFor.add(reset);
    advar(`Danadresse: ${limit - rest} af ${limit} kald brugt denne måned`);
  }
  const status = res.headers.get("x-danadresse-key-status");
  if (status) advar(`Danadresse: nøglen er markeret som ${status}`);
  return res;
}
```

Kører du flere processer, så send tallene til et fælles sted (Prometheus, Datadog, en databasetabel) i stedet for at advare fra hver proces.

## Plads pr. minut

`X-RateLimit-Minute-Remaining` er det tal, der betyder noget for batchjob. Når den nærmer sig 0, så hold en pause til `X-RateLimit-Minute-Reset` i stedet for at løbe ind i `429`. Grænsen er 360 kald i minuttet på Hobby, 1 700 på Basis og 7 000 på Pro.

```python id=minute-pacing run=syntax
import time


def vent_hvis_noedvendigt(r, reserve: int = 5) -> None:
    """Kald efter hvert svar i et batchjob. Holder pause, før minutgrænsen rammes."""
    rest = r.headers.get("X-RateLimit-Minute-Remaining", "")
    reset = r.headers.get("X-RateLimit-Minute-Reset", "")
    if rest.isdigit() and reset.isdigit() and int(rest) <= reserve:
        time.sleep(max(0, int(reset) - time.time()) + 0.5)
```

## I browseren

Kalder du API'et fra en webside, kan dit script læse alle `X-RateLimit-*`-headerne, `X-Quota-Grace`, `X-Quota-Remaining` og `Retry-After`. De er frigivet via CORS. `X-Danadresse-Key-Status` er ikke, men domænelåste browsernøgler bliver heller aldrig markeret som lækkede. Til overvågning er det stadig bedst at samle tallene på serversiden eller kigge i dashboardet, fordi tallene i en browser kun når frem til den besøgende, ikke til dig.

## Forbrugssiden i dashboardet

[Forbrug](/dashboard/usage) viser kontoens kald og fejl pr. dag for de seneste 30 dage, summen og den travleste dag. Tallene gælder hele kontoen, ikke den enkelte nøgle, og de bliver opdateret med en lille forsinkelse. Brug siden til at se udviklingen, og brug headerne til alarmer.

## Næste skridt

- Hvad du gør, når grænsen rammes: [Retries og backoff](/guides/retries-og-backoff).
- Spar kald: [Caching af adresseopslag](/guides/caching).
- Mere plads: se planerne på [forsiden](/#pricing).
