Rate limits
How many requests you can make, what happens when you exceed, and how to build a client that backs off cleanly.
The API caps how many requests a single client can make. Caps exist to keep the service responsive for everyone. Most clients never come close to hitting them.
Two layers of caps compose, and they work differently:
- Per-account caps on the trading API (
/trade/v1/...) are token buckets: you can spend up to a bucket's burst budget instantly, and tokens return at a steady refill rate. They're tied to your account — every request you authenticate is metered, regardless of which IP you call from. - Per-IP caps at the edge are sliding windows: total requests from one IP over the trailing 60 seconds, counted on every request, authenticated or not. A safety net that mainly catches misbehaving bots and unauthenticated abuse.
Per-account caps
Caps apply per user account (not per token — if you mint multiple personal access tokens, they share one envelope).
Five independent buckets. For each, you can spend up to the burst instantly; tokens come back at the refill rate, capped at the burst.
| Bucket | What it covers | Authenticated tier | Market-maker tier |
|---|---|---|---|
| Read | Every authenticated request debits this — see request costs. | 100 burst, refills at 50/sec | 200 burst, refills at 500/sec |
| Place | Single order placement (POST /trade/v1/orders and POST /trade/v1/orders/signed). | 100 burst, refills at 10/sec | 500 burst, refills at 100/sec |
| Place-batch | Batched pre-signed orders, account batches, and batch supersedes. One token per call, whatever the batch size. | 10 burst, refills at 1/sec | 100 burst, refills at 20/sec |
| Cancel | Cancelling one order (DELETE /trade/v1/orders/{order_id}). | 200 burst, refills at 25/sec | 2,000 burst, refills at 250/sec |
| Cancel-all | Cancelling everything open (POST /trade/v1/orders/cancel-all). | 10 burst, refills at 1/sec | 10 burst, refills at 1/sec |
The batch bucket is separate so a ladder re-quote can't drain your
single-order Place budget. Because one batch call costs one token no
matter how many orders it carries (up to 32), batching is far cheaper on
your budget than the same orders sent one at a time — a market-maker-tier
account can place up to 32 × 20 = 640 orders/sec through it.
Cancel budgets sit well above place budgets on purpose: pulling your quotes to reduce risk should never be the thing that gets throttled. When you're flattening all your exposure, prefer one cancel-all call over looping individual cancels — a single request removes every resting order you have.
Request costs
Most requests cost 1 token. The exceptions, exactly:
| Request | Cost |
|---|---|
GET /trade/v1/portfolio/summary | 5 Read |
POST /trade/v1/portfolio/positions/list | 5 Read |
POST /trade/v1/portfolio/open-orders/list | 5 Read |
GET /trade/v1/portfolio/trades | 5 Read |
GET /trade/v1/portfolio/activities | 5 Read |
Everything else costs 1. Place, place-batch, cancel, and cancel-all requests debit 1 token from their own bucket plus their Read cost — Read meters your total request rate, the others meter the specific action. A batch call debits exactly one Place-batch token regardless of how many orders it carries, even if some of those orders are rejected.
What counts against what
- Rejected orders still count. An order that fails validation or bounces on insufficient balance consumed a Place token getting there. The buckets meter requests, not successful orders.
- A
429does not count. A request turned away with429was not processed and consumed nothing from the bucket that rejected it, so it's always safe to resend the same request after backing off — includingPOST /trade/v1/orders. A429means the order was never placed. One caveat: every trading request also spends from yourreadbudget on the way in, and that spend happens even when theplaceorcancelbucket then turns the request away — a tight retry loop that keeps hitting429s will still drainread, so back off rather than hammer. - Streaming messages are free; connecting is not. Messages over an open WebSocket consume nothing. Opening a connection counts as one request against the per-IP trading cap below, so a client stuck in a tight reconnect loop will eventually be blocked at the edge — reconnect with backoff.
Market-maker tier
Every account starts at the Authenticated tier. If your account is assigned
the Market Maker tier, the larger limits apply from the next request; no
new token or sign-in is required. Treat the returned headers as authoritative
instead of assuming a tier from client configuration.
Response headers
Every authenticated response reports the bucket you're closest to draining:
| Header | Meaning |
|---|---|
X-RateLimit-Bucket | The bucket closest to empty. |
X-RateLimit-Limit | Bucket capacity (the burst budget). |
X-RateLimit-Remaining | Tokens left right now. |
X-RateLimit-Reset | Seconds until the bucket is fully refilled. |
When a request trips a bucket you get a 429 Too Many Requests:
| Header | Meaning |
|---|---|
Retry-After | Seconds to wait before this request will fit. |
X-RateLimit-Bucket | Which bucket tripped: read, place, place_batch, cancel, or cancel_all. |
X-RateLimit-Limit | Capacity of the bucket that tripped. |
X-RateLimit-Remaining | Tokens left in that bucket right now. |
X-RateLimit-Reset | Seconds until that bucket is back to full. |
Retry-After and X-RateLimit-Reset answer different questions: wait
Retry-After seconds and this one request fits again; wait
X-RateLimit-Reset seconds and your full burst budget is back.
Handling a 429
Wait the full Retry-After, add a little jitter, then resend the
identical request:
Retrying a rate-limited order placement is safe: the 429 means the
request was not processed, so resending it cannot double-place the
order.
Per-IP edge caps
These apply before any account lookup, on every request. Each cap is a sliding window — total requests from your IP over the trailing 60 seconds:
| What you're calling | Cap |
|---|---|
Trading, including stream connections (/trade/v1/...) | 3,000 per minute — about 50 per second |
Market data, events, prices, search (/api/v1/...) | 6,000 per minute — about 100 per second |
| Everything else (web pages, docs) | 12,000 per minute — about 200 per second |
The two specific caps apply on top of the catch-all, so a burst of
/api/v1/ calls counts against both the API cap and the overall cap.
The per-IP trading cap matches the standard tier's sustained request
rate, so for a single Authenticated-tier account on one IP the account
buckets are what you hit in practice — the edge only comes into play if
you hold every bucket at its absolute maximum for a full minute. A
market-maker-tier account running at full rate through a single IP can
reach the edge cap first — if that's your setup, mention it when you
request the tier and we'll plan around it with you.
Edge counting has a brief settling delay: a sudden spike may slip
through for a few seconds before blocking starts, and may take a few
seconds to clear after you slow down. Treat the cap as an average over
the window, not an instantaneous limit, and build your client to back
off on 429 rather than ride right up to the line.
An edge-generated 429 carries Retry-After: 60 but does not carry the
account-bucket X-RateLimit-* headers. Handle Retry-After even when the
bucket headers are absent.
Designing a client that stays well under
- Watch the response headers.
X-RateLimit-Remainingtells you exactly how much budget you have left. Slow down before you hit zero. - Back off on
429. Respect the fullRetry-Afterwindow. Immediate retries keep you blocked and waste your budget further. - Poll slowly. Market data changes by the second, not the millisecond. A poll every 1-5 seconds is plenty for most use cases.
- Use streaming for live updates. The orderbook and account event streams push changes over WebSocket as they happen — one request to connect, nothing after that. See Streaming and Account stream.
- Flatten with cancel-all. One cancel-all beats hundreds of individual cancels, for you and for the book.
If you're behind a shared IP
The per-IP edge caps are per source IP. If your application calls the API from a shared egress — corporate network, NAT gateway, a serverless function pool with a small IP range — every caller behind that network shares the per-IP cap. The per-account caps above are unaffected by shared IPs.
In practice the per-IP caps almost never bite because they're well above normal usage. If you're operating from an egress that genuinely needs more headroom, get in touch.
Testing changes
Test backoff and retry changes with test funds before sending traffic to this deployment.
Treat headers as authoritative
Do not hardcode the table as a permanent contract. Use the returned limit, remaining, reset, and retry headers so your client follows the limits active for its account.