Every protected endpoint carries a per-window counter. When the counter exceeds the configured cap the API returns HTTP 429 with the recovery headers needed to retry correctly.

Headers on a 429 response

Rate-limit headers are emitted only on 429 — reactively, alongside Retry-After. They are never included in 200 responses or any other status code.

HeaderTypeDescription
Retry-AfterintegerSeconds to wait before retrying. Matches the endpoint's bucket window.
X-RateLimit-LimitintegerConfigured cap for the bucket (e.g. 5 requests/hour).
X-RateLimit-RemainingintegerRequests remaining — always 0 at the moment of a 429.
X-RateLimit-ResetintegerAbsolute Unix epoch (seconds) when the window resets.

Example response

HTTP/1.1 429 Too Many Requests
Retry-After: 3600
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1748649600
Content-Type: application/json

{
  "errors": [{ "status": "429", "code": "rate_limit_exceeded", "detail": "Rate limit exceeded. Try again in 3600 seconds." }],
  "meta": { "version": "1.50.8", "request_id": "f7a8b9c0d1e2" }
}

Per-endpoint error codes

The error.code field identifies the exact bucket that was exceeded. Some endpoints carry specific codes:

CodeEndpoint / context
rate_limit_exceededGeneric (fallback)
rate_limited_loginPOST /v1/auth/login
rate_limited_twofaPOST /v1/auth/2fa/*
rate_limited_billing_<family>Admin billing endpoints
broadcast_rate_limitedPOST /admin/notifications/broadcasts

The code field is the stable contract — it does not change between versions and can be used for conditional logic in the client.

  1. Detect the 429.
  2. Read Retry-After (in seconds) or compute the time until X-RateLimit-Reset.
  3. Wait that amount of time before retrying.
  4. Apply exponential jitter if the endpoint has multiple concurrent clients.