API
Validating Polymarket API responses with Pydantic
The SDK returns plain dicts. That is fine until a snapshot list is empty or an outcome label changes, and the failure surfaces three functions later. Pydantic moves validation to the boundary where the error is easy to read.
What this guide covers
- get_market_books returns books keyed by outcome label (Yes/No), each a list of {t, bids, asks}
- Ladders are [price, size] pairs, best price first - bids descend, asks ascend
- A snapshot list can be empty for markets with no captures in the window
- Pydantic v2 validates and coerces at the boundary, catching shape changes early
Model the book snapshot
Each snapshot has a timestamp t and two ladders. A ladder entry is a two-element list, price first and size second. Modeling it as a tuple of floats means a malformed row - a string price, a missing size - raises where you parse rather than where you compute.
Keep the model permissive on optional structure but strict on the essentials. A snapshot with no bids is valid; a snapshot whose bids are not pairs is not.
Model the outcome map
books data is keyed by outcome label. On crypto Up/Down markets those are Yes and No, but typing the key as a plain dict of lists is safer than assuming the labels, because the archive also covers threshold and sports markets where labels differ.
Validate that each value is a list of snapshots, not a single object. The SDK always returns a list, but a defensive model makes the contract explicit for the rest of your code.
Where validation pays off
The value of typing the boundary appears when you refactor. If you later switch from get_market_books to iter_market_books, or from books to prices, the model tells you immediately whether the shapes still line up. Without it, the change shows up as a wrong number rather than an exception.
It also documents the API for the next reader. A Pydantic model is executable documentation of the response shape, and it cannot drift out of date without breaking tests.
Where the models should live
Keep the models in a small module separate from the fetch code, so both scripts and notebooks import the same types. That way a change to the response shape is a one-line edit in one place, not a hunt through every consumer.
Pair the models with a fixture: save one real response to a file and assert your models parse it in a test. When the API evolves, the test tells you immediately rather than at the end of a long backtest run.
Models are also a good boundary for the endpoints the SDK does not wrap. The reference-price and snapshot-at calls return their own shapes, and typing them the same way keeps the whole pipeline consistent whether a call goes through the SDK or through requests directly.
Code examples
import os
from pydantic import BaseModel, field_validator
from polyorderbooks import PolyOrderbooksClient
class Snapshot(BaseModel):
t: str
bids: list[tuple[float, float]]
asks: list[tuple[float, float]]
@field_validator("bids", "asks")
@classmethod
def non_negative(cls, levels):
for price, size in levels:
assert 0 <= price <= 1 and size >= 0
return levels
with PolyOrderbooksClient(api_key=os.environ["POLYORDERBOOKS_API_KEY"]) as c:
raw = c.get_market_books(
"btc-updown-5m-1787486400",
start_ts="2026-10-17T07:00:00Z",
end_ts="2026-10-17T08:00:00Z",
resolution="1s",
)
books = {
outcome: [Snapshot(**row) for row in rows]
for outcome, rows in raw["data"].items()
}
print({k: len(v) for k, v in books.items()})Free tier
The Starter plan is free, includes order books, prices, and metrics at 250ms resolution, with 3 days of history, 60 requests/min, 1,000/day, 1 free AI backtest, and 3 strategy backtests. No credit card required.
Paid data windows from $19/mo extend history to 30–120 days at higher throughput. Backtest AI add-on is +$19/mo or standalone at $29/mo.
FAQ
Does the SDK return typed objects?
No. It returns plain dicts. Pydantic models add validation and typing at the boundary so shape errors surface early.
How are book ladders shaped?
Each is a list of [price, size] pairs, best price first: bids descend and asks ascend. An outcomes' value is a list of {t, bids, asks} snapshots.
Can a snapshot have no bids?
Yes. A snapshot list can also be entirely empty for a market with no captures in the window, so guard before indexing.
Pydantic v1 or v2?
Use v2. It is faster and the field_validator API shown here is v2 syntax.