API

Loading Polymarket data into pandas

Pandas wants tidy rows. The API returns nested snapshots with ladders, so the first job is flattening: one row per timestamp and outcome, with best bid, best ask, mid and depth as columns.

What this guide covers

  • Books are keyed by outcome with a list of {t, bids, asks} snapshots
  • Ladders are best-first, so bids[0] and asks[0] are the touch
  • Empty snapshots must be skipped before reading the touch
  • get_market_prices returns a series you can join to the flattened books on timestamp

Flatten to one row per timestamp

The analysis-friendly shape is one row per outcome per timestamp, with columns for the best bid, best ask, the mid, and the summed depth on each side. From that frame, spreads, depths and repricing are one line each.

Skip empty snapshots rather than padding them. A row with null bid and ask is valid data - the book was one-sided or absent - and should be represented, not fabricated.

Computing derived columns

Once flattened, the mid is the average of best bid and best ask, the spread is their difference, and depth is the sum of sizes across levels. Compute them as columns so you can filter and group without re-parsing ladders.

Guard the one-sided case: when either side is empty the mid is undefined, so leave it null rather than averaging against zero. A zero mid would corrupt every downstream average.

Joining prices to books

The prices endpoint gives you a series per market; join it to the flattened books on timestamp to compare the traded price against the quoted touch. Mismatches between the last trade and the mid are exactly where microstructure questions live.

Keep the timestamp as a proper datetime dtype, not a string, so joins and resampling work. Parse once at load time rather than casting repeatedly during analysis.

Resampling to a coarser grid

Books captured finely are often easier to reason about at a coarser resolution. After loading the frame with a datetime index, resample to one second or one minute and aggregate the spread and depth per bucket.

Choose the aggregation to match the question: the mean spread describes typical conditions while the max describes the worst window a taker might hit. Reporting only the mean hides the tail that actually determines execution cost.

Resampling also reduces memory for long studies. If you do not need per-250ms detail, dropping to one-second rows can cut the frame by a large factor while keeping every metric you plan to compute.

Code examples

import os
import pandas as pd

from polyorderbooks import PolyOrderbooksClient

rows = []
with PolyOrderbooksClient(api_key=os.environ["POLYORDERBOOKS_API_KEY"]) as c:
    books = c.get_market_books(
        "eth-updown-5m-1787486400",
        start_ts="2026-10-17T07:00:00Z",
        end_ts="2026-10-17T08:00:00Z",
        resolution="1s",
    )
for outcome, snaps in books["data"].items():
    for s in snaps:
        bids, asks = s.get("bids") or [], s.get("asks") or []
        rows.append({
            "t": pd.to_datetime(s["t"]),
            "outcome": outcome,
            "best_bid": bids[0][0] if bids else None,
            "best_ask": asks[0][0] if asks else None,
            "bid_depth": sum(sz for _, sz in bids),
            "ask_depth": sum(sz for _, sz in asks),
        })

df = pd.DataFrame(rows).set_index("t").sort_index()
df["mid"] = (df["best_bid"] + df["best_ask"]) / 2
df["spread"] = df["best_ask"] - df["best_bid"]
print(df.groupby("outcome")["spread"].describe())

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

What shape should the DataFrame be?

One row per outcome per timestamp, with columns for best bid, best ask, mid, spread and per-side depth. That makes most microstructure metrics one line each.

How do I handle one-sided snapshots?

Keep the row but leave the missing side and the mid as null. Never average against zero, which would corrupt downstream statistics.

How do I get the touch?

Ladders are best-first, so bids[0][0] is the best bid and asks[0][0] is the best ask, after guarding for empty lists.

Should timestamps be strings or datetimes?

Parse to a datetime dtype at load time so joins, sorting and resampling work without repeated casting.