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
PublishedoperationId 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 thepolysim_polymarketclients no longer raiseValueErrorwhen no key is set. They read public market data; a call that needs a key raisesApiKeyRequiredError(anApiErrorwithstatus_code == 401). create_key()andbootstrap()no longer default totier="free". Leavetierunset and the key is minted at the tier your plan entitles you to.ClobClient.get_fee_rate_bps()readsGET /v1/fee-rateand can raise. It used to return0without a request, which priced every fill as free.- A long
Retry-Afterraises instead of sleeping. On a429the client waits at mostmax_retry_afterseconds (default 60). A longer wait raisesRateLimitErrorat once, with the server’s value inretry_after. - New:
settlements()readsGET /v1/account/settlements. polysim_polymarketnow mirrorspolymarket-client0.9.0.
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/keysandPOST /v1/keys/bootstrapnow 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-balancereturns402 UPGRADE_REQUIRED) or topped up, and by 100 order placements per UTC day per account (429 FREE_TIER_DAILY_LIMIT, withRetry-After,limit,usedandresets_at). - Free keys issued before 2026-09-05 are unchanged: read-only. The
tradepermission 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;
adminis never self-issued at any tier. 403 TIER_REQUIRES_UPGRADEon key creation is now returned only for a paidtieryou are not subscribed to, foradmin, or fortradeon 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 JSONerrorfield, with human prose inmessage;X-Polysim-Codecarries 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 bodyerroror the identicalX-Polysim-Code, not onmessage.- The public error catalog now documents
TRADE_IN_PROGRESS,SERVER_BUSY,DEADLINE_OVERSHOT_BUT_PERSISTED, andPERSISTENCE_UNKNOWN, including which responses are safe to retry and which require an order lookup first. - Invalid account
side, positionstatus, 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, andX-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, andINSUFFICIENT_LIQUIDITY(400) for book-walk size exhaustion. See Error Handling. - Trade rate limiting: Documented
429 RATE_LIMITEDon 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_SOONare 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.txtare generated fromdocs-site/MDX byscripts/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_idsmap and return the resolved map. - Equity is keyset-paginated (
data,next_cursor) like trades. - OpenAPI advertises
503plus the runtimeX-Polysim-Codevalues (INVALID_REQUEST,BACKTEST_*,SIMULATION_HOURS_EXCEEDED,METERING_UNAVAILABLE,CONDITION_HOUR_NOT_COVERED,ARCHIVE_*, …). - Adjacent v4 capture sidecars are fail-closed; producer reason tiers
(
frames_spilledvalid,frames_droppeddegraded) are honored.
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_24hnow 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_totalis new and carries the cumulative all-time figure. If you were readingvolume_24hand 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 numbervolume_24hused to return. The exception is the small set of markets for which only a 24-hour figure is reported upstream: the oldvolume_24hpassed that rolling value through, whereasvolume_totalisnullthere, 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
nullrather than a number from the wrong window. As before,nullmeans “no data” and"0"means a real zero — a market that traded nothing in 24 hours reportsvolume_24h: "0", notnull. is_hotis unchanged. Its $5,000 threshold reads the cumulative figure and always did, so no market changes hotness because of this release, andhot_only=truereturns the same set it did yesterday.live_price.volumeis 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 singleps_live_…key). The one deliberate divergence: the named errors subclassPolyExceptionhere, not py-sdk’sPolymarketError. - One install ships all three surfaces —
polysim_sdk(native),polysim_clob_client(py-clob-clientv1 drop-in), andpolysim_polymarket(this py-sdk mirror).
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 fullamountat the displayed price (Polymarket’s “spend up toamount” semantics), instead of the conservativeamount / worst-price-limitthat under-bought — a$50BUY had been buying only ~$12of shares. Thepricefield still caps execution: a fill that would cross your worst-price limit kills rather than overspending. Legacy:amountwas sized off the worst-price cap, under-buying ~4×. - Accurate
slippage_bpson market orders. A market order’sslippage_bpsis 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 thehigh_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) andpost_onlyorders.time_in_force=GTD(a resting limit that auto-expires at itsexpirationtimestamp) andpost_only=true(maker-only — rejected if it would cross at placement) are now accepted, matchingpy-clob-client. Part of the PM-faithful order-semantics rollout (same flag as the entries below). Legacy:GTDreturned422;post_onlywas 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 liveclob.polymarket.com
probes and/or Polymarket’s API reference before changing.
Breaking changes
GET /v1/data/ordersstatus enum now uses Polymarket’s exact members.ORDER_STATUS_LIVE(was the inventedORDER_STATUS_PENDING),ORDER_STATUS_MATCHED(wasORDER_STATUS_FILLED),ORDER_STATUS_CANCELED— single L, per PM — (wasORDER_STATUS_CANCELLED),ORDER_STATUS_INVALID(wasORDER_STATUS_REJECTED). Bots string-matching the old values must update; the?status=filter accepts both spellings.GET /v1/data/ordersdefaults 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-historyreturns PM’s exact shape by default:{"history": [{"t": <unix int>, "p": <float>}]}—pis 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), supportsstartTs/endTs/fidelityand PM’s full interval enum (1h/6h/1d/1w/1m/max/all), and 400s (not 422s) with PM’s verbatim message whenmarketis missing.GET /v1/ordersnext_cursoris 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=truecursors are now PM-format and actually round-trip.next_cursoris 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=Nremains as an explicit best-N trim, cap raised from 50 to 500. before/afterfilters on/v1/data/orders+/v1/data/tradestake unix-seconds timestamps (PM convention). Unparseable values now return 400INVALID_TIME_FILTERinstead of being silently dropped (which returned the full unfiltered set). ISO datetimes remain accepted as a polysim extra.
Fixes
- Pagination request param
next_cursorhonored on/v1/data/orders,/v1/data/trades,/v1/ordersand/v1/markets?envelope=true— py-clob-client sends the cursor back under this name; it was silently ignored, soget_orders()/get_trades()refetched page 1 forever. The SDK’sMA==initial seed andLTE=terminal sentinel now behave exactly as on real PM.cursorremains an accepted alias everywhere. /v1/data/ordersasset_idfilter 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-riskreports the market’s real flag (was hardcodedfalse, contradicting both real PM and polysim’s own/v1/bookfor the same token). Source is the upstream book payload with cached-book and catalog fallbacks; unknown tokens get PM’s verbatim404 {"error": "market not found"}. Sim execution semantics are unchanged for neg-risk markets — the flag is for routing/branching parity.GET /v1/balance-allowanceemits PM’s keys:balance/allowance(base-unit strings, 1.00 USDC ="1000000") soresp["balance"]no longer KeyErrors in ported SDK code. The legacycollateral/conditionalkeys remain as extras.asset_type/token_id/signature_typeare now declared query params. The values stay “effectively unlimited” sentinels — readGET /v1/account/balancefor real sizing.
New
WS /v1/ws/usernow pushes PM-shapetradeframes 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 atMATCHED(noMINED/CONFIRMED— no chain),maker_ordersis empty, andowner/trade_ownerare empty strings. PM’sorderplacement/update/cancel events are still not emitted — pollGET /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 tomatched) withquantityset to the filled slice and apartial_fill:filled=…,requested=…entry inwarnings. 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=GTDonPOST /v1/orders,order_type=GTDon/v1/clob/order, andorderType=GTDon the PM-shapePOST /v1/orderbecome true Good-Til-Date resting limits: the engine skips and auto-cancels them once their unix-secondsexpirationpasses (cancelled rows carrycancelled_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_onlyfield (PM-shape: top-levelpostOnly): guaranteed-maker orders rejected with400 INVALID_POST_ONLY_ORDER(X-Polysim-Code: POST_ONLY_WOULD_CROSS) when marketable at placement;400 INVALID_POST_ONLY_ORDER_TYPEwhen combined with FOK/FAK/IOC/market. GTC/GTD limits only. Legacy: field accepted but ignored. - Minimum order size. The per-market
min_order_sizethatGET /v1/bookadvertises (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.
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 againstclob.polymarket.com, not just Polymarket’s docs — the two
disagree in places), plus truthful fee reporting and wallet scoping.
Breaking changes
GET /v1/priceside semantics flipped.side=BUYnow returns the best bid (your side of the book) andside=SELLthe 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/booklevel ordering re-sorted to Polymarket’s live wire.bidsare ASCENDING andasksDESCENDING by price — the best price is the LAST element on both sides (bids[-1]/asks[-1]), byte-compatible with realclob.polymarket.com/bookresponses. 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-analysispreviously returned rows from ALL your wallets (including website MAIN/SANDBOX wallets) whenwallet_idwas omitted. The default is now your API wallet, consistent with/balance,/portfolio, and/equity. Passwallet_id=allfor the old behavior,wallet_id=<id>for a specific wallet. Theall/apikeywords 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-ratereturns{"base_fee": 0|1000, "fee_rate_bps": <effective rate>}.base_feemirrors Polymarket’s legacy base-fee parameter (observed: flat1000on fee-charging markets,0on fee-free ones);fee_rate_bpsis 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/tradesrows now carry the realfee_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 return400 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.txtcorrected 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).