Tooling · 6 min read

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