Skip to main content
Beta notice. During the public open beta, breaking changes may ship with little or no advance notice — this page is the authoritative record. Pin your integration to the behaviors documented here and re-check after the dates listed. A formal versioning and deprecation policy will be published when the API leaves beta.

2026-09-05 — Free keys can trade (keys issued from this date)

  • Free API keys issued on or after 2026-09-05 (UTC) are trade-capable. POST /v1/keys and POST /v1/keys/bootstrap now mint ["read", "trade"] at every self-serve tier, free included. Free trading is bounded by a $100 API wallet that cannot be reset (POST /v1/account/reset-api-balance returns 402 UPGRADE_REQUIRED) or topped up, and by 100 order placements per UTC day per account (429 FREE_TIER_DAILY_LIMIT, with Retry-After, limit, used and resets_at).
  • Free keys issued before 2026-09-05 are unchanged: read-only. The trade permission is stripped on every request even where the stored key carries it, and rotating such a key preserves its read-only scope. Create a new key to get a trade-capable one.
  • A free account whose paid grant has expired stays read-only regardless of the key’s issue date; admin is never self-issued at any tier.
  • 403 TIER_REQUIRES_UPGRADE on key creation is now returned only for a paid tier you are not subscribed to, for admin, or for trade on an expired paid grant — no longer for plain free + trade. See Authentication → Open Beta and Wallets.

2026-08-29 — Structured errors and cache-safe market data

  • /v1/* errors now expose the stable machine code in the JSON error field, with human prose in message; X-Polysim-Code carries the same code. Clarification only (no runtime change): this two-field envelope was already the deployed behavior. Earlier pages incorrectly described the Polymarket single-field {"error": "<message>"} shape. Clients should branch on body error or the identical X-Polysim-Code, not on message.
  • The public error catalog now documents TRADE_IN_PROGRESS, SERVER_BUSY, DEADLINE_OVERSHOT_BUT_PERSISTED, and PERSISTENCE_UNKNOWN, including which responses are safe to retry and which require an order lookup first.
  • Invalid account side, position status, and malformed price-history token filters now fail with 4xx errors instead of returning misleading empty data.
  • Successful order-book and market-by-slug responses explicitly use Cache-Control: no-store.
  • Health and OpenAPI now report the same API version.

2026-08-27 — API contract alignment & error codes

  • 429 rate limit headers: Legacy keyed and unauthenticated public endpoints return Retry-After, X-Polysim-Code, and X-Request-Id. Full quota metadata (x-ratelimit-* headers) is emitted on authenticated private endpoints and flagged token resolver routes. See Rate Limits.
  • Order rejection codes: Documented STALE_QUOTE (400) for quote freshness enforcement, NO_EXECUTABLE_DEPTH (400) for order-book depth enforcement, and INSUFFICIENT_LIQUIDITY (400) for book-walk size exhaustion. See Error Handling.
  • Trade rate limiting: Documented 429 RATE_LIMITED on session-JWT trade endpoints (POST /trade, POST /limit-order).
  • WebSocket best_bid_ask: Non-first outcome frames now strictly omit rather than fabricate top-of-book prices when outcome-specific orderbook depth is unavailable. See PM-compat WebSocket.

2026-08-22 — Open beta: self-serve free read-only keys and paid trading

  • Key issuance is self-serve. Sign in, mint a key from the dashboard or POST /v1/keys/bootstrap. Free keys are read-only; active Pro / Pro+ keys are trade-capable. (Superseded for free keys issued on or after 2026-09-05 — see that entry above.) CLOSED_BETA / API_PRO_COMING_SOON are residual codes, not the default path. See Authentication → Open Beta.
  • Honest limits now include the full V2 fee table (including the 5% unknown-category fallback) and numeric exceptions. See Honest Limits and Fees.
  • Datalake reader recipes are executable against a user-provided local mount or POLYSIM_DATALAKE_* endpoint configuration. Credentials and raw archive objects are never published. See Datalake Schema.
  • Markdown mirrors + llms-full.txt are generated from docs-site/ MDX by scripts/generate_docs_mirrors.py. Do not hand-edit the generated files.

2026-08-22 — Simulation API v1 public contract (feature-dark)

/v1/simulation is now declared in the public OpenAPI document: fill, coverage, fill-models, book, backtests (create/list/get/trades/equity/cancel), and exports. The router stays 404 until an operator flips FEATURE_SIMULATION_API_ENABLED. This is not a commercial activation and does not claim a Telonex-validated SKU.
  • Fill and book responses echo resolved token_id / comp_token_id.
  • Backtests accept an optional token_ids map and return the resolved map.
  • Equity is keyset-paginated (data, next_cursor) like trades.
  • OpenAPI advertises 503 plus the runtime X-Polysim-Code values (INVALID_REQUEST, BACKTEST_*, SIMULATION_HOURS_EXCEEDED, METERING_UNAVAILABLE, CONDITION_HOUR_NOT_COVERED, ARCHIVE_*, …).
  • Adjacent v4 capture sidecars are fail-closed; producer reason tiers (frames_spilled valid, frames_dropped degraded) are honored.
See Simulation.

2026-08-12 — Breaking: volume_24h is now genuinely 24-hour; new volume_total

volume_24h on /v1/markets and /v1/markets/{condition_id} was returning cumulative all-time volume, not a 24-hour figure. The upstream feed publishes both numbers and our poller resolved the cumulative one first, so on essentially every market the field carried a lifetime total under a rolling-24h name. It was wrong by 19x on strait-of-hormuz-traffic-returns-to-normal-b… (12,149,389 served vs 643,698 actual) and by 91x on will-adanech-abiebie-be-the-next-prime-minis… (77,573,355 vs 850,899). Only brand-new markets, where the two figures coincide, were accurate.
  • volume_24h now means what it says — volume traded in the trailing 24 hours. Expect the values you read to drop sharply for any market older than a day. This is the correction, not a data loss.
  • volume_total is new and carries the cumulative all-time figure. If you were reading volume_24h and wanted lifetime volume, change the field name. On every market where a cumulative figure is reported — which is nearly all of them — this returns exactly the number volume_24h used to return. The exception is the small set of markets for which only a 24-hour figure is reported upstream: the old volume_24h passed that rolling value through, whereas volume_total is null there, because no cumulative number exists to give you. Both fields appear on the list and detail responses, and both are strings, as before.
  • Neither field substitutes for the other. Where a figure has not been reported, you get null rather than a number from the wrong window. As before, null means “no data” and "0" means a real zero — a market that traded nothing in 24 hours reports volume_24h: "0", not null.
  • is_hot is unchanged. Its $5,000 threshold reads the cumulative figure and always did, so no market changes hotness because of this release, and hot_only=true returns the same set it did yesterday.
  • live_price.volume is unchanged and is an all-time figure — it now says so in the schema.

2026-06-20 — Python SDK 0.4.2 (current)

The polysimulator SDK is now at 0.4.2pip install polysimulator. Hardening across the 0.4.x beta line: Python 3.10+ support, faithful market-order time-in-force (FOK / FAK preserved through the default post path), accurate batch-order result mapping, and Decimal-precise money handling — plus a retry_on_market_warmup helper and the condition_id:UP / :DOWN shorthand for the Up/Down drop-in. The three import surfaces are unchanged: polysim_sdk (native), polysim_clob_client (py-clob-client drop-in), and polysim_polymarket (py-sdk mirror). See the SDK overview.

2026-06-18 — Python SDK 0.4.0: Polymarket py-sdk mirror (polysim_polymarket)

The polysimulator package ships a third import surface in 0.4.0: polysim_polymarket, a paper-mode mirror of Polymarket’s newer unified py-sdk (polymarket-client, import polymarket), pinned to 0.1.0b8.
  • Sync + async PublicClient / SecureClient, returning typed pydantic models (OrderBook, Market, LastTradePrice, …) instead of raw dicts. CLOB market-data reads are full parity; the authenticated trading write surface, account/auth reads, on-chain methods (paper no-ops), and the core realtime streams (market / user / crypto_prices) are implemented with documented paper-mode seams.
  • Port = the same mechanical swap as the v1 drop-in — change the import prefix (polymarketpolysim_polymarket), the host, and the auth (drop the private key + EIP-712 signing for a single ps_live_… key). The one deliberate divergence: the named errors subclass PolyException here, not py-sdk’s PolymarketError.
  • One install ships all three surfacespolysim_sdk (native), polysim_clob_client (py-clob-client v1 drop-in), and polysim_polymarket (this py-sdk mirror).
See the py-sdk Mirror overview and the py-sdk Compatibility deep reference (sim→real seam contract, compat matrix, fidelity gaps, stream seams) for the full surface and every documented paper-mode seam.

2026-06-16 — Dollar sizing, market-order slippage, GTD + post-only (live)

Three execution / telemetry improvements surfaced by a near-HFT SDK developer review, now live in production.
  • Dollar-denominated market BUYs spend the full amount. A market BUY priced by amount (USD) now sizes to spend ~the full amount at the displayed price (Polymarket’s “spend up to amount” semantics), instead of the conservative amount / worst-price-limit that under-bought — a $50 BUY had been buying only ~$12 of shares. The price field still caps execution: a fill that would cross your worst-price limit kills rather than overspending. Legacy: amount was sized off the worst-price cap, under-buying ~4×.
  • Accurate slippage_bps on market orders. A market order’s slippage_bps is now measured against the displayed price you saw at submit, not the worst-price cap — a clean fill at the touch now reports a small, true value (~tens of bps), and the high_slippage_bps_* warning no longer fires on every clean market fill. impact_bps (the book-walk cost from top-of-book) is unchanged and remains the authoritative depth-impact field. Legacy: slippage was measured vs the cap, reporting price improvement as ~7500 bps of “slippage” plus a false high-slippage warning on essentially every clean fill.
  • GTD (Good-Til-Date) and post_only orders. time_in_force=GTD (a resting limit that auto-expires at its expiration timestamp) and post_only=true (maker-only — rejected if it would cross at placement) are now accepted, matching py-clob-client. Part of the PM-faithful order-semantics rollout (same flag as the entries below). Legacy: GTD returned 422; post_only was advisory.

2026-06-11 — Realistic book impact + maker price fidelity (rolling out)

Two further matching-engine fidelity upgrades, flag-dependent — they ship behind the same deployment flag as the “PM-faithful order semantics” entry below and activate together (announced here). Until activation, the legacy behaviors remain in force.
  • Book-impact depletion. Limit-order fills now consume the displayed liquidity they walk. Within one upstream order-book snapshot, size already consumed at a price level by limit executions (your fills or anyone else’s) is not offered again: the depth walk subtracts prior consumption before pricing, so repeated orders walk to worse levels within your limit, partial-fill, or (FOK) kill — instead of repeatedly buying the same displayed shares while the snapshot stayed cached. A fresh upstream snapshot supersedes the synthetic depletion (real book truth wins; the safety TTL is sized to outlive the snapshot cache window). Applies to both resting-limit matching and immediate FOK/FAK/IOC execution. Market-order fills don’t yet feed or read the depletion overlay — that path re-resolves fresher books per request; wiring it into the same overlay is a documented follow-up. Simulated fills still never move the displayed book or midpoint — depletion is execution-side only. Legacy: the same displayed level was re-buyable every cycle.
  • Resting maker orders fill at their limit price. Polymarket’s maker/taker rule — price improvement always benefits the taker. A resting GTC/GTD limit the market later crosses now executes at the maker’s own limit price (for BUYs the reserve equals the spend — zero refund delta; maker fee remains $0). Marketable orders (FOK/FAK/IOC and limits that cross at placement) are takers and keep their depth-walk price improvement, exactly as before. Legacy: resting orders filled at the later touch/VWAP price — systematically better than the maker’s own limit, which inflated PnL.

2026-06-11 — PM-compat P1 batch (pagination, status enums, prices-history, neg-risk, cursors, full book, WS fills)

The second wire-parity wave, driven by the 2026-06-10 API evaluation’s confirmed P1 findings. Every item below was verified against live clob.polymarket.com probes and/or Polymarket’s API reference before changing.

Breaking changes

  • GET /v1/data/orders status enum now uses Polymarket’s exact members. ORDER_STATUS_LIVE (was the invented ORDER_STATUS_PENDING), ORDER_STATUS_MATCHED (was ORDER_STATUS_FILLED), ORDER_STATUS_CANCELED — single L, per PM — (was ORDER_STATUS_CANCELLED), ORDER_STATUS_INVALID (was ORDER_STATUS_REJECTED). Bots string-matching the old values must update; the ?status= filter accepts both spellings.
  • GET /v1/data/orders defaults to OPEN orders only — matching PM (“Retrieves open orders for the authenticated user”). History is an explicit opt-in via the new polysim-extension ?status= param (ORDER_STATUS_MATCHED, ORDER_STATUS_CANCELED, ALL, friendly aliases). Previously every status came back, so open-order counts and cancel-all sweeps operated on dead rows.
  • GET /v1/prices-history returns PM’s exact shape by default: {"history": [{"t": <unix int>, "p": <float>}]}p is a JSON number here, mirroring PM’s wire. The old default (a bare array of {t, o, h, l, c} string points) remains available via ?format=ohlcv. The endpoint also accepts PM’s required ?market= param (token id; ?token_id= stays as an alias), supports startTs/endTs/fidelity and PM’s full interval enum (1h/6h/1d/1w/1m/max/all), and 400s (not 422s) with PM’s verbatim message when market is missing.
  • GET /v1/orders next_cursor is now urlsafe-base64. The raw ISO form contained +, which broke un-percent-encoded round-trips with a 400. Treat cursors as opaque; legacy raw-ISO (and the +-mangled-to-space form) are still accepted on the way in.
  • GET /v1/markets?envelope=true cursors are now PM-format and actually round-trip. next_cursor is a base64-encoded offset ("NTA=" = 50) with PM’s "LTE=" terminal sentinel (was a raw int string + "" terminal that NO query param accepted — PM-style paginators looped on page 1 forever). Pass it back via ?next_cursor= or ?cursor=; raw int offsets are still accepted on the way in.
  • Order-book endpoints return the FULL book by default (/v1/book, /v1/clob/book/{token_id}, /v1/markets/{condition_id}/book, POST /v1/books) — PM’s wire contract (PM has no depth param; we measured PM 96/113 levels where polysim silently returned 10/10). ?depth=N remains as an explicit best-N trim, cap raised from 50 to 500.
  • before/after filters on /v1/data/orders + /v1/data/trades take unix-seconds timestamps (PM convention). Unparseable values now return 400 INVALID_TIME_FILTER instead of being silently dropped (which returned the full unfiltered set). ISO datetimes remain accepted as a polysim extra.

Fixes

  • Pagination request param next_cursor honored on /v1/data/orders, /v1/data/trades, /v1/orders and /v1/markets?envelope=true — py-clob-client sends the cursor back under this name; it was silently ignored, so get_orders() / get_trades() refetched page 1 forever. The SDK’s MA== initial seed and LTE= terminal sentinel now behave exactly as on real PM. cursor remains an accepted alias everywhere.
  • /v1/data/orders asset_id filter implemented (was accepted and ignored): resolves the token to its market+outcome and filters both; unknown tokens short-circuit to an empty terminal envelope.
  • GET /v1/neg-risk reports the market’s real flag (was hardcoded false, contradicting both real PM and polysim’s own /v1/book for the same token). Source is the upstream book payload with cached-book and catalog fallbacks; unknown tokens get PM’s verbatim 404 {"error": "market not found"}. Sim execution semantics are unchanged for neg-risk markets — the flag is for routing/branching parity.
  • GET /v1/balance-allowance emits PM’s keys: balance / allowance (base-unit strings, 1.00 USDC = "1000000") so resp["balance"] no longer KeyErrors in ported SDK code. The legacy collateral / conditional keys remain as extras. asset_type / token_id / signature_type are now declared query params. The values stay “effectively unlimited” sentinels — read GET /v1/account/balance for real sizing.

New

  • WS /v1/ws/user now pushes PM-shape trade frames when your orders fill on a subscribed market (event_type: "trade", type: "TRADE", status: "MATCHED", unix-string timestamps — PM’s documented user-channel trade message). Divergences: paper trades terminate at MATCHED (no MINED/CONFIRMED — no chain), maker_orders is empty, and owner/trade_owner are empty strings. PM’s order placement/update/cancel events are still not emitted — poll GET /v1/data/orders, or use the polysim-native /v1/ws/executions.

2026-06-11 — PM-faithful order semantics (rolling out)

The matching-engine fidelity fast-follows announced on 2026-06-10 are implemented and rolling out behind a deployment flag. Until the rollout activates (announced here), the legacy behaviors below remain in force; once active, the engine matches real Polymarket on all five:
  • FAK/IOC partial fills. Synchronous IOC/FAK limit orders walk the displayed order-book depth within your limit, fill what’s available at the level-by-level VWAP, and cancel the remainder. A partial fill returns status: "FILLED" (PM-compat surfaces map it to matched) with quantity set to the filled slice and a partial_fill:filled=…,requested=… entry in warnings. Legacy: atomic — full quantity at the touch price or cancel.
  • FOK depth-aware atomicity. FOK computes the fillable quantity within your limit across book levels first: fills entirely (at the walked VWAP) iff depth covers your size, else kills cleanly. Legacy: full-size fill at the touch price whenever the top of book crossed, regardless of depth.
  • GTD auto-expiry. time_in_force=GTD on POST /v1/orders, order_type=GTD on /v1/clob/order, and orderType=GTD on the PM-shape POST /v1/order become true Good-Til-Date resting limits: the engine skips and auto-cancels them once their unix-seconds expiration passes (cancelled rows carry cancelled_reason: "gtd_expired"; reservations are refunded). Expired-at-placement → 400 INVALID_ORDER_EXPIRATION. Legacy: GTD coerced to GTC (and rejected on native /v1/orders).
  • post-only. New post_only field (PM-shape: top-level postOnly): guaranteed-maker orders rejected with 400 INVALID_POST_ONLY_ORDER (X-Polysim-Code: POST_ONLY_WOULD_CROSS) when marketable at placement; 400 INVALID_POST_ONLY_ORDER_TYPE when combined with FOK/FAK/IOC/market. GTC/GTD limits only. Legacy: field accepted but ignored.
  • Minimum order size. The per-market min_order_size that GET /v1/book advertises (5 shares on standard binary markets, 1 on ≤0.001-tick markets) is enforced at placement: 400 INVALID_ORDER_MIN_SIZE (X-Polysim-Code: ORDER_BELOW_MIN_SIZE). Note this applies to SELL too — like real Polymarket, a sub-minimum residual position can’t be exited via a below-minimum order. Legacy: any size > 0.0001 shares accepted.
New request fields (expiration, post_only) are accepted today on POST /v1/orders, POST /v1/clob/order, and the PM-shape POST /v1/order / batch; they are advisory no-ops until the rollout activates. The activation will be announced in this changelog.

2026-06-10 — Polymarket wire-parity wave

A coordinated set of fixes aligning the API with Polymarket’s live wire behavior (verified against clob.polymarket.com, not just Polymarket’s docs — the two disagree in places), plus truthful fee reporting and wallet scoping.

Breaking changes

  • GET /v1/price side semantics flipped. side=BUY now returns the best bid (your side of the book) and side=SELL the best ask — matching Polymarket’s live wire and its API reference. Previously the values were inverted. Quotes return your side of the book; executions still cross the spread (market BUY fills at the best ask) — same convention as Polymarket.
  • GET /v1/book level ordering re-sorted to Polymarket’s live wire. bids are ASCENDING and asks DESCENDING by price — the best price is the LAST element on both sides (bids[-1] / asks[-1]), byte-compatible with real clob.polymarket.com/book responses. Recommendation: read the inside market order-independently (max bid price / min ask price) so ordering can never bite you. Applies to /v1/book, /v1/clob/book/{token_id}, POST /v1/books, and /v1/markets/{condition_id}/book.
  • Account reads default to the API wallet. GET /v1/account/positions, /history, and /profile-analysis previously returned rows from ALL your wallets (including website MAIN/SANDBOX wallets) when wallet_id was omitted. The default is now your API wallet, consistent with /balance, /portfolio, and /equity. Pass wallet_id=all for the old behavior, wallet_id=<id> for a specific wallet. The all/api keywords work on all five account-read endpoints.

Fees — now reported truthfully

The engine has always charged Polymarket-V2 per-category taker fees; the reporting surfaces wrongly claimed zero. Now:
  • GET /v1/fee-rate returns {"base_fee": 0|1000, "fee_rate_bps": <effective rate>}. base_fee mirrors Polymarket’s legacy base-fee parameter (observed: flat 1000 on fee-charging markets, 0 on fee-free ones); fee_rate_bps is the effective per-category taker rate actually charged (sports 300, finance/politics/mentions/tech 400, economics/culture/weather/other 500, crypto 700, geopolitics 0).
  • The crypto taker rate was corrected from 7.2% to 7.0% (Polymarket’s published rate). Fills before 2026-06-10 may have been charged at 7.2%.
  • GET /v1/data/trades rows now carry the real fee_rate_bps (0 for maker fills).
  • Maker/taker classification now follows marketability at placement: a limit order that crosses the book when placed pays the taker fee even though it fills via the ~1s matching cycle; resting orders that fill later remain fee-free makers, and the resting remainder of a partial fill is treated as maker. See Trading Fees for the schedule and formula.

Fixes

  • Candle intervals documented correctly: 1h/6h/1d/1w/max (sub-hour intervals are not yet available; unknown values return 400 INVALID_INTERVAL).
  • FAK/IOC behavior documented honestly: currently atomic (full fill at the touch price or cancel) — Polymarket-style partial-fill-then-cancel is a planned fast-follow and will be announced here.
  • llms.txt corrected across both hosts (error codes, bootstrap permissions, book-ordering parity claims, fee schedule).

Coming soon

  • Matching-engine fidelity fast-follows: GTD auto-expiry, post-only, per-market minimum order size, FAK partial fills. Update 2026-06-11: implemented and rolling out — see the entry above.
The official Python SDK shipped — pip install polysimulator (see the SDK overview).

2026-06-09 and earlier

The beta API surface was assembled and hardened through internal audit waves (error-envelope consistency, public market-data reads without a key, PM-shape compatibility endpoints, rate-limit headers). The 2026-06-10 entries above are the first changes shipped after external-facing documentation went live.