A Polymarket order book API is any interface that gives you the bids and asks resting in a Polymarket market. Polymarket is a prediction market that matches orders in a central limit order book, and it publishes developer documentation for reading markets, books and prices. The exact services, addresses, message formats and limits are Polymarket's to define and they change, so take every one of those from its current docs.
Getting a book once is easy. Keeping a local copy correct while it changes many times a second is where clients break. This guide covers that part: the data model, a snapshot-plus-updates book in Python with its checks, and what to ask Polymarket's docs before relying on any feed.
What an order book holds
For each outcome in a market, a book is two sorted lists:
- Bids: prices people will buy at, with the size at each price. The best bid is the highest.
- Asks: prices people will sell at, with sizes. The best ask is the lowest.
Polymarket outcome shares trade at prices between 0 and 1 dollar, so a price reads directly as an implied probability. A best bid of 0.41 and a best ask of 0.44 give a midpoint of 0.425, or 42.5%, and a spread of 3 cents. The midpoint is an estimate, not a price you can trade at: buying now costs 0.44, and only up to the size at that level.
Where the data comes from
Polymarket's docs describe more than one service, and the names people search for reflect that:
- Market metadata. Searches for "gamma api polymarket" come from the name Polymarket has used for its service that lists markets, events and their details. Use it to map a human-readable market to the ids the book uses.
- Order books and trading. The CLOB side of the docs covers books, prices and orders.
- Streaming. A Polymarket websocket API, where offered, pushes book changes instead of making you poll.
- History and positions. A Polymarket data API, where offered, covers trades and activity rather than resting orders.
Check the current docs for which of these exist, which need authentication, and what each one's limits are. People looking for a Polymarket data download for research should also check whether the docs offer bulk history, since rebuilding history by polling live books is slow and incomplete.
A local book from a snapshot plus updates
The standard pattern: load a full snapshot, then apply each change in order, and start again from a fresh snapshot whenever you can't be sure you saw every change.
from decimal import Decimal
class GapError(Exception): pass
class CrossedBook(Exception): pass
class Book:
def __init__(self):
self.bids, self.asks, self.seq = {}, {}, None
def load_snapshot(self, bids, asks, seq):
self.bids = {Decimal(p): Decimal(s) for p, s in bids if Decimal(s) > 0}
self.asks = {Decimal(p): Decimal(s) for p, s in asks if Decimal(s) > 0}
self.seq = seq
def apply(self, side, price, size, seq):
if self.seq is None or seq != self.seq + 1:
msg = f"after {self.seq}, got {seq}"
self.seq = None # force a fresh snapshot
raise GapError(msg)
levels = self.bids if side == "bid" else self.asks
p, s = Decimal(price), Decimal(size)
if s == 0:
levels.pop(p, None)
else:
levels[p] = s # size replaces, it doesn't add
self.seq = seq
def top(self):
bid = max(self.bids) if self.bids else None
ask = min(self.asks) if self.asks else None
if bid is not None and ask is not None and bid >= ask:
raise CrossedBook(f"bid {bid} >= ask {ask}")
return bid, ask
Why it's written this way:
- Decimal, not float. Prices like 0.1 aren't exact in binary floating point, and a price used as a dictionary key has to match exactly.
- Size zero removes a level. Leaving zero-size levels in place makes
top()report a price nobody is offering. - A gap or a crossed book means rebuild. Don't patch. Fetch a fresh snapshot.
Two assumptions in that code must be checked against Polymarket's docs: whether an update gives the new total size at a price (as above) or a change to add, and whether updates carry a sequence number at all. If they don't, use whatever the docs offer instead, such as a hash or timestamp to compare with a fresh snapshot, and resync on a schedule.
Checks worth running all the time
- Age. Store the time you received each update. A book that hasn't changed in a minute might be quiet, or your connection might be dead.
- Sum of outcomes. In a two-outcome market, the best asks across both outcomes should sum to a bit more than 1. Far from that, something is off in your mapping.
- Spot comparisons. Every so often, fetch a fresh snapshot and compare it with your local book. Any difference is a bug.
Where ZippyOddsAPI fits
ZippyOddsAPI is independent and not affiliated with Polymarket. Its /v1/prediction-contracts endpoint returns native Polymarket US contract catalog rows and best bid and offer observations, with the documented source polymarket_us:
curl -s "https://zippyoddsapi.com/v1/prediction-contracts?source=polymarket_us&kind=contract,bbo&limit=100" \
-H "Authorization: Bearer $ZIPPYODDS_KEY"
ZippyOddsAPI gives best bid and offer here, not full depth, and the rows are raw: every one has raw_only: true, execution_eligible: false, payout: null and ev: null. Last trades and samples are kept apart from current offers in price_facts, and native_quote_updated_at is currently null because no quote-update time is inferred. For a complete walk of stored records, export=ndjson streams them, and only an end frame with complete: true confirms the walk finished. ZippyOddsAPI documents all of that, plus credit costs and how keys are issued, in the API docs.
For the wider picture of Polymarket's developer access, read our Polymarket API guide. To compare it with other venues, start with the prediction market API overview.