Rate limitsMenu
On this page

Rate limits

Updated

Stay within the per-key and per-workspace request limits, and back off correctly with Retry-After.

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
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:

HTTP
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

  1. On 429, wait at least Retry-After seconds. Do not retry sooner — an early retry is refused and counts against the limit again.
  2. Add jitter. Many workers that all woke on the same limit and all slept the same interval will collide again on the same second.
  3. Cap total attempts and surface the failure rather than retrying forever.
  4. Honour Retry-After on 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}/contacts adds 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/batch takes up to 100 messages, but counts as one request per message: it saves round trips, not budget. The same holds for POST /v1/contacts/batch.
  • Raise limit when 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.

Next steps