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.

Unreleased — Stable OpenAPI operation names

Published operationId values stay unchanged. Explicit route metadata now pins these names, so generated clients keep their existing method names. Route URLs and response payloads do not change. See issue #8024.

2026-10-02 — Python SDK 0.6.0 (current)

pip install -U polysimulator now installs 0.6.0, published to PyPI on 2026-10-02. It also carries the 0.5.0 changes, which were never uploaded on their own. Read these before upgrading from 0.4.x:
  • Clients construct without a key. PolySimClient(), AsyncPolySimClient(), ClobClient() and the polysim_polymarket clients no longer raise ValueError when no key is set. They read public market data; a call that needs a key raises ApiKeyRequiredError (an ApiError with status_code == 401).
  • create_key() and bootstrap() no longer default to tier="free". Leave tier unset and the key is minted at the tier your plan entitles you to.
  • ClobClient.get_fee_rate_bps() reads GET /v1/fee-rate and can raise. It used to return 0 without a request, which priced every fill as free.
  • A long Retry-After raises instead of sleeping. On a 429 the client waits at most max_retry_after seconds (default 60). A longer wait raises RateLimitError at once, with the server’s value in retry_after.
  • New: settlements() reads GET /v1/account/settlements.
  • polysim_polymarket now mirrors polymarket-client 0.9.0.
See the SDK overview.

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

The polysimulator SDK is now at 0.4.2 — pip 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 (polymarket → polysim_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 surfaces — polysim_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.