Rate Limiting
This guide explains how rate limits work on the Synthetix off-chain trading APIs and how to build clients that stay within limits.
Overview
Rate limits protect the system and ensure fair access for all users. Limits apply per subaccount and, on WebSocket, per IP address.
| API | Per-subaccount limit | Per-IP limit |
|---|---|---|
REST (POST /v1/trade) | Yes | No |
| WebSocket | Yes (trade actions only) | Yes (all actions) |
How Limits Work
Limits use a token bucket model:
- Each subaccount (and on WebSocket, each IP) has a bucket of tokens
- Each request consumes tokens based on the action
- Tokens refill over time
- If a request would exceed available tokens, it is rejected with
429 Too Many Requests
Rate limit buckets (Mainnet)
Token costs are the same across environments. Bucket sizes may differ on Testnet. See Environments for environment-specific configuration.
| Limit type | Tokens per window | Window | Scope |
|---|---|---|---|
| Per-subaccount (REST) | 1,000 | 10 seconds | POST /v1/trade |
| Per-subaccount (WebSocket) | 1,000 | 10 seconds | Trade actions only |
| Per-IP (WebSocket) | 10,000 | 10 seconds | All actions (trade + info) |
Token costs — Trade actions (REST and WebSocket subaccount)
These costs apply to both REST POST /v1/trade and WebSocket trade actions. For placeOrders, cost = base cost × number of orders in the batch.
| Action | Base cost | Notes |
|---|---|---|
addDelegatedSigner | 100 | |
cancelAllOrders | 2 | |
cancelOrders | 2 | |
createSubaccount | 100 | |
getBalanceUpdates | 100 | |
getDelegatedSigners | 20 | |
getDelegationsForDelegate | 20 | |
getFeeRate | 10 | |
getFees | 10 | |
getFundingPayments | 100 | |
getOpenOrders | 10 | |
getOrderHistory | 50 | |
getPerformanceHistory | 100 | |
getPortfolio | 10 | |
getPositions | 10 | |
getRateLimits | 20 | |
getReferral | 20 | |
getSubAccount | 20 | |
getSubAccounts | 20 | |
getTrades | 20 | |
getTransfers | 10 | |
modifyOrder | 5 | |
modifyOrderBatch | 5 | |
placeIsolatedOrder | 5 | |
placeOrders | 5 | Cost = 5 × number of orders in batch |
removeAllDelegatedSigners | 100 | |
removeDelegatedSigner | 100 | |
scheduleCancel | 5 | |
transferCollateral | 5 | |
updateIsolatedMargin | 5 | |
updateLeverage | 5 | |
updateSubAccountName | 100 | |
voluntaryAutoExchange | 100 | |
withdrawCollateral | 5 |
Token costs — WebSocket info actions (per-IP limit only)
These costs apply only to the per-IP bucket on WebSocket. Info actions do not consume from the per-subaccount bucket.
| Action | Cost |
|---|---|
getCandles | 200 |
getCollaterals | 50 |
getFundingRate | 250 |
getFundingRateHistory | 1,000 |
getIsWhitelisted | 250 |
getLastTrades | 200 |
getMarketPrices | 200 |
getMarkets | 50 |
getMids | 50 |
getOpenInterest | 50 |
getOrderbook | 200 |
getSubAccountIds | 250 |
Trade actions on WebSocket also consume from the per-IP bucket using the same costs as the trade table above.
REST API
- Endpoint:
POST /v1/trade - Limit: Per authenticated subaccount
- Scope: All trade actions
WebSocket API
Two limits apply:
- Per-IP limit — Applies to all actions (trade and info). Checked first.
- Per-subaccount limit — Applies to trade actions only.
Both must pass for a trade action. Multiple WebSocket connections from the same IP share the same per-IP budget.
Rate Limit Response
When you exceed a limit, the API returns:
- HTTP status:
429 Too Many Requests - Error code:
RATE_LIMIT_EXCEEDED - Category:
RATE_LIMIT - Retryable:
true
REST example
{
"success": false,
"clientRequestId": "abc-123",
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"category": "RATE_LIMIT",
"message": "Rate limit exceeded for action 'placeOrders'",
"retryable": true
}
}WebSocket example (subaccount limit)
{
"success": false,
"clientRequestId": "abc-123",
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"category": "RATE_LIMIT",
"message": "Rate limit exceeded for action 'placeOrders'"
}
}WebSocket example (IP limit)
{
"success": false,
"clientRequestId": "abc-123",
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"category": "RATE_LIMIT",
"message": "IP rate limit exceeded"
}
}Implementation Notes for Bots
1. Use exponential backoff on 429
When you receive 429 with RATE_LIMIT_EXCEEDED, wait before retrying. Start with about 1 second and increase on repeated failures (e.g. 1s, 2s, 4s).
2. Pace placeOrders requests
Each order in a batch consumes 5 tokens (cost = 5 × batch size). A batch of 20 orders consumes 100 tokens. With a 1,000-token subaccount limit per 10 seconds, large batches can hit limits quickly. Consider:
- Smaller batches
- Spacing requests over time
- Avoiding bursts of many orders at once
3. Share IP budget on WebSocket
Per-IP limits apply across all WebSocket connections from the same IP. If you run multiple bots or connections from one server, they share the same IP budget.
4. Treat rate limits as retryable
Rate limit errors are marked retryable: true. Retry after a short delay instead of treating them as permanent failures.
5. Avoid hammering after a 429
Back off and reduce request rate after hitting a limit. Continuing at the same rate will keep triggering 429s.
Related Documentation
- Environments - Environment-specific configuration
- Error Handling - Complete error handling guide
- General Information - API overview
- Place Orders - Order placement endpoint
- WebSocket API - WebSocket connection documentation