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.
| Header | Type | Description |
|---|---|---|
Retry-After | integer | Seconds to wait before retrying. Matches the endpoint's bucket window. |
X-RateLimit-Limit | integer | Configured cap for the bucket (e.g. 5 requests/hour). |
X-RateLimit-Remaining | integer | Requests remaining — always 0 at the moment of a 429. |
X-RateLimit-Reset | integer | Absolute 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:
| Code | Endpoint / context |
|---|---|
rate_limit_exceeded | Generic (fallback) |
rate_limited_login | POST /v1/auth/login |
rate_limited_twofa | POST /v1/auth/2fa/* |
rate_limited_billing_<family> | Admin billing endpoints |
broadcast_rate_limited | POST /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.
Recommended retry strategy
- Detect the 429.
- Read
Retry-After(in seconds) or compute the time untilX-RateLimit-Reset. - Wait that amount of time before retrying.
- Apply exponential jitter if the endpoint has multiple concurrent clients.