Polymarket Bot Tutorial · Chapter 8 of 32
Polymarket CLOB API for bots: REST endpoints for order book snapshots, WebSocket subscriptions for real-time updates, parsing bids/asks, computing mid-price and depth, code samples.
What this chapter covers
The CLOB API is where orders are signed, sent, matched, and where the order book lives. Polymarket has two SDK generations - the deprecated v1 and the current v2. This chapter covers v2 only; v1 should not appear in any bot you ship in 2026. We walk the REST snapshot path, the WebSocket update channel, the parsing details that trip up new builders, and the reconnect logic without which a long-running bot drifts out of sync within hours.
- CLOB v1 vs v2 (use v2]
- Order book REST snapshot
- WebSocket subscriptions: market and user channels
- Parsing bids/asks/depth
- Computing mid-price and best-bid/ask
- Maker fees, taker fees, rebates
- Code: connect WS and process price-change events
- Reconnect and gap-handling
CLOB v1 vs v2 (use v2]
Polymarket maintains two SDK generations. v1 (@polymarket/clob-client on npm, py-clob-client <0.30) is deprecated and missing several order types added in 2024. v2 (@polymarket/clob-client-v2 v1.0.2 in Node, py-clob-client 0.34.6+ in Python) is the current standard.
Three concrete differences. v2 supports the negRisk flag for multi-outcome markets - required since the NegRisk exchange launched in late 2024. v2 ships TypeScript types for the WebSocket message shapes; v1 returns any. v2 handles the August 2025 Gnosis Safe signature flow natively; v1 requires custom signing glue.
The rest of this chapter is written assuming v2 throughout. If you see v1 code in an older tutorial, treat it as broken until proven otherwise - order placement against NegRisk markets in particular will silently route to the wrong exchange contract under v1.
Order book REST snapshot
The REST snapshot endpoint returns the full book for a single token at a point in time.
GET https://clob.polymarket.com/book?token_id=<ERC1155_TOKEN_ID>
Response shape:
{
"market": "0x...",
"asset_id": "5413...",
"timestamp": "1715600000000",
"hash": "0x...",
"bids": [{"price":"0.45","size":"120"}, {"price":"0.44","size":"380"}, ...],
"asks": [{"price":"0.47","size":"85"}, {"price":"0.48","size":"210"}, ...]
}
Prices are strings with 2-3 decimal places; sizes are strings representing share counts (not dollars). Bids are sorted high-to-low, asks low-to-high. hash is a deduplication marker - repeated polls of an unchanged book return the same hash and your bot can skip processing.
The REST snapshot is the right call for one-off lookups (price check on entry decision). For continuous monitoring, use the WebSocket channel below.
WebSocket subscriptions: market and user channels
Two WebSocket channels matter.
Market channel: wss://ws-subscriptions-clob.polymarket.com/ws/market. Subscribe to one or many tokens; receive order-book updates as they happen.
{"type":"Market","markets":["0xCondId1","0xCondId2"]}
Messages arrive on every change. Types include book (full snapshot), price_change (delta), tick_size_change (rare), and last_trade_price (most recent fill).
User channel: wss://ws-subscriptions-clob.polymarket.com/ws/user. Authenticated; receive your own order events - fills, partial fills, cancellations.
{"type":"User","auth":{"apiKey":"...","secret":"...","passphrase":"..."}}
The user channel is the cleanest way to detect a fill. Polling the orders REST endpoint costs more and may miss state changes between polls; the WebSocket pushes the event the moment the matcher acknowledges it.
Parsing bids/asks/depth
The order book is a list of price levels with aggregated size. Two parsing conventions to get right.
Order direction: bids are buy orders (someone wants to BUY at this price). When YOUR bot sells, you hit a bid. When your bot buys, you lift an ask. The Polymarket UI shows the same direction; some other exchanges invert.
Sorting: bids arrive sorted descending (best bid first). asks arrive sorted ascending (best ask first). Best bid is bids[0]; best ask is asks[0]. Beware: the public WebSocket sometimes sends partial book updates that are not pre-sorted - always re-sort defensively after any merge.
Depth at a level is the dollar value transactable: price * size. Top-5-level depth is a common liquidity metric: sum(b.price * b.size for b in bids[:5]). If top-5 depth is under $100, the book is illiquid and most strategy assumptions break.
Computing mid-price and best-bid/ask
Three derived price points your bot needs.
- Best bid / best ask:
bids[0].priceandasks[0].price. The prices you can actually trade at, one share. - Mid-price:
(best_bid + best_ask) / 2. The mathematical center of the spread. Useful for valuation; you never trade at the mid. - VWAP price for size N: walk the book until cumulative size reaches N, return the size-weighted average price. The actual cost to BUY N shares right now, accounting for sweep into deeper levels.
Edge case: an empty bid or ask side (no one selling, or no one buying) means the book is one-sided. In Polymarket's market structure this happens to resolved or near-resolved markets where one side is at 0.999 and no one offers liquidity at the loser side. Treat best-bid = 0 or best-ask = 1 as "do not trade" signals.
Maker fees, taker fees, rebates
Polymarket runs a maker-taker fee model. Numbers as of May 2026:
- Taker fee: 0 (zero) - orders that lift existing book liquidity pay no fee. Note that gas / network costs apply for proxy operations.
- Maker rebate: small positive, programmatic, paid out per filled rested order in eligible reward-program markets. Not all markets have rewards.
- NegRisk markets: same fee structure but on a separate exchange contract; rewards accrue separately.
The zero taker fee makes Polymarket meaningfully different from traditional CFD venues - most of the trading "cost" is the bid-ask spread itself, not an explicit fee. For a strategy crossing the spread on every trade, the spread tax is the real cost; assume 1-3 cents round-trip on typical books, more on illiquid ones.
Maker rebates are only worth chasing when liquidity-rewards-eligible markets line up with strategy ideas. Chapter 19 covers liquidity-rewards farming as a dedicated approach.
Code: connect WS and process price-change events
Minimal Node example: connect, subscribe, log every price-change event for one token.
import WebSocket from "ws";
const ws = new WebSocket("wss://ws-subscriptions-clob.polymarket.com/ws/market");
ws.on("open", () => {
ws.send(JSON.stringify({ type: "Market", markets: ["<CONDITION_ID>"] }));
});
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
if (msg.event_type === "price_change") {
console.log("price_change", msg.asset_id, msg.changes);
} else if (msg.event_type === "book") {
console.log("book snapshot", msg.bids?.[0], msg.asks?.[0]);
}
});
ws.on("close", () => console.log("closed"));
ws.on("error", (e) => console.error("err", e.message));
Subscribe to up to ~30 tokens per WebSocket connection comfortably. Beyond that, split across multiple connections - the server occasionally drops large subscriptions on the floor without erroring, which produces silent stale book reads.
Reconnect and gap-handling
A long-running WebSocket connection will drop. Cloudflare cycles connections every few hours; networks blink; Polymarket sometimes deploys. Plan for it.
Reconnect strategy: on close or error, wait min(2^attempt, 30) seconds with jitter, then re-subscribe. Reset the attempt counter on first successful message after reconnect.
Gap handling matters more than reconnect speed. While the WebSocket was disconnected, the book moved. On every reconnect, re-fetch the REST snapshot of every subscribed token and reconcile: any open positions whose book moved meaningfully need a state re-check, exits may need to fire, alarms may be stale. The "I missed 30 seconds of book updates" case is the silent killer of long-running bots - they keep running on stale state and place orders at prices that no longer exist.
Defensive pattern: snapshot every subscribed book once a minute regardless of WebSocket state, and treat the WS as a fast-path optimization on top of the snapshot poll.


