API

Paginating Polymarket API results in Python

Every list endpoint caps its page size and hands back a cursor for the next page. Ignoring it silently truncates your data. The SDK gives you both a manual cursor and generator helpers that loop for you.

What this guide covers

  • List responses carry metadata.next_cursor; absent or null means the end
  • iter_markets and iter_market_books yield pages until the cursor is exhausted
  • get_market_books accepts a limit and cursor for manual paging
  • Omitting the cursor loop returns only the first page, silently truncating results

The cursor contract

A list response is a page plus a metadata block. The block's next_cursor is a string when more pages exist and absent or null at the end. The loop is always the same: fetch a page, process data, read next_cursor, stop when it is empty.

The failure mode worth naming: code that fetches one page and reads data. It works in testing, where the first page holds everything, and silently loses rows in production where it does not.

Using the generator helpers

The SDK provides iter_markets and iter_market_books, which yield each page and drive the cursor internally. For most tasks these are all you need - a for loop over pages, accumulating or processing as you go.

Because they yield whole pages rather than individual rows, you keep the ability to batch-process. A common pattern is to append each page's data to a list and concatenate once at the end, which is faster than appending row by row.

Driving the cursor manually

When you need control - pausing, resuming, or stopping early once you have enough - drive the cursor yourself with get_market_books and pass cursor and limit explicitly. This is also how you checkpoint a long crawl so a crash does not lose progress.

Store the last cursor and the last timestamp you processed. On resume, re-issue the request with that cursor and continue. The cursor is the API's way of letting you do exactly this.

Checkpointing a long crawl

A crawl that runs for many pages can fail partway, and starting over wastes quota. Persist the last cursor and the count of processed rows to a small state file after each page, then resume from that cursor on the next run.

Because the cursor encodes where the API left off, resuming is exact: there is no risk of double-counting or skipping as long as you do not also change the query parameters. Changing the filter mid-crawl invalidates the cursor, so treat the query as part of the checkpoint too.

This pairs naturally with local caching. Cache each page under its own key and record the cursors you have consumed, so a resumed run re-reads cached pages and only fetches the tail it is missing.

Code examples

import os

from polyorderbooks import PolyOrderbooksClient

with PolyOrderbooksClient(api_key=os.environ["POLYORDERBOOKS_API_KEY"]) as c:
    # Helper form: yields pages until next_cursor is exhausted.
    pages = list(c.iter_markets(event_slug="crypto-up-down", limit=500))
    total = sum(len(p["data"]) for p in pages)
    print("markets:", total, "pages:", len(pages))

    # Manual form: stop early once enough markets are collected.
    cursor, collected = None, []
    while len(collected) < 100:
        page = c.list_markets(limit=25, cursor=cursor)
        collected.extend(page["data"])
        cursor = (page.get("metadata") or {}).get("next_cursor")
        if not cursor:
            break
    print("collected:", len(collected))

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

How do I know there are more pages?

When metadata.next_cursor is a non-empty string. When it is absent or null, you have reached the end.

What do the iter helpers do?

iter_markets and iter_market_books yield each page and advance the cursor until it is exhausted, so you never write the loop yourself.

Can I resume a crawl after a crash?

Yes. Drive the cursor manually, persist the last cursor, and re-issue the request with it on resume rather than starting from the first page.

Why did I only get one page of data?

Because the cursor was not followed. A single call returns only the first page; always loop on metadata.next_cursor.