Rate limits¶
Limits are per organization, not per IP, and split across three buckets so cheap status polling can never starve your ability to send a payment.
The buckets¶
| Bucket | Routes | Burst | Sustained |
|---|---|---|---|
initiate |
POST /transactions, POST /batches |
30 | 5 / second |
write |
Every other POST, PUT, PATCH, DELETE |
60 | 10 / second |
read |
Every GET and HEAD |
300 | 50 / second |
Each is a token bucket: burst is how many requests you can make back to back from full, and sustained is how fast tokens refill.
At 5 payments a second sustained, one credential can start 18,000 payments an hour: far beyond any real corridor volume, and far below the point where a runaway loop does damage.
Why initiate is separate¶
POST /transactions is categorically more expensive than a status read. It
writes a compliance record and starts a durable process that keeps running,
calling providers and posting ledger entries, long after the HTTP response is sent.
Sharing a bucket with reads would let a client start thousands of payments inside an allowance sized for polling.
A batch costs one token per item¶
POST /batches is in the same bucket, and it is charged one token per item,
not one per request: a batch starts as many payments as it has items, and the
bucket exists to bound how many payments you start. A 30-item batch empties a full
initiate bucket. Size batches to fit, or expect a 429 and retry after
retry-after seconds with the same batchId.
The defaults above are generated into the API reference from the code, as is the bucket each endpoint draws from.
Headers¶
Every authenticated response carries them, not only a 429:
| Header | Meaning |
|---|---|
ratelimit-limit |
The bucket's capacity |
ratelimit-remaining |
Tokens left right now |
ratelimit-reset |
Seconds until the bucket refills |
They are on every response so you can slow down before being rejected, rather than discovering the limit by hitting it.
When you are limited¶
{
"error": "rate_limited",
"message": "Too many initiate requests. Retry after 6s.",
"retryAfterSeconds": 6
}
Wait at least retryAfterSeconds, then retry with the same idempotency key.
A 429 means the request was rejected before doing anything, so retrying is
always safe.
Staying under the limit¶
Use webhooks instead of polling. The single biggest source of read traffic is polling every open payment. Register an endpoint and the problem disappears.
Watch ratelimit-remaining. When it drops below a threshold you choose, slow
down. Do not wait for the 429.
Back off exponentially. A tight retry loop against a limit keeps the bucket empty and makes the outage last longer.
Queue your sends. If you batch payments, pace them at your sustained rate rather than firing the whole batch and absorbing rejections.
Higher limits¶
The figures above are defaults. Per-organization limits can be raised: talk to
your account contact, ideally with a 429 rate from your own metrics so the
increase can be sized to real traffic.