Skip to main content

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.
Because the windows are fixed rather than sliding, there is a boundary burst: a client aligned to the clock second can send its full per-second allowance just before the tick and again just after, briefly reaching about twice the nominal RPS. We do not treat that as abuse, but it is a property of the window rather than a documented allowance — do not design around it.The limiter also fails open: if Redis is unreachable the check returns full headroom instead of rejecting, so during a cache outage the stated limits are not enforced. That is a deliberate availability trade, stated here so the guarantee you are buying is the guarantee you actually get.

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.
The authoritative source for tier limits is GET /v1/keys/tiers. If a doc page ever disagrees with that endpoint, the endpoint wins.
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, a free 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.
When the day’s orders are spent, the order endpoints answer 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.
Distinguish this from RATE_LIMIT_EXCEEDED: that clears within a second, this clears at midnight UTC. Sleeping Retry-After on both is correct, but a bot that hits FREE_TIER_DAILY_LIMIT should stop placing orders for the day rather than spin — or move to a paid tier, which has no daily cap.
Like the rate limiter, the daily cap fails open: if Redis is unreachable the counter is neither enforced nor incremented, rather than blocking trading. The stated cap is the cap you get whenever the cache is healthy.

Rate Limit Response

When you exceed your limit, the API returns HTTP 429 with the standard two-field error envelope and a stable X-Polysim-Code response header:
Branch on 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 carries x-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 off Retry-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 poll GET /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:
  1. Minimum 1–2 Second Interval: Never poll an individual resting order more frequently than once every 1–2 seconds.
  2. Add Jitter: Apply randomized delays (e.g. 1.0s + random(0.1s, 0.5s)) to prevent synchronized polling bursts across multiple orders.
  3. Honor Retry-After: If a 429 occurs, parse the Retry-After header and back off for the specified duration before retrying.
  4. Batch Fetching: Use GET /v1/data/orders or GET /v1/orders?status=PENDING to inspect all open orders in a single request rather than executing individual GET /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 (with auth.apiKey) streams live trade frames with status: "MATCHED".
WebSocket Limitations (Fill-Only & Process-Local):
  • No Cancellation Events: Execution WebSockets emit order fill events only; they do not emit order cancellation events. Cancellations must be monitored or confirmed via REST endpoints.
  • Process-Local Scope: In multi-process and daemon deployments, matching loops running in background daemon processes do not cross process boundaries to API-worker WebSocket registries. Bots must retain periodic REST reconciliation as the authoritative source of truth.

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