Rate limits

View as Markdown

Requests are rate-limited per API key. The limits are sized for production traffic; you should never hit them under normal use, but build retry-on-429 into any high-volume integration.


Limits#

There is no per-minute cap on payments. Charges, captures, payment-intent confirms and refunds are not counted against a payment budget: a promotion that puts many buyers on the checkout in the same minute is never refused for volume. Card testing is stopped by fraud screening (velocity on IP, card and email, before the issuer is asked), not by counting your payments.

Global volume ceiling#

Every endpoint is subject to a per-principal volume limit across three rolling windows:

WindowDefault limit
1 second200 requests
1 minute5,000 requests
1 hour100,000 requests

The principal is your API key (falling back to the client IP for unauthenticated calls). These ceilings are configurable per merchant. Contact support if your steady-state volume needs more.

These paths are exempt from the global ceiling, so health probes and the docs surface are never throttled:

/health   /healthz   /readyz   /v1/docs   /v1/openapi.json   /v1/ping

When you exceed a limit#

You get a 429 with the standard envelope:

json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded (200/1s)"
  }
}

Every 429 carries a Retry-After header (seconds): the window that was hit (1, 60, or 3600), or 60 for a 429 that did not set its own value. There are no RateLimit-* headers. The message names the limit that was hit.


Handling 429 well#

Honour Retry-After. It names the window that was hit (1, 60 or 3600 seconds), so a retry sooner than that will 429 again:

js
async function withRetry(fn, attempts = 4) {
  for (let i = 0; i < attempts; i++) {
    const res = await fn();
    if (res.status !== 429) return res;
    const wait = Number(res.headers.get("Retry-After") ?? 60) * 1000;
    await new Promise(r => setTimeout(r, wait));
  }
  throw new Error("rate limited after retries");
}

For POST /charges, pair the retry with an Idempotency-Key so a retried charge replays the original result instead of charging twice.

If your steady-state volume needs higher limits, contact support. The per-key ceilings are configurable.