Skip to content
Get an API key

Every API key is rate limited per plan. Exceeding the window returns 429 RATE_LIMITED with retryable: true in the standard error envelope.

Rate limiting and credits are separate controls. The limiter caps how fast you may call; credits cap how much you may call in total. A request can pass the limiter and still fail with 402 INSUFFICIENT_CREDITS, or fail the limiter without ever being charged.

Plan Requests API keys Concurrent WebSocket streams
Free 60 / min 1 2
Builder 300 / min 3 20
Pro 3,000 / min 10 100
Scale 10,000 / min 25 500
Enterprise negotiated negotiated negotiated

The window is a rolling 60 seconds per API key, not per account and not per endpoint. Two keys on one account each get the full allowance; one key spread across ten processes shares a single allowance.

Enterprise plans and individual keys can carry a bespoke override. An override is applied as a per-second window rather than per-minute — so a key provisioned at 50/s is allowed 50 in any given second, not 3,000 spread freely across a minute.

{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "rate limit exceeded — retry shortly",
"retryable": true
}
}

With no server-supplied delay, use exponential backoff with jitter. The window is 60 seconds, so a client retrying every second spends its next allowance on failures.

async function withRetry<T>(call: () => Promise<T>, attempts = 5): Promise<T> {
for (let attempt = 0; ; attempt++) {
try {
return await call();
} catch (err) {
const code = (err as { code?: string }).code;
// Only these are worth retrying. A 400 or 401 will fail identically forever.
if (code !== 'RATE_LIMITED' && code !== 'PLATFORM_UNAVAILABLE') throw err;
if (attempt >= attempts - 1) throw err;
// 1s, 2s, 4s, 8s … plus jitter so parallel workers do not resynchronise.
const backoff = 2 ** attempt * 1000 + Math.random() * 1000;
await new Promise((r) => setTimeout(r, backoff));
}
}
}

The TypeScript SDK retries GET requests once on 429 by default (retryOn429). Writes are never auto-retried — a submit or cancel that may have reached the venue must not be replayed by a client library. For those, retry deliberately and send an Idempotency-Key; see Trading & execution.

Code HTTP Retry?
RATE_LIMITED 429 Yes — back off, then retry
PLATFORM_UNAVAILABLE 503 Yes — back off, then retry
CATALOG_UNAVAILABLE / HISTORY_UNAVAILABLE 503 Yes — back off, then retry
INSUFFICIENT_CREDITS 402 No — retrying cannot succeed until the balance changes
VALIDATION_ERROR 400 No — fix the request
UNAUTHORIZED 401 No — fix the key
NOT_SUPPORTED 400 / 501 No — an honest capability gap, not a failure

retryable is present on every error and is the field to branch on. Treat it as authoritative over the HTTP status.

  • Prefer cursors over parallel offset pages. Following nextCursor keeps one request in flight; twenty parallel offset pages spend twenty of the allowance in one second.
  • Batch where a batch verb exists. fetchOrderBooks takes many outcomes in one request. Note it is priced by items, so it saves allowance rather than credits.
  • Stream instead of polling. A WebSocket subscription delivers book and trade updates without consuming the request window at all. Polling a book every second on Free spends the entire minute allowance on one market.
  • Cache what does not move. Venue capability maps (has) and taxonomy (fetchCategories, fetchTags) change rarely; re-fetching them per request is pure overhead.
  • Spread scheduled work. Offset cron jobs by a random delay so batch runs do not collide with each other or with interactive traffic.

If the limiter’s backing store is unreachable, the API fails open for reads — requests are served rather than rejected, and credits remain the spend backstop. Side-effecting routes fail closed with 503 PLATFORM_UNAVAILABLE instead, because replaying an uncertain write is worse than refusing it.

No special handling is required. It is documented so that a burst of 503s on writes while reads continue is recognisable as designed behaviour rather than a partial outage.