Rate limits

Per-key request limits, the 429 response with Retry-After, and how to back off correctly.

Requests are rate-limited per API key. Exceeding the limit returns 429 Too Many Requests with a Retry-After header telling you exactly how long to wait.

The limit

LimitValue
Per-key request rate600 requests / minute (10 req/s sustained)
WindowFixed one-minute window
Response when exceeded429 + Retry-After

The limit is per key, so isolating agents or environments onto separate keys gives each its own budget.

Request rate limits are separate from plan limits on email volume and storage — those are per-tenant and return a 403 cap_hit, not a 429. See Errors.

The 429 response

When you go over, the request has no side effects — nothing is partially performed — and the response is the standard error envelope plus a Retry-After header:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json

{
  "name": "rate_limited",
  "message": "Rate limit exceeded. Retry after 12 seconds."
}

Retry-After is the number of seconds until the current window resets.

Backing off correctly

Honor Retry-After when it's present, and otherwise back off exponentially with jitter. Cap the number of retries so a persistent 429 surfaces as an error rather than hanging.

async function withRetry(request: () => Promise<Response>, maxRetries = 5) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await request();
    if (res.status !== 429) return res;

    // Prefer the server's Retry-After; fall back to exponential backoff.
    const retryAfter = Number(res.headers.get("Retry-After"));
    const backoff = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : Math.min(2 ** attempt * 1000, 30_000) + Math.random() * 250;

    await new Promise((r) => setTimeout(r, backoff));
  }
  throw new Error("Rate limited: retries exhausted");
}
import random, time

def with_retry(request, max_retries=5):
    for attempt in range(max_retries + 1):
        res = request()
        if res.status_code != 429:
            return res

        # Prefer the server's Retry-After; fall back to exponential backoff.
        retry_after = res.headers.get("Retry-After")
        if retry_after is not None:
            backoff = int(retry_after)
        else:
            backoff = min(2 ** attempt, 30) + random.random() * 0.25

        time.sleep(backoff)
    raise RuntimeError("Rate limited: retries exhausted")

The typed SDKs can do this for you — they expose the Retry-After value on the rate-limit error and optionally auto-retry with backoff.

On this page