Rate Limits
Rate limits are enforced per API key using fixed-window counters with both per-second (RPS) and per-minute (RPM) buckets. Each bucket is keyed on the current clock second and clock minute, and resets on the tick.Tiers
The
free tier allows up to 2 requests in any one clock second, and is
also capped at 120 requests per clock minute, so the per-minute bucket is
the one you’ll hit first under sustained load. Use POST /v1/prices/batch and the WebSocket feeds
(which don’t count against the REST limit) to stay well inside it.Trade Write Serialization vs. Rate Limits: Rate limits apply per API key to general REST requests. In addition, order-writing endpoints (
POST /v1/orders, POST /v1/orders/batch, DELETE /v1/orders/{id}, etc.) are serialized per user via an internal lock to prevent balance and position races. A second concurrent write for the same user waits cooperatively up to 2 seconds; if the first write is still in flight when the wait window expires, it returns 409 TRADE_IN_PROGRESS with Retry-After: 1. Using multiple API keys for the same account does not bypass this lock. Callers should honor Retry-After and retry with the same client_order_id. See Placing Orders and Error Handling.Free-tier daily order cap
On top of the per-key rate limits above, afree key can place up to
100 accepted orders per UTC calendar day. This is a placement cap, not a
request cap — reads, cancels and rejected orders do not count — and it is the
one limit on this page that is counted per user, not per key.
The cap is per user deliberately. Keys are free, so a per-key cap would be
defeated by minting hundreds of keys and rotating through them. The rate
limit bounds how fast one key can act; the daily cap bounds how much free
trading one person can do.
429 before
validating the order, so a spent quota surfaces as itself rather than as a
400 about a field you would fix and resend for nothing:
Retry-After is the whole number of seconds until the next UTC midnight
(never below 1), which is the machine-readable form of the reset. Send
X-Polysim-Verbose: true and the body also carries
"details": {"limit": 100, "used": 100, "resets_at": "2026-09-19T00:00:00Z"}.
Inside POST /v1/orders/batch a spent quota surfaces per entry as
"status": "REJECTED" with "error": "FREE_TIER_DAILY_LIMIT", and the batch
call itself still returns 200.
Rate Limit Response
When you exceed your limit, the API returns HTTP 429 with the standard two-field error envelope and a stableX-Polysim-Code
response header:
error or the identical X-Polysim-Code value, not on
message, and read the Retry-After header for the exact wait time
in seconds.
Rate Limit Headers (on authenticated responses)
Every authenticated response carriesx-ratelimit-* headers so bots
can pre-throttle instead of waiting for an actual 429 (unauthenticated
public routes and legacy keyed public 429s return Retry-After, X-Polysim-Code,
and X-Request-Id; full quota metadata headers are returned on authenticated
endpoints and flagged token resolver routes):
Every header above is also emitted under the PolySim-namespaced
x-polysim-ratelimit-* prefix with identical values (e.g.
x-polysim-ratelimit-remaining). Read whichever your SDK or proxy
keys off — the unprefixed x-ratelimit-* form is canonical.Handling Rate Limits
When the limiter does fire, exponential backoff keyed offRetry-After:
Safe Order Polling Contract
A common pattern among trading bots is checking whether a resting limit order has been filled or canceled. Do not pollGET /v1/orders/{id} or GET /v1/orders in an unthrottled loop. Rapid polling exhausts per-second (RPS) and per-minute (RPM) buckets, generating 429 RATE_LIMIT_EXCEEDED responses.
Authoritative REST Polling Rules
REST endpoints are the authoritative source for order state, fills, and cancellations. When implementing order tracking:- Minimum 1–2 Second Interval: Never poll an individual resting order more frequently than once every 1–2 seconds.
- Add Jitter: Apply randomized delays (e.g.
1.0s + random(0.1s, 0.5s)) to prevent synchronized polling bursts across multiple orders. - Honor
Retry-After: If a429occurs, parse theRetry-Afterheader and back off for the specified duration before retrying. - Batch Fetching: Use
GET /v1/data/ordersorGET /v1/orders?status=PENDINGto inspect all open orders in a single request rather than executing individualGET /v1/orders/{id}calls per order.
WebSocket Fill Streams (Opportunistic & Fill-Only)
For lower fill latency, clients may listen to WebSocket execution channels alongside authoritative REST reconciliation:- PolySim Native:
WS /v1/ws/executions?token=<jwt>receives push notifications when limit orders fill in-process. - Polymarket Parity:
WS /v1/ws/user(withauth.apiKey) streams livetradeframes withstatus: "MATCHED".
Best Practices
Use Batch Endpoints
POST /v1/orders/batch and POST /v1/prices/batch combine multiple
operations into one request — and a batch call counts as one tick
against your RPS/RPM. It’s bounded by your tier’s Max Batch Size
(see the Tiers table above): free=1 means no batching benefit on
free, so this pays off most on pro (5) / pro_plus (10) /
enterprise (25).Use WebSocket Feeds
Subscribe to
WS /v1/ws/prices instead of polling GET /v1/markets.
WebSocket connections don’t count against your REST rate limit.Cache Market Metadata
Market metadata (slug, question, outcomes) changes infrequently.
Cache it locally and only refresh periodically.
Idempotency Keys
On order placement (
POST /v1/orders, POST /v1/order,
POST /v1/clob/order), send an Idempotency-Key header — a
Stripe-style alias for the body’s client_order_id — so a retried
request can’t double-fill. Reusing a key with a different payload
returns 409 IDEMPOTENCY_KEY_REUSE; reuse it only for the exact same
order you’re retrying.Next Steps
- String Numerics — Why all numbers are strings
- Batch Orders — Reduce request count with batching