API fundamentals
Rate limits
How many requests a key may send, what a 429 means and how to back off.
The API limits how fast one key can send requests, so that a runaway loop on one integration cannot slow the others down. A request over the limit is refused with 429, and nothing is processed.
The limits
| Limit | Value |
|---|---|
| Requests per key | 60 per second on average, in bursts of up to 120 |
| Failed authentications | 30 per minute from one IP address |
Payer receipt links (receipt_url) | 30 per minute from one client address |
The request budget is a token bucket: it holds up to 120 requests, and refills 60 every second. Every key has its own budget, so a sandbox key and a live key, or two services with two keys, never use each other's. The numbers may change: rely on the Retry-After header, not on these values. The API sends no X-RateLimit-* headers: a 429 and its Retry-After are the only signal.
When you hit a limit
The answer is 429 with the code rate_limit_exceeded and a Retry-After header, in seconds:
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Rais-Request-Id: req_8f3a1c9d2b7e4a60c5d1
{"error":{"type":"rate_limit_error","code":"rate_limit_exceeded","message":"Too many requests. Slow down and retry after the delay in the Retry-After header.","param":null,"request_id":"req_8f3a1c9d2b7e4a60c5d1"}}Repeated requests with keys that do not exist or are malformed are throttled too, per IP address: after 30 failures in a minute, the address answers 429 with the message "Too many failed authentication attempts. Retry later." for the rest of that minute. While an address is throttled, every request from it is refused, even with a valid key. A correct integration never meets this, unless a stale or revoked key is still being retried somewhere, for example by a job nobody stopped: when you revoke or roll a key, make sure the old one is no longer in use.
Stay below the limits
- Use webhooks, not polling. One webhook replaces hundreds of
GETrequests. See Webhooks. - Back off. On
429, wait forRetry-After, then retry, with the sameIdempotency-KeyforPOSTrequests. See Idempotency and retries. - Spread batch work. A job that refunds or lists thousands of items should pace itself instead of sending a burst.
- Page efficiently. Use
limit=100and walk the list with cursors. See Pagination. - One key per service. Services that share a key share its budget.
import time
import requests
def get_with_backoff(url, headers, attempts=5):
for attempt in range(attempts):
response = requests.get(url, headers=headers, timeout=30)
if response.status_code != 429:
return response
time.sleep(float(response.headers.get("Retry-After", 2 ** attempt)))
return response