Skip to content

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

LimitValue
Requests per key60 per second on average, in bursts of up to 120
Failed authentications30 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 GET requests. See Webhooks.
  • Back off. On 429, wait for Retry-After, then retry, with the same Idempotency-Key for POST requests. 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=100 and 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