Sliding-window per-key. Sandbox is 10× production. Load-test partners can upgrade to 50×.
| Endpoint category | Production sk_live_* | Sandbox sk_test_* |
|---|---|---|
Read (GET /v1/markets, GET /v1/candles, etc.) |
60 req/min | 600 req/min |
Account (GET /v1/account/*) |
30 req/min | 300 req/min |
Trade (POST /v1/orders, DELETE /v1/orders/{id}) |
30 req/min | 300 req/min |
Create (POST /v1/markets) |
10 req/min | 100 req/min |
Faucet (POST /v1/sandbox/faucet) |
n/a | 1 req/hour |
| WebSocket connections per key | 10 concurrent | 100 concurrent |
Every response includes:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1786190460 (UNIX seconds when window resets)
On 429 RATE_LIMITED you additionally get:
Retry-After: 13 (seconds until you can retry)
Partners running load tests, MMs, or high-frequency strategies can request higher tiers:
| Tier | Multiplier | Requires |
|---|---|---|
| Default | 1× | Any API key |
| Partner | 5× | Signed partner agreement |
| Market Maker | 20× | MM agreement + committed spread |
| Load Test (sandbox only) | up to 50× | Email partners@predictasiax.com with test plan |
Sandbox has a fleet-level circuit breaker: if VM CPU exceeds 80% for 30 seconds, all sandbox rate limits temporarily drop to production baseline regardless of tier. This protects sandbox availability for all partners. Circuit resets automatically when CPU falls below 60% for 60 seconds. WS event circuit_breaker fires when triggered.
Unauthenticated public reads (no X-Api-Key) are limited per source IP: 30 req/min. Mint an API key to raise your quota by the tier multiplier.
import time, requests
def retry_with_backoff(fn, max_tries=5):
for attempt in range(max_tries):
r = fn()
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
return r
raise RuntimeError("max retries exhausted")
r = retry_with_backoff(lambda: requests.get(
"https://predictasiax.com/api/v1/markets",
headers={"X-Api-Key": "sk_live_ABC123"}
))
print(r.json())
Server-to-client messages have no rate cap. Client-to-server messages (SUBSCRIBE / UNSUBSCRIBE / AUTH / LOCALE) are limited to 10 msgs/sec per connection to prevent DoS. Excess messages are silently dropped after a warning log. Well-behaved clients batch subscriptions into a single SUBSCRIBE call:
# Good:
{"method":"SUBSCRIBE","params":["fast_tick","signals","orderbook","account"]}
# Bad (4 messages instead of 1):
{"method":"SUBSCRIBE","params":["fast_tick"]}
{"method":"SUBSCRIBE","params":["signals"]}
{"method":"SUBSCRIBE","params":["orderbook"]}
{"method":"SUBSCRIBE","params":["account"]}
400 MISSING_FIELD won't fix itself with retry — you'll just waste quota. See Retry policy.