For DevelopersAPIRate Limits & User Limits

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

EndpointLimitWindowScope
POST /info1,200 requests1 minutePer IP address
POST /exchange (orders)100 orders1 secondPer address
POST /exchange (cancels)200 cancels1 secondPer address
API key creation5 requests1 minutePer wallet address
Authenticated reads (HMAC)30 requests1 secondPer API key
Public reads (REST)60 requests1 secondPer IP address
WebSocket subscriptions100 channelsPer connectionPer connection

Rate Limit Headers

Every REST response includes rate limit information:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix 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.