Polymarket data model
What the Polymarket endpoints and the archive record: market and token ids, event rows, order-book snapshots and the on-chain settlement.
The Polymarket CLOB here is recorded by Vike, not a proxy of Polymarket's own API, and settlement comes from the chain. These are the rules that hold across the endpoints and the bulk archive.
Identifiers
Two different ids appear in this namespace and they are not interchangeable.
condition_id— a0x-prefixed hex string identifying a market. It is what/markets/{condition_id}and/settlements/{condition_id}take in the path.token_id— a long decimal string identifying one outcome token. A binary market has two, returned intoken_ids. It is what the per-token endpoints take:/market_stats,/ohlcv,/orderbookand/events.
Get both from /markets.
Event rows
These notes are about the rows /events returns.
bids and asks are JSON-encoded strings, not arrays
The bids and asks fields hold JSON text, not arrays:
"bids": "[[0.782,0.0]]" must be
parsed a second time to become [[0.782, 0.0]], and an event that carries no book side sends the
empty string "" rather than null or [].
Unpopulated numeric fields are 0, not null
A zero in an event row is usually absent data. A price_change row carries
"best_ask": 0 and "best_bid": 0 — that is "this event type does not populate the field", not
"the best bid is zero". status is "" for the same reason. Read fields according to
event_type; do not treat a zero here as a price.
Book snapshots repeat rows and may lack a side
These notes are about /orderbook.
The same (side, level) can arrive several times — de-duplicate before summing size
The repeats are byte-identical rows, they are not extra depth, and a client that sums the raw array
overstates the book several-fold: de-duplicate on (side, level) first. The multiplier is
per-token and is not stable over time, so do not code against a fixed factor: twelve tokens
sampled in one minute on 2026-09-03 returned factors of 1 (no duplication at all), 2, 5, 9 and 11,
and the token that returned 6 copies per level on 2026-09-02 returned 9 the next day. Every row in
a response shares a single as_of_ts: the recorder writes several snapshot rows that carry the
same venue timestamp, and /orderbook returns all of them, so the repeats are one moment
fanned out, not successive ones. Read the depth of a side as its max(level), never as a row count.
A snapshot's rows come in a fixed order. Asks, when a snapshot has them, come first in the array (the
side column sorts alphabetically, ask before bid), then the bids, each ascending by level.
Do not assume a response contains both sides: group by side and handle an absent one, which
matters to any client computing a spread.
Settlement is chain truth
Settlement is chain truth; the market row may disagree
/settlements/{condition_id} is the on-chain resolution, not the market's own lifecycle flags, and the two agree in one
direction only. /markets/{condition_id}
derives closed from on-chain resolutions plus the venue's own closed flag, so a market with a settlement row
reads closed: 1 there. The reverse does not hold: a market the venue closed without the
chain ever resolving it — a cancellation or an oracle void — is closed: 1 in the directory and
[] from the settlements route (about 2,134 markets when measured on 2026-09-03). So [] means "the chain resolved
nothing", never "still open". For "how did this end", the settlement is the authority.
Wallet labels
How an address gets a label: a consensus over several sources, what each source claimed, how to read the fields and why a thin answer is ambiguous.
Freshness
How current each Data API answer is: the provisional hourly row, the freshness block, the position-match gate, snapshot age and the archive manifest.