Monitor usage with the X-RateLimit headers

Read quota and per-minute limit from the response headers, log and alert at 80 percent, and use the usage page in the dashboard.

4 min Level: Intermediate ✓ Code tested

You want to know you are approaching the quota before calls start failing or costing extra. Every response says how much is left, so your own code can keep watch without extra calls.

There is no REST endpoint that returns usage per key. You have two sources: the headers on every response and the Usage page in the dashboard. If you use our MCP server, its quota_status tool also shows the month's usage. /api/v1/stats is something else. It holds public counts of how many addresses, streets and postcodes we have, and says nothing about your usage.

The headers

Every call with a valid key has these headers:

HeaderContent
X-RateLimit-LimitThe monthly quota of the bucket the call counts in
X-RateLimit-RemainingWhat is left of the monthly quota after this call
X-RateLimit-ResetUnix timestamp (seconds, UTC) of when the month resets: the 1st of next month at 00:00 UTC
X-RateLimit-Minute-LimitCalls per minute, or unlimited on Enterprise
X-RateLimit-Minute-RemainingWhat is left in the current minute
X-RateLimit-Minute-ResetUnix timestamp of when the minute resets
X-Quota-Gracetrue when a free account is over its quota and served from the grace buffer
X-Quota-RemainingCalls left in the grace buffer. Only together with X-Quota-Grace
X-Danadresse-Key-StatusOnly present when the key is flagged: warn or blocked

Which bucket a call counts in depends on the key:

  • On the free plan (Hobby) all live keys of the account share one monthly quota of 2,000 calls.
  • On paid plans each key has its own counter.
  • Test keys are counted per IP address at the free plan's level. See Test keys and sandbox.
  • Calls from the MCP server have their own quota, and the headers then show the MCP quota.

On the free plan there is a grace buffer after the quota. When X-RateLimit-Remaining is 0, you are still served up to 2,400 calls, with X-Quota-Grace: true and X-Quota-Remaining. Once the buffer is used, the API answers 429 quota_exceeded until next month. On paid plans you can continue past the quota, and the extra calls are invoiced. X-RateLimit-Remaining stays at 0 for the rest of the month. If you want a ceiling on overage, set a budget cap under Billing. Refused calls (429) are not counted as usage. The per-minute limit is hard on every plan except Enterprise.

See the headers from the terminal:

Shell#headers-curl
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)'

Read the headers in your code

Read them on every response you get anyway, and pass the numbers to your logging or metrics system. The example computes how much of the month is used and warns at 80 percent:

Python#read-headers
import os
from datetime import datetime, timezone

import requests

ALERT_AT = 0.8


def usage(r: requests.Response) -> dict:
    h = r.headers
    limit = h.get("X-RateLimit-Limit", "")
    left = h.get("X-RateLimit-Remaining", "")
    if not (limit.isdigit() and left.isdigit()):
        return {}
    limit, left = int(limit), int(left)
    return {
        "limit": limit,
        "left": left,
        "share": (limit - left) / limit if limit else 0.0,
        "resets": datetime.fromtimestamp(int(h.get("X-RateLimit-Reset", "0")), timezone.utc),
        "minute_left": h.get("X-RateLimit-Minute-Remaining"),
        "key_status": 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,
)
u = usage(r)
print(f"used {u['share']:.1%} of {u['limit']} calls, {u['left']} left, resets {u['resets']:%Y-%m-%d}")
print(f"left in this minute: {u['minute_left']}")
if u["share"] >= ALERT_AT:
    print("WARNING: more than 80 % of the monthly quota is used")
if u["key_status"]:
    print(f"WARNING: the key is flagged as {u['key_status']} - see /dashboard/keys")

In a real service the alert should only go out once per month. Use the X-RateLimit-Reset value as the key: if you already alerted for that value, do not alert again. Here is the same as a small fetch wrapper in Node that passes the alert to your own function:

JavaScript#node-monitor
const alertedFor = new Set();

export async function danadresseFetch(url, init = {}, alert = 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 left = Number(res.headers.get("x-ratelimit-remaining"));
  const reset = res.headers.get("x-ratelimit-reset");
  if (limit > 0 && Number.isFinite(left) && (limit - left) / limit >= 0.8 && !alertedFor.has(reset)) {
    alertedFor.add(reset);
    alert(`Danadresse: ${limit - left} of ${limit} calls used this month`);
  }
  const status = res.headers.get("x-danadresse-key-status");
  if (status) alert(`Danadresse: the key is flagged as ${status}`);
  return res;
}

If you run several processes, send the numbers to one shared place (Prometheus, Datadog, a database table) instead of alerting from each process.

Per-minute headroom

X-RateLimit-Minute-Remaining is the number that matters for batch jobs. When it gets close to 0, pause until X-RateLimit-Minute-Reset instead of running into 429. The limit is 360 calls per minute on Hobby, 1,700 on Basis and 7,000 on Pro.

Python#minute-pacing
import time


def wait_if_needed(r, reserve: int = 5) -> None:
    """Call after each response in a batch job. Pauses before the minute limit is hit."""
    left = r.headers.get("X-RateLimit-Minute-Remaining", "")
    reset = r.headers.get("X-RateLimit-Minute-Reset", "")
    if left.isdigit() and reset.isdigit() and int(left) <= reserve:
        time.sleep(max(0, int(reset) - time.time()) + 0.5)

In the browser

If you call the API from a web page, your script can read every X-RateLimit-* header, X-Quota-Grace, X-Quota-Remaining and Retry-After. They are exposed through CORS. X-Danadresse-Key-Status is not, but domain-locked browser keys are never flagged as leaked either. For monitoring it is still best to collect the numbers on the server side or look in the dashboard, because in a browser the numbers only reach the visitor, not you.

The usage page in the dashboard

Usage shows the account's calls and errors per day for the last 30 days, the total and the busiest day. The numbers cover the whole account, not the single key, and they update with a small delay. Use the page to see the trend, and use the headers for alerts.

Next steps

Related guides

Markdown source Spotted an error? Tell us API-reference (Swagger)

Try it with a free key

2,000 calls/month on the free plan. No credit card.

Create account →