Sliding-window, per-key, per-endpoint-category. Independent bucket accounting — read quota exhaustion never blocks trading, and vice versa. Every response returns X-RateLimit-* headers so client-side throttling can be exact rather than heuristic.
Approved builder keys start at self_serve and auto-graduate to read_live at $100 30-day attributed volume; higher tiers multiply the per-category budgets. Sandbox keys (tier=self_serve) share the same host and quotas — the $10 per-order + $100 daily notional caps handle safety, not rate throttling.
| Endpoint category | Read-live 1× | Genesis 10× | Partner up to 20× / negotiable |
|---|---|---|---|
Read (GET /v1/markets, GET /v1/data/history, orderbook, candles) |
fair-use | 10× fair-use | uncapped for committed contracts |
Account (GET /v1/builders/me, attribution, revenue) |
fair-use | 10× fair-use | uncapped for committed contracts |
| Trade (order placement + cancel, single + batch) | fair-use | 10× fair-use | MM-tier burst, contract-defined |
| Create (Composer markets, RFQ, webhook config) | fair-use | 10× fair-use | contract-defined |
| WebSocket concurrent connections | fair-use | 10× fair-use | dedicated stream, contract-defined |
Fair-use quotas are published in X-RateLimit-Limit on every response and version-bumped with 90-day deprecation notice — hard-coding numbers into client code is discouraged; use the header instead.
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)
Builders routing volume through their apps, MMs, and institutional partners can upgrade to higher tiers:
| Tier | Multiplier | Requires |
|---|---|---|
| Self-serve (sandbox) | 1× w/ $10 order + $100/day notional caps | Anonymous mint via POST /v1/sandbox-keys |
| Read-live | 1× baseline | Auto-graduated at $100 30-day attributed volume from self_serve — no application needed |
| Genesis (first 20 cohort) | 10× baseline, custom bursts negotiable | Apply via POST /v1/apply with track=genesis — human review |
| Partner / Institutional | up to 20× or fully negotiable per contract | Signed partner / MM agreement with committed spread or volume |
| Load Test | ephemeral (up to 50×) | Submit a Request access ticket with test plan |
Sandbox and production traffic run on independent lanes with separate SLOs — sandbox load-testing never impacts read_live / trade_capped / trade_full / genesis / partner trade latency. WS event circuit_breaker fires if any lane enters degraded mode, letting well-behaved clients back off automatically.
Public unauthenticated reads (no key) are IP-throttled for anti-abuse. Mint an API key via POST /v1/sandbox-keys in 30 seconds to raise your quota to the self_serve baseline immediately, no email required.
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://api.predictasiax.com/v1/markets",
headers={"X-Api-Key": "sk_live_ABC123"}
))
print(r.json())
Server-to-client messages have no rate cap. Client-to-server control frames (SUBSCRIBE / UNSUBSCRIBE / AUTH / LOCALE) are gently throttled to prevent misbehaving clients from exhausting a shared connection — batch multiple 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.