Rate Limits
Per-wallet rate limiting for order operations and API requests.
Overview
Rate limiting uses fixed 60-second windows. Wallet limits come from the wallet's tier and are applied to:
- Order placements (
POST /order,PUT /order, WebSocketPlaceOrder, and TEE perp order submissions) - Order cancellations (
DELETE /order,DELETE /order_cloid, and TEE perp cancels) - API requests (wallet-scoped reads and other signed writes)
Separate limits keyed by network source, with a process-wide ceiling, apply to cancels, agent authorization, MMP writes, and public risk simulations.
Counters are held in memory by each API process. They are not shared across replicas, so treat the published numbers as the most a single process will admit, not a cluster-wide budget.
Default Limits
| Tier | Orders/min | Cancels/min | API Requests/min | Max Open Orders | Max Positions |
|---|---|---|---|---|---|
| Default | 60 | 120 | 600 | 100 | 50 |
| Tier 1 | 30 | 60 | 300 | 50 | 20 |
| Tier 2 | 120 | 300 | 1,200 | 500 | 200 |
| Market Maker | 1,200 | 2,400 | 6,000 | 5,000 | Unlimited |
New wallets receive default limits. Contact support for tier upgrades. Your current API request limit is in the X-RateLimit-Limit header of any wallet-scoped read, for example GET /orders?wallet=....
Rate Limit Response
When a rate limit is exceeded, the API returns 429 Too Many Requests:
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded for OrderPlacement: 60 per minute, retry after 45 seconds",
"retry_after_secs": 45,
"limit": 60
}
Response Headers
Wallet-limited endpoints return these headers. A 429 carries X-RateLimit-Limit, X-RateLimit-Remaining: 0, and Retry-After, but no X-RateLimit-Reset:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed per window | 60 |
X-RateLimit-Remaining | Requests remaining in current window | 42 |
X-RateLimit-Reset | Unix timestamp in seconds when the window resets (success responses only) | 1737312060 |
Retry-After | Seconds until retry (only on 429) | 45 |
Example Response Headers
Successful request:
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1737312060
Rate limited request:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Rate Limit Categories
Rate limits are tracked separately for each action type:
Order Placement
Applies to:
POST /orderandPUT /order(options orders)- WebSocket
PlaceOrder POST /bulk_order(bulk orders, each order in batch counts)PUT /bulk_order(bulk replacements, each replacement counts)POST /v1/tee-perp/orders/submit,POST /v1/tee-perp/orders/submit-trigger, andPOST /v1/tee-perp/orders/submit-batch(TEE perp orders, a batch counts once)POST /v1/tee-perp/leverage/submit
POST /bulk_order and PUT /bulk_order are capped at 50 entries per request.
Order Cancellation
Applies to:
DELETE /orderandDELETE /order_cloidDELETE /bulk_order(bulk cancels, each cancel counts)DELETE /bulk_order_cloid(bulk cancels by client order ID, each cancel counts)POST /v1/tee-perp/cancels/submit,POST /v1/tee-perp/cancels/submit-by-oid, andPOST /v1/tee-perp/schedule-cancel/submit(TEE perp cancels)POST /v1/tee-perp/cancels/submit-prepared(manager-signed TEE perp cancel, counted against the manager wallet rather than the Account)
Every cancel route, including DELETE /bulk_order and DELETE /bulk_order_cloid, is also limited before signature verification to 6,000 cancel legs per network source and 60,000 legs process-wide per minute. A bulk cancel counts one leg per entry.
TEE Perp Routes
The TEE perp routes listed above, except submit-prepared (manager-signed and counted against the manager wallet), are signed by an API wallet and counted against the Account:
- Tier: The limit comes from the Account's tier, not from the API wallet that signed the request.
- Shared window: TEE perp orders and options orders sent for the same Account address use one order window. Their cancels use one cancel window.
- API wallets: An Account with several API wallets has one window. A new or rotated API wallet does not start a new one.
- Headers: Every counted request returns the
X-RateLimit-*headers, even when it is rejected later. A request rejected before it is counted, such as one with an unauthorized signature, returns none.
API Requests
Counted per wallet against the tier's API Requests/min:
- Wallet-scoped reads, keyed on the
walletquery parameter - Signed writes not listed above, such as MMP configuration and margin mode
RFQ Submission
POST /rfq/request and the WebSocket RFQ submit commands are limited to 10 per wallet per minute, independent of tier.
Risk Simulation
Public portfolio and order simulations (POST /risk/simulate/portfolio,
POST /risk/simulate/orders, and the perp preview POST /v1/tee-perp/preview)
have separate limits of 60 requests per network source and 600 requests
process-wide per minute. At most four
simulations run concurrently in one API process. A request that exceeds a quota
or finds the simulation workers saturated returns 429 Too Many Requests.
Agent Authorization
POST /approve-agent, DELETE /revoke-agent, and DELETE /revoke-all-agents are limited before signature verification to 120 requests per network source and 1,200 requests process-wide per minute.
MMP Writes
MMP configuration writes are limited before signature verification to 120 requests per network source and 1,200 requests process-wide per minute, in addition to the wallet's API request limit.
Position and Order Limits
In addition to rate limits, wallets have maximum open order and position limits:
| Limit Type | Description | Rejection |
|---|---|---|
| Max Open Orders | Maximum concurrent open orders | New order rejected while the wallet is at the limit |
| Max Positions | Maximum unique positions (-1 = unlimited) | Order rejected if it would open a new position |
These are order rejections, not HTTP errors. The order response is HTTP 200 with status: "REJECTED" and a reason such as:
max_open_orders_exceeded: current=100, limit=100
max_positions_exceeded: wallet 0x... has 50 positions, limit is 50
Best Practices
Handling Rate Limits
- Monitor headers: Track
X-RateLimit-Remainingproactively - Respect Retry-After: Wait the specified duration before retrying
- Implement backoff: Use exponential backoff on repeated 429s
import time
def place_order_with_retry(order, max_retries=3):
for attempt in range(max_retries):
response = api.place_order(order)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
time.sleep(retry_after)
continue
return response
raise RateLimitError("Max retries exceeded")
Optimizing Request Usage
- Use bulk endpoints:
POST /bulk_orderfor multiple orders in one request - Batch cancels:
DELETE /bulk_orderorDELETE /bulk_order_cloidinstead of multipleDELETE /orderrequests - WebSocket for updates: Subscribe to order updates instead of polling
GET /orders
Monitoring
Track these client-side metrics:
- Request rate per action type
X-RateLimit-Remainingtrends- 429 response frequency
- Average
Retry-Afterdurations
Error Reference
| HTTP Status | Error Code | Description |
|---|---|---|
| 429 | rate_limit_exceeded | Rate limit exceeded, retry after specified time |
See Errors for the full error reference.