Rate limits
Requests are limited per account. The default budget is 60 requests per minute, and every response tells you where you stand — so a well-behaved client paces itself instead of discovering the ceiling by hitting it.
One account, one budget. Every key on your account draws on the same allowance, and that
includes test-mode keys: minting more keys does not mint more budget. X-RateLimit-Limit is
therefore the ceiling actually enforced for you, whichever key a request arrives on — which is what
makes X-RateLimit-Remaining safe to pace against. If you need more headroom, the account limit
itself is what gets raised — and raising it raises the ceiling you can actually reach, because the
pre-authentication guard described below never meters a request that authenticates.
On this page
Response headers
| Header | On | Value |
|---|---|---|
X-RateLimit-Limit |
every budgeted response | Requests allowed per window (one minute) |
X-RateLimit-Remaining |
every budgeted response | Requests left in the current window, already counting the response carrying the header. 0 on a rejection |
X-RateLimit-Reset |
every budgeted response | Absolute Unix timestamp in seconds — the instant the current window ends |
Retry-After |
429 only |
Whole seconds to wait, per RFC 9110. Never 0 |
X-RateLimit-Reset is an instant, not a delay. It is identical on every response within the
same window, so a client that queues work does not have to remember when it read the header:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1750000080
Retry-After on a 429 counts whole seconds to that same instant and is clamped to at least
1 — a sub-second remainder still means "wait", and 0 would read as "retry immediately".
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1750000080
The simplest correct client sleeps for Retry-After seconds on a 429, and otherwise slows down
when X-RateLimit-Remaining gets low.
429 response
{
"status": 429,
"code": "rate_limited",
"error": "Rate limit exceeded."
}
Branch on code: "rate_limited", not on the message. The full envelope is described in
Errors.
Three responses that carry no budget headers
- A
401from authentication. No key was resolved, so there is no account and no budget to report. - A
403 insufficient_scope. The scope check runs before the budget is spent, so a request your key is not permitted to make never costs you a request from your per-account allowance. It is not free of every limit, though: repeated denials count against the coarse anti-flood guard below, so a retry loop against an endpoint your key is not scoped for eventually gets a429. GET /api/v1/events/stream. The event stream is deliberately outside the per-account budget: a reconnect loop would otherwise burn a per-minute allowance and cost you your event feed exactly when your integration is misbehaving. It is therefore never rate-limited and reports no budget.
A separate coarse anti-flood guard sits in front of authentication and applies to every
/api/v1 request, the stream included. It exists to stop a flood of invalid keys from driving
database lookups, and it counts only requests your credential could not carry out — one that
fails to authenticate, or one refused by the scope check. A request that authenticates and is
permitted spends nothing from it, whatever your account's limit is.
Why the two 429s can report different limits
There are two limiters, and each 429 reports the ceiling that actually rejected you:
| Counts | Partitioned by | Ceiling | |
|---|---|---|---|
| Your per-account budget | Every authenticated and permitted request | Your account | X-RateLimit-Limit on your successes — raisable |
| Coarse anti-flood guard | Only requests your credential could not carry out: it failed to authenticate, or the scope check refused it | The key you presented, or your IP if you sent none | A fixed server-side number, unrelated to your account's |
For a working integration, only the first row can ever reject you: the per-account budget is the only
ceiling a caller doing permitted work can reach, at any value. You meet the guard only while you are
sending a key that does not resolve — a stale or revoked credential in a retry loop, say — or one that
keeps asking for something it is not scoped for. Its 429 then reports its own ceiling rather than
yours. That is not a contradiction: the two numbers describe two different buckets.
Note that the guard's bucket is per presented key, and it is checked before the request is dispatched. So a key that has spent that bucket on refused requests is briefly throttled on its permitted routes as well, until the guard's one-minute window rolls over. Your account's other keys are unaffected.
One consequence worth knowing if you are debugging: because the guard runs before any key is resolved,
its rejections cannot be attributed to an account and so do not appear in your request log
(Member → API → Logs). Every 429 from your per-account budget does.
The window is fixed, not sliding
The budget resets on a fixed one-minute boundary rather than sliding. That has one consequence worth knowing: you can send up to twice the limit across a boundary — a full window's worth in the last second of one window and another full window's worth in the first second of the next. This is accepted and documented rather than smoothed away; if you are pacing near the ceiling, spread requests across the window rather than bursting at its edges.
One related detail: if your account's per-minute limit is changed mid-window, your current window restarts. It is a rare, administrative event and it only ever gives you budget back.
Handling limits well
- Read
X-RateLimit-Remainingand throttle before you are rejected. - On a
429, sleep forRetry-Afterseconds, then retry. Add jitter if you run many workers, so they do not all wake at the same instant. - Retrying a credit-spending POST after a timeout? Send an
Idempotency-Keyso the retry cannot charge you twice. See Idempotency. - For high volume, prefer batch validation (
POST /api/v1/verify/batch): one request uploads an entire file, so a million addresses cost one request against your budget rather than a million.