Rate limits
Two token buckets apply to every authenticated request: one per credential (requests per second) and one per account (requests per minute). Either bucket being empty refuses the request with 429; the refused request is not counted against the other bucket.
| Scope | Default | Notes |
|---|---|---|
| Per API key | 10 / second | Burst of 10, refilled continuously. Raised per key on request (api_keys.rateLimit.rps). |
| Per dashboard session | 30 / second | Dashboard pages fan out several requests at once. |
| Per account | 600 / minute | Across every key and session on the account. Raised for high-volume integrations. |
| Claims per verification | 50 | Hard limit, 400 validation_error above it. |
| Consumer portal | 20 / hour / IP | Public endpoints. |
Headers
Every authenticated response, including a 429, carries the account bucket's state. A 429 adds Retry-After in whole seconds.
X-RateLimit-Limit: 600 X-RateLimit-Remaining: 597 Retry-After: 2 (on 429 only)
The 429 body
{ "error": { "type": "rate_limit_error", "code": "rate_limited",
"message": "Rate limit exceeded (10 req/s per credential, 600 req/min per account).",
"requestId": "req_…", "docUrl": "https://primarycheck.com/docs/errors#rate_limited" } }What to do
- Wait
Retry-Afterseconds and retry the same request with the sameIdempotency-Key. The SDKs do this twice by default before raisingRateLimitError. - Use
?wait=25and webhooks instead of polling in a loop; one long-poll costs one request. - Batch work per key: several keys on one account share the per-minute account bucket, so more keys do not mean more throughput.
- Need more? Ask from the dashboard; limits are raised per key and per account, not by plan.
Source politeness
Behind the API, every primary source has its own concurrency and rate budget so a burst from one customer never gets a state board blocked for everyone. Bursts are queued, not refused; use ?wait= or webhooks rather than tight polling.