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