Menu

Rate limits

Per-key and per-org limits, Retry-After, and correct backoff

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

  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. Apply exponential backoff on 5xx, where no Retry-After is 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}/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 sends, but counts as one request per send: it saves round trips, not budget.
  • 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.

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.

Next steps