Skip to main content

CLOB Compatibility

PolySimulator mirrors Polymarket’s real execution model:
  • All orders are limit orders — market orders are just limit orders with FOK time-in-force at marketable prices
  • BUY fills at best ask, SELL fills at best bid — not the midpoint. (Don’t confuse fills with quotes: GET /v1/price?side=BUY returns the best bid — your side of the book — while executions cross the spread. Same convention as live Polymarket.)
  • The price field is the worst-price limit — slippage protection built into the order, not a separate parameter
  • FOK (Fill-or-Kill) is the immediate-execution order type on this endpoint. Polymarket’s real CLOB also supports FAK (Fill-and-Kill); on PolySimulator, FAK lives on the PM-raw POST /v1/order path, and POST /v1/clob/order accepts GTC/FOK/GTD only (see below)
The POST /v1/clob/order endpoint mirrors Polymarket’s real CLOB API schema, so a bot written for Polymarket’s order fields can be tested here. PolySimulator never forwards these orders to Polymarket.

Porting to Polymarket

PolySimulator never forwards orders to Polymarket. To trade on Polymarket, your own code sends orders to Polymarket’s API with your own credentials and signed orders. The order fields carry over; the client setup and authentication change.
Authentication difference: PolySimulator uses the X-API-Key header. Polymarket’s CLOB requires L2 HMAC credentials (API key + secret + passphrase) derived from your wallet’s private key via py_clob_client. See the porting guide for full credential setup.

Request Schema

Send numeric fields (price, size) as strings. Polymarket’s real CLOB API expects string-encoded decimals, e.g. "0.65" not 0.65. PolySimulator also accepts a JSON number here and converts it, so a float does not fail on this host but can still fail on Polymarket. Strings are the portable form.
Polymarket’s camelCase names work too. tokenId, orderType, postOnly and feeRateBps are read as token_id, order_type, post_only and fee_rate_bps, so {"orderType": "FOK"} is a fill-or-kill order and {"postOnly": true} is a post-only order. Sending both spellings of one field with different values is refused with 422 VALIDATION_FAILED. Any other key this endpoint does not know is ignored.
order_type=IOC returns 400 {"error": "VALIDATION_FAILED", "message": "order_type=IOC not yet supported; use GTC (rests on book) or FOK (immediate-or-fail)."} with the header X-Polysim-Code: UNSUPPORTED_ORDER_TYPE on POST /v1/clob/order. Branch on the header: the body’s error is the generic VALIDATION_FAILED. The two trading paths have genuinely different time-in-force support: this CLOB path accepts GTC/FOK/GTD, while the PM-raw POST /v1/order path accepts PM’s full GTC/FOK/FAK/GTD set. Polymarket’s own CLOB enum is GTC/FOK/GTD/FAK — there is no IOC on Polymarket.

Response Schema

This is the PolySimulator CLOB-compat response. It is close to Polymarket’s real insert-order response but not byte-identical — the differences are spelled out below so a migrating bot doesn’t string-match on the wrong field.
Differences from Polymarket’s real POST /order response — port defensively:
  • transactID (single string) vs PM’s transactionsHashes / tradeIDs (arrays). PolySimulator returns transactID set to the order ID; Polymarket has no transactID field — it returns transactionsHashes: [] and tradeIDs: []. The PM-raw POST /v1/order path returns the PM-shape arrays.
  • takingAmount / makingAmount are human decimals here (e.g. "6.50", "10.0"); Polymarket returns 6-decimal fixed-point integer strings (e.g. "500000" for 0.50). Don’t divide ours by 1e6.
  • status enum here is matched / live / unmatched. Polymarket’s insert-order statuses are live / matched / delayed — PolySimulator never emits delayed, and uses unmatched (not a PM insert status) for cancelled/rejected orders.

Field Mapping

Fields like signature, salt, maker, and signer that are required for Polymarket’s blockchain settlement are accepted but ignored in virtual mode. This means you can develop your bot with (or without) these fields — either way works.
String vs Float numerics: Polymarket requires price and size as strings (e.g., "0.65" not 0.65). PolySimulator also accepts numbers, so a float that works here can fail on Polymarket. Always pass strings.

Public CLOB Read Endpoints

These endpoints mirror Polymarket’s public CLOB data API and require no authentication. They accept token_id (the CLOB outcome token) as query parameter.
/v1/prices-history is PM wire-compatible. It accepts PM’s required ?market= (token id; the ?token_id= alias also works), returns PM’s exact {"history": [{"t", "p"}]} envelope by default (p is a JSON number here, mirroring PM), supports startTs/endTs/fidelity, and 400s with PM’s verbatim error message when market is missing. For a bucketed OHLCV array of {t, o, h, l, c} points instead, pass ?format=ohlcv.
These endpoints serve the same cached prices as the authenticated API, refreshed roughly every 30 seconds.

Cancel Endpoints

Bulk cancel endpoints match Polymarket’s cancel response shape: {canceled: [...], not_canceled: {...}}. The cancel-market-orders endpoint accepts either market (condition_id) or asset_id (token_id) as query parameters.
The status word inside a not_canceled reason is the internal uppercase order-status enum (FILLED, CANCELLED, EXPIRED), which differs from the lowercase insert-order status the POST /v1/clob/order response uses (matched / live / unmatched). If you string-match on the status you saw at insert time, don’t expect the casing to line up here — match case-insensitively, or map FILLED → matched, CANCELLED → unmatched.

When to Use CLOB-Compat vs Native API


Next Steps