Start Here
Rate Limits
How limits work
Every limit is a token bucket. The whole budget is available at once as a burst, then refills evenly — a 100-per-minute budget gets one token back every 0.6 seconds. You never wait for a window to reset.
The limits
One budget shared by every API key and by sends from the dashboard. A second key does not buy a second budget.
Counted separately from verify, so users mistyping codes never block new sends.
One user hammering resend cannot spend your whole budget. Two spellings of one number count as one.
Separately, each code is locked by its fifth wrong guess.
Leave a delay between polls — the budget refills one request every 0.6 seconds.
A backstop against a single abusive host, applied before any other limit.
Sending quotas
Broadcasts are also capped by how many recipients they carry, not just how many requests you make. Every workspace currently has the same quotas.
Counted after the list is cleaned. A larger list is refused with 400 VALIDATION_ERROR — split it into several broadcasts.
A rolling window over every broadcast your workspace accepted, from the API and the dashboard alike.
Recipients across your in-flight broadcasts that no provider has taken yet, scheduled ones included. Room frees up as sending drains.
QUOTA_EXCEEDED response carries no Retry-After: the message says how much room is left.Response headers
Every rate-limited route names the budget it charged and what is left of it. When you get a 429, wait for Retry-After seconds before trying again.
RateLimit-Policy: otp:send
RateLimit-Limit: 100
RateLimit-Remaining: 87
// only on a 429 caused by one of these budgets
Retry-After: 12Retry-After. For a destination, one new send is allowed every 20 seconds; a locked code never recovers — send a new one.During an outage
If our rate-limit store is unreachable, reads and verifies keep working, but OTP and broadcast sends are refused with 429 until it recovers. We would rather reject a send than deliver messages we cannot count.

