Skip to content

Rate limits

The public API applies two rate limits: a pre-auth IP limit that defends against brute-force key enumeration, and a per-key limit sharded by route class. Both return rate_limit_exceeded (HTTP 429) when exceeded.

The IP limit runs before authentication — coarse enough to shed abuse without affecting legitimate traffic.

The per-key limit runs after authentication and is sharded by route class so your read traffic does not crowd out your session-create traffic. Route classes:

  • sessions:createPOST /v1/sessions.
  • applicants:createPOST /v1/applicants.
  • dsar:createPOST /v1/data_subject_requests (capped low).
  • reads — every GET /v1/*.
  • global — catchall per key, applied alongside the more specific class.

Each organization has a tier (standard, elevated, enterprise, or custom). New organizations start on standard.

Route classes are an internal throttling dimension, independent from the scopes you grant a key: route_class names the traffic bucket a route falls into, not a scope you hold. A key granted only sessions:write still shows route_class: "sessions:create" in a 429 from POST /v1/sessions, since the class name predates a later scope simplification and was kept as-is so existing rate-limit tooling and alerts do not need to change.

Every authenticated response carries the per-key limit state:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 873
X-RateLimit-Reset: 1717000060
X-RateLimit-Window: 60
  • X-RateLimit-Limit — the request budget for the current window.
  • X-RateLimit-Remaining — how many requests are left before 429.
  • X-RateLimit-Reset — UNIX epoch seconds when the window rolls over.
  • X-RateLimit-Window — window length in seconds.

Track X-RateLimit-Remaining and slow down before you reach zero — it is cheaper than handling 429s reactively.

A 429 rate_limit_exceeded response includes details inside the error envelope:

{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded for this API key",
"request_id": "req_...",
"details": {
"reset_at": 1717000060,
"limit": 100,
"window_seconds": 60,
"route_class": "sessions:create",
"tier": "standard"
}
}
}

Back off until reset_at, then retry. Use exponential backoff with jitter if you are processing a queue — synchronizing every worker on reset_at just hits the next window’s limit.

Limits are enforced as a fixed-window count, not a token bucket. A burst that fits inside one window passes; long-term sustained throughput is bounded by the window’s limit. The IP limit and most per-key limits use a 60-second window; dsar:create uses 24 hours.

If you consistently see rate_limit_exceeded and adding backoff is not enough, contact support. Custom tiers are sized to your traffic shape. See the API error codes.