Rate limits protect the exchange from bursts and keep latency predictable. Limits are applied per address and per IP.
Defaults
| Scope | Limit |
|---|---|
| Address weight | 1,200 per minute |
| IP weight | 2,400 per minute |
| Orders | 20 per second per account |
| Cancels | 100 per second per account |
| WebSocket messages | 30 per second per connection |
| WebSocket subscriptions | 200 per connection |
Endpoint weights
| Endpoint | Weight |
|---|---|
meta | 2 |
l2Book | 2 |
allMids | 2 |
clearinghouseState | 2 |
openOrders | 2 |
userFills | 20 |
userFillsByTime | 20 |
candleSnapshot | 4 |
fundingHistory | 4 |
order (place) | 4 |
cancel | 2 |
modify | 3 |
Batching counts per action
A batched request with 10 cancels consumes 20 weight, not 2. Batch for latency, not for evading the limit.
Headers
Every response includes:
code
X-RateLimit-Limit: 1200
X-RateLimit-Remaining: 842
X-RateLimit-Reset: 1762732860A 429 response includes Retry-After in seconds.
Handling 429
code
async function withRetry<T>(fn: () => Promise<T>, attempts = 5): Promise<T> {
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await fn();
} catch (error) {
if (!(error instanceof RateLimitError)) throw error;
const wait = error.retryAfter * 1000 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, wait));
}
}
throw new Error("Rate limit retries exhausted");
}Designing within limits
- Use WebSocket for live data; poll only for reconciliation.
- Cache
metaandspotMetafor at least 60 seconds. - Use
userFillsByTimefor history instead of repeatedly fetchinguserFills. - Give each strategy its own API wallet and budget.
- Prefer cancel-all or batch cancel when re-quoting.