Rate limits cap how many requests a key may make in a window; past it, the API returns
429 RATE_LIMITED.
Calls draw on one of three budgets. High-volume calls have their own, larger one, so a burst of receipts or a contact sync never starves the calls that manage your contacts and campaigns, and the reverse. Events have a third, the largest, so page views never spend what your sends need. Every other call shares the general budget.
| Budget | Routes |
|---|---|
| Events | POST /v1/events/track, POST /v1/events/batch, and the app SDKs’ events |
| High volume | POST /v1/transactional/messages and /batch, POST /v1/contacts/batch |
| General | Every other /v1 call, including POST /v1/contacts/identify |
A batch counts as one request per item: 100 contacts in POST /v1/contacts/batch cost
100 of the high-volume budget, so at the per-key default a sync runs at 3,000 contacts a
minute. Events count the same way, from a server or an app: a workspace records up to
60,000 events a minute by default.
| Limit | General | High volume | Events | Scope |
|---|---|---|---|---|
| Per API key | 300 / minute | 3,000 / minute | (the workspace’s) | One key |
| Per workspace | 1,200 / minute | 6,000 / minute | 60,000 / minute | All keys in the workspace combined; for events, your apps’ events too |
A flood guard of 72,000 requests a minute per IP sits above both, so one server can call for many workspaces. 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-workspace limit is the one that surprises integrators: splitting work across several keys does not buy more throughput, because they share the workspace 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": "Too many API requests. Wait for the time in Retry-After before trying again.",
"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.
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.
Seeing what is left
Every response given after the key’s budget check, a 429 from that check included,
carries what is left of the budget it drew on, by the tighter limit (the key’s or the
workspace’s), for the current minute. The per-IP 429 and any refusal made before the
key check (a missing or bad key, for example) carry no RateLimit-* headers; the per-IP
429 still has Retry-After:
RateLimit-Limit: 300
RateLimit-Remaining: 287
RateLimit-Reset: 41RateLimit-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.
- Honour
Retry-Afteron any status that sends it, not only 429: a 503 carries it when the wait is known, as when checking a sending domain while the email provider is busy (30 seconds). Otherwise apply exponential backoff on 5xx.
Retry-After is always whole seconds, never a date. 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. All GET requests are safe. For a write,
see Idempotency and the retry note on each operation’s reference page:
some take an Idempotency-Key, some are safe because a
repeat sets the same values again, and some are not safe to repeat. A 429 means the
request was refused before it ran, so it can be sent again after the wait.
Staying under the limit
- Batch.
POST /v1/lists/{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 messages, but counts as one request per message: it saves round trips, not budget. The same holds forPOST /v1/contacts/batch. - 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.
Request size
Body size limits per route are in Limits.