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
CLOB stands for Central Limit Order Book. It is the engine that holds every resting buy and sell order for a market, matches them when prices cross, and exposes that book to your bot. In plain terms, this is where orders are signed, sent, and filled, and where you read the live prices. Polymarket ships two generations of the software kit (SDK) that talks to it: the deprecated v1 and the current v2. This chapter covers v2 only, because v1 should not appear in any bot you ship in 2026. We walk through the REST snapshot path (a one-time read of the book), the WebSocket update channel (a live stream of changes), the parsing details that trip up new builders, and the reconnect logic that a long-running bot cannot survive without.
- 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.6 in Node, py-clob-client-v2 in Python) is the current standard, and the version numbers above are the latest as of mid-2026.
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 spent most of its history charging no trading fees at all. That changed in 2026. Fees were introduced on the 15-minute crypto markets early in the year, expanded to Sports on March 30, 2026, and have since rolled out across most categories. Any tutorial that still says "Polymarket is fee-free" is out of date, and getting this wrong will quietly eat a high-frequency strategy alive. Here is how the model actually works as of mid-2026.
First, the split between the two sides of every trade. A maker is whoever posts a resting limit order that sits in the book and waits. A taker is whoever sends an order that immediately matches against that resting liquidity. Makers still pay zero fees and additionally earn a rebate; only takers pay a fee.
The taker fee is not a flat percentage. It follows a curve that depends on both the trade size and the price:
fee = shares × feeRate × price × (1 - price)
The price × (1 - price) term peaks at a price of 0.50 (a true coin-flip market) and shrinks toward 0 as the price approaches 0 or 1. In words: you pay the most fee on the most uncertain markets, and almost nothing on near-resolved ones. The feeRate is set per category:
- Crypto: feeRate 0.07 (the highest, peaking around 1.8% effective), maker rebate 20%.
- Sports: feeRate 0.03 (peaking around 0.75% effective), maker rebate 25%.
- Finance, Politics, Tech, Mentions: feeRate 0.04, maker rebate 25%.
- Economics, Culture, Weather, general: feeRate 0.05, maker rebate 25%.
- Geopolitics and major world events: 0, still fee-free.
A worked example. Say your bot takes 100 shares of a crypto market at a price of $0.50. The fee is 100 × 0.07 × 0.50 × (1 - 0.50) = 100 × 0.07 × 0.25 = $1.75, which on a $50 notional position is about 3.5% of the shares-times-rate basis but roughly 1.8% of the dollars at risk. Take the same 100 shares at $0.90 instead and the fee drops to 100 × 0.07 × 0.90 × 0.10 = $0.63, because the price is far from the uncertain middle. The lesson for a bot is direct: taking liquidity in volatile, near-even crypto and sports markets is where fees bite hardest, so those are exactly the markets where posting as a maker (and collecting the rebate instead of paying the fee) pays off most.
On top of the explicit fee, you still pay the bid-ask spread every time you cross it. The spread is the gap between the best buy price and the best sell price, and on a strategy that enters and exits by taking liquidity, that gap is a real cost on top of the fee. Assume 1-3 cents of round-trip spread on a typical book, and more on illiquid ones. NegRisk markets (the multi-outcome exchange) use the same fee model but settle on a separate contract, so their rewards accrue separately. Chapter 19 covers liquidity-rewards farming, where collecting maker rebates is the strategy rather than a side benefit.
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.










