Rate Limits
GX Exchange enforces rate limits to ensure fair access and system stability. This page documents all rate limit tiers, headers, and handling strategies.
Rate Limit Tiers
| Endpoint | Limit | Window | Scope |
|---|---|---|---|
POST /info | 1,200 requests | 1 minute | Per IP address |
POST /exchange (orders) | 100 orders | 1 second | Per address |
POST /exchange (cancels) | 200 cancels | 1 second | Per address |
| API key creation | 5 requests | 1 minute | Per wallet address |
| Authenticated reads (HMAC) | 30 requests | 1 second | Per API key |
| Public reads (REST) | 60 requests | 1 second | Per IP address |
| WebSocket subscriptions | 100 channels | Per connection | Per connection |
Rate Limit Headers
Every REST response includes rate limit information:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets |
Checking Your Rate Limit
Query your current rate limit usage programmatically:
POST /info
Content-Type: application/json
{ "type": "userRateLimit", "user": "0xYourAddress..." }Rate Limit Exceeded Response
When a rate limit is exceeded, the server responds with HTTP 429 Too Many Requests:
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 0.3 seconds.",
"retry_after": 0.3
}The Retry-After HTTP header is also set, indicating how many seconds to wait before retrying.
Handling Rate Limits
Backoff Strategy
async function rateLimitAwareRequest(
url: string,
body: any,
maxRetries: number = 5
): Promise<any> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (response.status === 429) {
const retryAfter = parseFloat(
response.headers.get("Retry-After") || "1"
);
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
return response.json();
}
throw new Error("Rate limit retries exhausted");
}import time
import requests
def rate_limit_aware_request(url: str, body: dict, max_retries: int = 5) -> dict:
for attempt in range(max_retries):
resp = requests.post(url, json=body)
if resp.status_code == 429:
retry_after = float(resp.headers.get("Retry-After", "1"))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
return resp.json()
raise Exception("Rate limit retries exhausted")Optimization Tips
Batch Orders
Instead of sending individual order requests, batch multiple orders into a single POST /exchange call. Each call can contain multiple orders in the orders array, and the entire batch counts as one request toward the rate limit.
{
"type": "order",
"orders": [
{ "a": 0, "b": true, "p": "67000.0", "s": "0.01", "r": false, "t": { "limit": { "tif": "Gtc" } } },
{ "a": 1, "b": false, "p": "3500.0", "s": "0.1", "r": false, "t": { "limit": { "tif": "Gtc" } } }
],
"grouping": "na"
}Use WebSocket for Market Data
Instead of polling the info endpoint for orderbook and trade data, subscribe to WebSocket channels. WebSocket subscriptions do not count toward the REST rate limit and deliver data with lower latency.
Cache Static Data
Market metadata (meta, spotMeta) changes infrequently. Cache these responses and refresh them periodically (e.g., every 60 seconds) rather than querying on every request.
Distribute Across Addresses
Rate limits for exchange actions are per-address. If you operate multiple strategies, consider using separate addresses (each with their own agent wallets) to distribute load.
IP-Level Blocking
Persistent or severe rate limit abuse may result in temporary IP-level blocks. If you believe you have been blocked incorrectly, wait 5 minutes and try again. For persistent issues, contact support.