Rate limits cap how many requests a key may make in a window; past it, the API answers
429 RATE_LIMITED.
Calls draw on one of two budgets. Sends and tracked events —
POST /v1/transactional/messages (and /batch), POST /v1/push/messages,
POST /v1/events/track (and /v1/events/batch) — have their own, larger one, so a burst
of receipts never starves the calls that manage your contacts and campaigns, and the
reverse. Every other call shares the general budget.
| Limit | General | Sends and events | Scope |
|---|---|---|---|
| Per API key | 300 / minute | 3,000 / minute | One key |
| Per organization | 1,200 / minute | 6,000 / minute | All keys in the organization combined |
A flood guard of 72,000 requests a minute per IP sits above both, so one server can call for many organizations. A request must pass every limit that applies to it.
Defaults are configurable per deployment, so treat these as the shape of the system rather
than guaranteed numbers. Do not hard-code them in a client; read Retry-After instead.
The per-organization limit is the one that surprises people: splitting work across several keys does not buy more throughput, because they share the organization budget. Keys are for separating authority and rotation, not for raising limits.
Being limited
Exceeding a limit returns 429 with code RATE_LIMITED:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{
"error": {
"code": "RATE_LIMITED",
"message": "API request limit exceeded",
"details": { "retryAfterSeconds": 12 }
}
}
The delay appears twice on purpose. Retry-After is the conventional HTTP header that
proxies, HTTP clients and generic retry middleware already understand;
details.retryAfterSeconds is the same number where structured error handling can reach
it without parsing headers. They always agree. Prefer the header.
Seeing what is left
Every response to an API-key request carries what is left of the budget it drew on, by the tighter limit (the key’s or the organization’s), for the current minute:
RateLimit-Limit: 300
RateLimit-Remaining: 287
RateLimit-Reset: 41
RateLimit-Reset is seconds until the window starts over. Pace a batch job by
RateLimit-Remaining and you will rarely see a 429 at all.
Backing off correctly
- On 429, wait at least
Retry-Afterseconds. Do not retry sooner — an early retry is refused and counts against the limit again. - Add jitter. Many workers that all woke on the same limit and all slept the same interval will collide again on the same second.
- Cap total attempts and surface the failure rather than retrying forever.
- Apply exponential backoff on 5xx, where no
Retry-Afteris given.
A reasonable default: up to 3 retries, Retry-After when present, otherwise
2^attempt seconds, each with ±25% jitter.
Only retry operations that are safe to repeat — see Idempotency. All
GET requests are safe. A POST without an idempotency key is not.
Staying under the limit
- Batch.
POST /v1/groups/{id}/contactsadds many contacts in one call. Adding them one per request is both slower and the usual cause of hitting a limit.POST /v1/transactional/messages/batchtakes up to 100 sends, but counts as one request per send: it saves round trips, not budget. - Raise
limitwhen paginating. Reading 100 at a time costs a fifth of the requests that reading 20 at a time does. - Do not poll for completion. Sends are asynchronous; subscribe to webhooks instead of asking repeatedly whether a campaign has finished.
- Bound your concurrency. One worker respecting the limit is better than twenty workers discovering it simultaneously.
A 503 SERVICE_UNAVAILABLE from the rate limiter itself means the limiter could not be
reached, not that you were limited. Back off and retry.