Fetching the Polymarket orderbook via the CLOB API
How to GET /book/?token_id= to retrieve live bids and asks, parse the response, and extract best bid/ask in TypeScript.
The endpoint
The Polymarket CLOB exposes a public orderbook endpoint that does not require authentication:
GET https://clob.polymarket.com/book/?token_id={token_id}
The token_id is the ERC-1155 token ID for a specific outcome (YES or NO). You get it from the Gamma API when you look up a market.
Fetching the book in TypeScript
const CLOB_BASE = 'https://clob.polymarket.com'
interface OrderbookLevel {
price: string
size: string
}
interface Orderbook {
market: string
asset_id: string
bids: OrderbookLevel[]
asks: OrderbookLevel[]
hash: string
}
async function fetchOrderbook(tokenId: string): Promise<Orderbook> {
const url = `${CLOB_BASE}/book/?token_id=${tokenId}`
const res = await fetch(url)
if (!res.ok) throw new Error(`CLOB book fetch failed: ${res.status}`)
return res.json()
}Parsing best bid and ask
The bids array is sorted descending (highest price first). The asks array is sorted ascending (lowest price first). Best bid is bids[0], best ask is asks[0].
interface BestQuote {
bid: number
ask: number
spread: number
midpoint: number
}
function bestQuote(book: Orderbook): BestQuote | null {
if (!book.bids.length || !book.asks.length) return null
const bid = parseFloat(book.bids[0].price)
const ask = parseFloat(book.asks[0].price)
return {
bid,
ask,
spread: ask - bid,
midpoint: (bid + ask) / 2,
}
}Available liquidity at a price level
For larger orders you need to know how much size is available within a price range. This tells you if your order will fully fill at the best ask or walk up the book.
function availableSize(
asks: OrderbookLevel[],
maxPrice: number,
): number {
return asks
.filter((level) => parseFloat(level.price) <= maxPrice)
.reduce((sum, level) => sum + parseFloat(level.size), 0)
}Polling the book for a resting GTC order
When the contrarian bot places a GTC bid and waits for the price to come down, it polls the book to confirm its own order is visible and to track how far away the current ask is.
async function waitForFill(
tokenId: string,
targetBid: number,
maxWaitMs = 120_000,
): Promise<boolean> {
const deadline = Date.now() + maxWaitMs
while (Date.now() < deadline) {
const book = await fetchOrderbook(tokenId)
const quote = bestQuote(book)
if (!quote) {
await sleep(3000)
continue
}
// If best ask has come down to our bid level, we are likely filled
if (quote.ask <= targetBid + 0.01) return true
await sleep(3000)
}
return false
}Using the SDK client instead
If you are already using @polymarket/clob-client, use getOrderBook directly -- it handles auth headers and request signing where required:
import { ClobClient } from '@polymarket/clob-client'
async function getBook(client: ClobClient, tokenId: string) {
const book = await client.getOrderBook(tokenId)
const bid = book.bids[0] ? parseFloat(book.bids[0].price) : null
const ask = book.asks[0] ? parseFloat(book.asks[0].price) : null
console.log(`Best bid: ${bid}, Best ask: ${ask}`)
return book
}Interpreting a thin book
A spread wider than $0.04 on a 50-cent binary indicates low liquidity. In thin markets:
- FOK orders at the best ask are more likely to fail if someone else consumes that level first.
- GTC bids below mid have a reasonable chance of filling during a transient dip.
- The midpoint is a less reliable estimate of true probability.
function isLiquid(book: Orderbook, spreadThreshold = 0.04): boolean {
const quote = bestQuote(book)
if (!quote) return false
return quote.spread <= spreadThreshold
}Caching and rate limits
The CLOB book endpoint can be called without authentication but has rate limits. For a bot checking the book every 5 seconds on 2-3 markets, a simple in-memory cache with a 2-second TTL avoids hitting the limit:
const cache = new Map<string, { data: Orderbook; ts: number }>()
async function getCachedBook(tokenId: string, ttlMs = 2000): Promise<Orderbook> {
const cached = cache.get(tokenId)
if (cached && Date.now() - cached.ts < ttlMs) return cached.data
const data = await fetchOrderbook(tokenId)
cache.set(tokenId, { data, ts: Date.now() })
return data
}Summary
GET /book/?token_id=returns{ bids: [{price, size}], asks: [{price, size}] }sorted best-first.bids[0]= best bid,asks[0]= best ask.- Use
availableSize()to check if there is enough depth for your order. - Spread > $0.04 signals a thin book -- prefer GTC over FOK.
- Cache aggressively: 2-second TTL prevents rate-limit issues.
Related bots
contrarian — GTC maker bids on underdog outcomes
GTC maker bids on 20-50¢ underdog outcomes across all Polymarket markets. Zero taker fees, 30-min scan loop, multi-layer risk stack. Inspired by @Car's +$22K strategy.
resolution-scalper — Near-certain outcome buyer
Buys $0.95-$0.99 outcomes across Polymarket and holds to resolution for the $0.01-$0.05 spread. Skips esports / close sports via keyword filter, dedupes correlated markets, 20-position cap.