Rate limits and retries

Open in the interactive reference

Requests are rate-limited per client IP address, across all endpoints, and every client behind the same address shares the limit. A client over the limit is refused with 429 until its request rate falls back below the limit.

The limit is not part of the contract and can change, so do not build against a number. Keep concurrency modest, and prefer one paged walk to many parallel requests.

A 429 comes from the WAF in front of the API, not from an endpoint. It may carry a Retry-After header naming a fixed number of seconds to wait, and a JSON body with title, status and detail fields — this is rolling out environment by environment, so do not assume either is present. Honor Retry-After when it is present — it is a fixed value, not a countdown of time actually left in the window — and otherwise retry after a delay that doubles with each attempt. Add random jitter so that clients do not retry in step, and give up after a few attempts.

Retrying other failures

Status What to do
429 Back off and retry, as above.
409 IDEMPOTENCY_KEY_CONFLICT Wait, then retry with the same key.
500 Retry once after a short wait, and retry a write only with the same Idempotency-Key. A 500 can also mean the API was briefly unavailable while your environment was being changed, which passes on its own.
400, 401, 403, 404, 422 Do not retry unchanged: the same request fails the same way.

While your environment is being updated, a request waits up to 45 seconds for that to finish before it runs or fails. Set client timeouts longer than that.

Examples

A 429, where the WAF sends one

HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>
Content-Type: application/json

{ "title": "Too Many Requests", "status": 429,
  "detail": "…" }

Backing off, in Python

import random, time

def send_with_backoff(send, attempts=6):
    delay = 1.0
    for attempt in range(attempts):
        response = send()
        if response.status_code != 429 or attempt == attempts - 1:
            return response
        retry_after = response.headers.get('Retry-After')
        wait = float(retry_after) if retry_after else delay + random.uniform(0, delay)
        time.sleep(wait)
        delay *= 2