Rate limits
Fixed per-minute windows, the headers that report them, and how to back off
Limits are fixed one-minute windows, chosen by the credential you present.
Two things follow from “counted per”:
- Minting more keys does not raise a workspace’s limit. Every key on a workspace draws from the same 300.
- Rotating a platform key does not reset its count. The budget is keyed to the account, not to the token.
Every request costs exactly one unit, reads included. A batch of 100 messages is one request against this limit — batching is the way to send more than 300 messages a minute on a workspace key.
The public error catalog (GET /errors and GET /errors/{code}) is the one exception:
it takes no credential and is not counted against either budget, so following a type
URL from an error never costs you a request.
The headers
Every response, not just a 429, reports where you stand:
Because the window is fixed rather than sliding, RateLimit-Remaining jumps back to the
full limit at RateLimit-Reset — it does not refill gradually.
These are the request-rate limits only. Your plan allowance is a separate budget,
reported on send responses as RRay-Quota-Limit, RRay-Quota-Remaining and
RRay-Quota-Reset; see Limits & facts.
Being limited
Over the limit returns 429 rate_limit.exceeded with Retry-After in seconds:
Honour Retry-After before retrying. It is never more than 60 seconds, since it is the
remaining time in the current window.
One other code shares the 429 status: billing.daily_cap_reached, which also carries
Retry-After — pointing at the next UTC midnight, not at the next minute. Branch on
code, not on the status, so you do not sleep a request-rate interval when the real wait
is hours.
Backing off
Retry on 429 and on 5xx. Never retry a 4xx other than 429: the request will not
start succeeding.
Three things make this safe rather than merely polite:
- Reuse the
Idempotency-Keyacross attempts. A timeout you retry without one can send the message twice. See Idempotency. - Prefer
Retry-Afterto your own guess. It is the exact remaining window. - Cap the attempts. A send that keeps failing after a few tries needs a queue and an alert, not a longer loop.
Staying under the limit
- Batch.
POST /v1/messages/batchsends up to 100 messages for one unit of budget. - Do not poll every message. Read a message when something needs its status; use
GET /v1/statsfor aggregate numbers, which is one request for a whole window. - Watch
RateLimit-Remainingin your own client. Slowing down at 10% remaining is cheaper than handling a429. - Use a platform key for dashboards and a workspace key for sending. The account budget is ten times larger and keeps a reporting job from eating the send budget.