Rate limits

View as Markdown

Every authenticated request counts toward 500 per minute per API key (per user for signed-in dashboard sessions). Operations that create quotes, uploads and orders, or move money, also count toward 60 per minute. Public sign-up, catalog and shipping endpoints are limited per IP address. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; past a limit the answer is 429 rate_limited with a Retry-After header.

The limits

Limits are counted in fixed windows. Each operation lists the limits it counts toward in x-ratelimit (limit requests per window_seconds, per API key or user, or per IP address); these are the defaults.

Applies toLimitCounted per
Every authenticated request500 per minuteAPI key, or user for dashboard sessions
Creating quotes, uploads and orders, cancelling orders, top-ups, card setup and withdrawals (on top of the above)60 per minuteAPI key, or user
Integrator sign-up (create_integrator_account)10 per hourIP address
Resending the confirmation email (resend_integrator_confirmation)5 per hourIP address
Catalog and shipping reads (materials, categories, shipping rates and quotes)120 per minuteIP address

get_health and the OpenAPI document are not limited.

Every limited response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds) for the limit closest to being reached. Past a limit the API answers 429 with code rate_limited, a Retry-After header in seconds, and details with limit, window_seconds, reset_at and retry_after_seconds. A refused request does not count. A request replayed with the same Idempotency-Key counts like any other request.

Quotes, uploads, orders and payments

Public endpoints

Handling a 429

Watch X-RateLimit-Remaining and slow down before it reaches 0. When a request is refused, wait for the number of seconds in Retry-After, then send it again. Retry a write with the same Idempotency-Key, so it never runs twice.

Retry after a 429
async function callWithRetry(url, init, attempts = 5) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(url, init);
    if (res.status !== 429 || attempt === attempts) return res;
    // Retry-After is in seconds; wait at least that long.
    const seconds = Number(res.headers.get('retry-after') ?? 1);
    await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
  }
}
A 429 response body
{
  "status_code": 429,
  "error": "Too Many Requests",
  "code": "rate_limited",
  "message": "Too many requests with this API key: the limit is 500 per minute. Retry after 42 seconds (at 2026-10-22T10:01:00.000Z).",
  "details": {
    "limit": 500,
    "window_seconds": 60,
    "reset_at": "2026-10-22T10:01:00.000Z",
    "retry_after_seconds": 42,
    "policy": "per_credential",
    "request_id": "req_6Yb2kQe0VxLr4TnPz8sWa1Mc"
  }
}