Polymarket CLOB API: Order Book Integration Guide
Step-by-step walkthrough of authenticating with Polymarket's Central Limit Order Book API, streaming live order book snapshots, and submitting limit orders programmatically using Python and the py-clob-client SDK.
Polymarket runs a genuine Central Limit Order Book settled on Polygon, not an AMM. That distinction matters enormously for bot builders: you get real price discovery, maker/taker rebates, and deterministic fill logic — but you also inherit the full complexity of maintaining a live book, managing nonces, and handling L1/L2 latency asymmetry. This guide covers the practical mechanics of getting that integration running in Python with the official py-clob-client SDK.
Authentication: API Keys and L1 Signing
Polymarket uses a two-layer auth model. Your L1 wallet (an EOA on Polygon) signs orders and controls funds. A separate CLOB API key (generated from that wallet) authenticates REST and WebSocket calls without exposing your private key on every request.
Generate your API key once, store it, and treat it like a hot-wallet secret:
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import ApiCreds
client = ClobClient(
host="https://clob.polymarket.com",
key=YOUR_PRIVATE_KEY, # L1 EOA private key
chain_id=137, # Polygon mainnet
)
creds: ApiCreds = client.create_or_derive_api_creds()
# Save creds.api_key, creds.api_secret, creds.api_passphrase
On subsequent runs, skip derivation and instantiate with saved credentials. The SDK handles HMAC signing of REST headers for you. One footgun: the API key is scoped to the signing address — if you rotate wallets, you need new credentials.
Reading the Order Book via REST
The REST endpoint /book returns a snapshot for a given token_id (the condition token for one outcome side). Market IDs and token IDs are distinct; get the token ID from /markets first.
book = client.get_order_book("71321045679252212594626385532706912750332728571942532289631379312455583992563")
# book.bids and book.asks are lists of {price, size}
best_bid = max(book.bids, key=lambda x: float(x.price))
best_ask = min(book.asks, key=lambda x: float(x.price))
spread_cents = (float(best_ask.price) - float(best_bid.price)) * 100
Prices are in USDC, expressed as probabilities between 0 and 1 (so 0.47 = 47 cents per share = 47% implied probability). Sizes are in shares. The book endpoint is rate-limited to roughly 10 req/s per IP — fine for polling, inadequate for real-time tracking.
Streaming Live Updates via WebSocket
For live book maintenance you need the WebSocket feed at wss://ws-subscriptions-clob.polymarket.com/ws/market. Subscribe to the book channel for a given asset:
import asyncio, json, websockets
ASSET_ID = "71321045..."
async def stream_book():
uri = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
async with websockets.connect(uri) as ws:
await ws.send(json.dumps({
"type": "subscribe",
"channel": "book",
"assets_ids": [ASSET_ID],
}))
async for raw in ws:
msg = json.loads(raw)
if msg.get("event_type") == "book":
# Full snapshot
process_snapshot(msg["bids"], msg["asks"])
elif msg.get("event_type") == "price_change":
# Delta update — apply to local book
apply_delta(msg)
The feed sends a full snapshot on subscribe, then delta updates (price_change events) afterwards. You must maintain your own in-memory book and apply deltas correctly — a missing update corrupts your state. In production, subscribe with a reconnection wrapper and re-request the snapshot on every reconnect. The feed does go quiet during low-liquidity periods; implement a heartbeat timeout of ~30 seconds.
Submitting Limit Orders
Orders are signed EIP-712 structs and submitted via REST. The SDK handles struct construction and signing:
from py_clob_client.clob_types import OrderArgs, OrderType
order_args = OrderArgs(
token_id=ASSET_ID,
price=0.46, # limit price in USDC
size=100.0, # shares
side="BUY",
order_type=OrderType.GTC,
)
signed_order = client.create_order(order_args)
resp = client.post_order(signed_order, OrderType.GTC)
print(resp["orderID"])
Key mechanics to internalize:
- Minimum order size is 5 USDC notional. Orders below this are rejected, not silently dropped.
GTC(good-till-cancel) andGTD(good-till-date, max 30 days) are the only persistent order types.FOKandIOCare supported for immediate execution.- Maker orders earn a small rebate (~0.0% to 0.02% depending on market); takers pay ~0.05% to 0.10%. For tight-spread markets, rebate capture is a meaningful component of strategy P&L.
- The CLOB matches orders off-chain (on Polymarket's infrastructure) and settles fills on Polygon. That means fill latency is milliseconds, but USDC collateral is locked on-chain and settlement of positions is L1-final.
Nonce and Order Management
Cancel and replace is the core loop for any market-making bot strategy. Cancel by order ID:
client.cancel(order_id=resp["orderID"])
# Or cancel all open orders on an asset:
client.cancel_market_orders(asset_id=ASSET_ID)
There is no atomic cancel-replace; you cancel first, then post the new order. In fast-moving markets this creates a brief window of no resting liquidity — size your spread wide enough to absorb the gap. Track open orders locally; the API endpoint for open orders (/data/orders) is paginated and slow relative to the WebSocket fill feed.
Error Handling and Edge Cases Worth Knowing
A few sharp edges that cost time in production:
NONCE_ALREADY_USED: The SDK auto-increments nonces per session. If you spin up two instances against the same key, they collide. Use one writer process.INSUFFICIENT_BALANCE: USDC must be deposited to the CTF Exchange contract, not just held in the EOA. Thedepositcall is in the SDK but is a Polygon transaction — factor in ~5s for inclusion.- Market resolution: At resolution, the winning outcome settles to 1.00 and the losing side to 0.00. Cancel all open orders before the close timestamp or they fill at settlement prices you do not want.
- Price precision: The API accepts up to 2 decimal places (cent precision). Submitting 0.461 is rejected; round before sending.
If you want this wired up in a production system — with position sizing, risk controls, and real-time monitoring rather than tutorial snippets — talk to the team at TierZero. We build and operate these systems, so we have the operational scars and the working code.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article