Polymarket API in Python: 3 Requests, No API Key Needed

A standard-library Python script reads one Polymarket market, the order book for one outcome and the fee that market charges, in three public requests with no key. Read this way, 309 of the 1,000 most-traded open markets charged takers nothing on 20 September 2026.

By Vultax Research Team6 min read
Article details

AI-assisted drafting, research and editing. Published by OmniOS OÜ. How we use AI

MethodologyRead as Markdown
Read article
A code editor showing a short Python script beside a chart window with a falling-object curve
A Python script and the chart it draws, open in a desktop code editor. Photo: MikeRun / Wikimedia Commons, CC BY-SA 4.0, cropped.

Most-traded open markets that charged takers nothing

0309 of 1,000

Each market's own fee fields, read on 20 September 2026

Sports markets at a 0.03 rate, not the published 0.05
065
Same read; markets listed September 2025 to July 2026
Public requests for one saved snapshot
03
The market, one outcome's order book and one research table, with no key or account
Live in VultaxBrowse live Polymarket marketsEvery open market with its price, volume and source time, kept current so you do not have to poll for it.

Three requests and no key: what the Polymarket API gives you

Polymarket's market data is public. Three requests return one market's identity and outcome prices, the full order book for one outcome and the fee that market charges a taker, with no API key and no account. Read that way on 20 September 2026, 309 of the 1,000 most-traded open markets charged takers nothing, and 65 sports markets reported a rate of 0.03 where Polymarket's published sports rate is 0.05. The full table is on the Polymarket fee calculator.

Two services do the work. Gamma describes a market: its question, its outcomes, the token ID behind each outcome, whether it is open and what it charges. CLOB is the exchange: it returns the bids and asks for one token at one moment. A price from Gamma is a summary; only the order book says what can be bought or sold, and how much of it.

Download the Python script or open the notebook. Python 3.10 or later is enough, because the script uses only the standard library. It places no orders and never retries on its own. A second script, polymarket_fee_survey.py, repeats the fee count above over as many markets as you ask for.

Responsibilities checked against Polymarket's documentation and live responses on 20 September 2026 UTC. The three sources are read one after another and are not one synchronized dataset.
ServiceAsk it forWhat comes back
Gamma, gamma-api.polymarket.comOne market, by its exact slugMarket and condition IDs, labelled outcomes with their token IDs, a summary price per outcome, open or closed, and the market's own fee settings
CLOB, clob.polymarket.comOne order book, by an outcome's token IDEvery bid and ask with its size, and the time the book was produced
Vultax research exportsOne published table, by article and dataset IDA historical table with its observation window, population, sources and a version that changes only when the table does

Download table figures:CSVJSON with sources

Run it in two commands

A market is addressed by its slug, the last part of its address on polymarket.com. The first command lists the most-traded open markets with their slugs and what each charges. The second saves a snapshot of one of them. You can paste a whole polymarket.com address after --slug instead; the script keeps the last segment.

On Windows, replace python3 with py. The script writes a new folder each time and refuses to overwrite an existing one, so an earlier snapshot is never lost. Open the token IDs in outcomes.csv as text: they are more than 70 digits long, and a spreadsheet that reads them as numbers silently changes them.

Terminal
python3 polymarket_snapshot.py --list 10
python3 polymarket_snapshot.py --slug another-fed-rate-hike-in-2026
Output of the second command
Another Fed rate hike in 2026?
  Yes                      Gamma price 0.865
  No                       Gamma price 0.135
Order book for 'Yes': bid 0.86 (100 shares), ask 0.87 (3,846 shares), spread 0.01; book time 2026-09-20T20:57:45.725000+00:00
Taker fee for 100 shares at the 0.87 ask: $0.5655 (this market's rate is 0.05; an estimate, not a quote)
Research table: 5 rows, 7,178 wallets, kept separate from this market
Saved snapshot.json, outcomes.csv and research.csv to polymarket-example
Captured on 20 September 2026 at 20:57 UTC. Prices, sizes and the fee estimate are whatever the market shows when you run it.

Match the outcome before you read a price

Gamma returns outcomes, prices and token IDs as three separate lists, and each list arrives as a string of JSON inside the JSON. The script parses all three, checks that they are the same length and only then pairs them by position. If the lengths differ it stops, because a guess here attaches one outcome's price to another.

The first outcome is not always Yes: a sports market lists two team names. The script reads the book for the first outcome under its real label; add --outcome No, or a team name, to choose another. A label that does not match exactly one outcome is refused and the valid labels are printed.

Three identifiers are easy to confuse. The market ID names the listing in Gamma. The condition ID names the question on-chain. The token ID names one outcome of that question, and it is what CLOB's order book is keyed on. The script checks that the book it receives carries both the token it asked for and the market's condition ID before it keeps a single price.

Part of Gamma's reply for this market, IDs shortened
{
  "question": "Another Fed rate hike in 2026?",
  "conditionId": "0x9b1a03b4…9e73b114",
  "outcomes": "[\"Yes\", \"No\"]",
  "outcomePrices": "[\"0.865\", \"0.135\"]",
  "clobTokenIds": "[\"8349889393…8777625\", \"5169973159…7696963\"]"
}

A listed price is not a quote you can trade

Gamma's price for an outcome is a summary. What can actually be bought is the lowest ask in the book and the size resting at it, and what can be sold is the highest bid. The script finds both by value, because the order of levels in the reply is not a promise, and reports a spread only when both sides exist.

One-sided books are normal. The run below is a settled NFL game read the same evening: the winner has bids at 99.9 cents and nobody left selling. The best ask and the spread are missing, and the script says so instead of writing zero. A zero there would read as a free share.

The book's own timestamp, Gamma's update time and the time of each request are kept as separate fields in snapshot.json. The requests run one after another, so the file describes three nearby moments and is not a single instant. A visible ask and its size do not guarantee a later fill.

A settled market: bids, no asks
Packers vs. Jets
  Packers                  Gamma price 0.9995
  Jets                     Gamma price 0.0005
Order book for 'Packers': bid 0.999 (1,073,704 shares), no asks, no spread without both sides; book time 2026-09-20T20:57:48.973000+00:00
Taker fee for 100 shares: $0 (this market's rate is 0)
Research table: 5 rows, 7,178 wallets, kept separate from this market
Saved snapshot.json, outcomes.csv and research.csv to onesided
Captured on 20 September 2026 at 20:57 UTC.

Read the fee the market actually charges

Every market's Gamma record carries its own fee settings. feesEnabled says whether a trading fee applies at all. feeSchedule.rate is the coefficient in Polymarket's formula, fee = shares × rate × price × (1 − price), which only the taker pays. Every market in our read reported an exponent of 1, the published curve; the script estimates nothing for any other value.

The record matters because the category does not settle the question. Polymarket publishes 0.05 for sports, yet on 20 September 2026 the day's NFL game markets carried a rate of 0 and season-long winner markets carried 0.03. The script prints the taker fee for 100 shares at the best ask from the market's own rate, and snapshot.json includes a link that opens the fee calculator on the same numbers.

The fee fields for this market
{
  "feesEnabled": true,
  "feeType": "economics_fees",
  "feeSchedule": {
    "exponent": 1,
    "rate": 0.05,
    "takerOnly": true,
    "rebateRate": 0.25
  }
}
The same arithmetic in Python
def taker_fee(shares, price, rate):
    """Polymarket's published formula, rounded once to five decimal places."""
    return round(shares * rate * price * (1 - price) + 1e-12, 5)

taker_fee(100, 0.87, 0.05)   # 0.5655

Read a price history, and the limits that are not documented

The order book is a single moment. For a price over time, CLOB has a third public endpoint, prices-history, keyed on the same token ID. It returns a list of points, each a Unix time t and a price p, one per fidelity minutes. We read it for the Flávio Bolsonaro contract in Polymarket's Brazil presidential market on 19 October 2026, and four of its behaviours decide whether a home-made series is right.

A custom window is capped at 15 days. startTs and endTs 16 days apart are refused with HTTP 400 and the message 'interval is too long', at one-minute, hourly and daily fidelity alike. A longer history has to be read in pieces of 15 days or less and joined, which is what the function below does.

interval=max does not mean everything. With fidelity=60 it returned 711 points covering only the last 30 days; with no fidelity, 4,280 points over the same 30 days. Only interval=max with fidelity=1440 reached back to the contract's first day, 19 November 2025: one point a day, each stamped 3 to 59 seconds after 00:00 UTC.

Every reply ends with one point too many. Whatever window was asked for, the last point is the current price, stamped at the moment of the request, even when the window ended weeks ago. Left in, it draws a straight line from the end of the window to today.

Points go missing, and the reply does not say so. From 1 August to 18 October the hourly series had 1,885 points for 1,896 hours: 11 hours had none. The daily series had 322 points for the 335 days from 19 November 2025 to 19 October 2026. The function reports each gap so it can be recorded as missing rather than filled with the last price. The Brazil first-round night drawn from these points is in our results guide.

Download polymarket_price_history.py for the same functions as a command: pass a token ID from outcomes.csv and a window, or --daily for the whole daily series, and it saves a CSV and prints every gap. Like the snapshot script it never retries on its own and refuses to overwrite a file.

Polymarket CLOB prices-history, read for one token on 19 October 2026. The limits are not in Polymarket's documentation and can change.
RequestWhat came back on 19 October 2026
interval=max&fidelity=1440What came back on 19 October 2026322 daily points, from the contract's first day, 19 November 2025
interval=max&fidelity=60What came back on 19 October 2026711 points, the last 30 days only
interval=maxWhat came back on 19 October 20264,280 points, the last 30 days only
startTs and endTs 15 days apart, fidelity=1What came back on 19 October 2026A point a minute for the 15 days, with gaps
startTs and endTs 16 days apart, any fidelityWhat came back on 19 October 2026HTTP 400: 'interval is too long'
Any of the aboveWhat came back on 19 October 2026One extra point at the end: the current price

Download table figures:CSVJSON with sources

price_history.py
import json
import time
import urllib.parse
import urllib.request
from datetime import datetime, timedelta, timezone

CLOB = "https://clob.polymarket.com"
MAX_WINDOW = timedelta(days=15)  # a longer startTs-endTs window is refused with HTTP 400


def _get(params):
    query = urllib.parse.urlencode(params)
    request = urllib.request.Request(f"{CLOB}/prices-history?{query}",
                                     headers={"User-Agent": "price-history-example/1.0"})
    with urllib.request.urlopen(request, timeout=30) as reply:
        return json.load(reply)["history"]


def price_history(token_id, start, end, fidelity):
    """Prices for one outcome token from `start` to `end` (UTC datetimes), one point per `fidelity` minutes.

    Long windows are read in pieces of at most 15 days. Returns (points, gaps): points as
    (datetime, price) inside the window, and each pair of neighbours with at least one point missing between them.
    """
    seen = {}
    piece_start = start
    while piece_start < end:
        piece_end = min(piece_start + MAX_WINDOW, end)
        for p in _get({"market": token_id, "startTs": int(piece_start.timestamp()),
                       "endTs": int(piece_end.timestamp()), "fidelity": fidelity}):
            # Every reply ends with the current price, stamped now: keep only the window.
            if start.timestamp() <= p["t"] <= end.timestamp():
                seen[p["t"]] = float(p["p"])
        piece_start = piece_end
        time.sleep(0.2)
    points = [(datetime.fromtimestamp(t, timezone.utc), seen[t]) for t in sorted(seen)]
    step = fidelity * 60
    gaps = [(a[0], b[0]) for a, b in zip(points, points[1:])
            if (b[0] - a[0]).total_seconds() > 1.5 * step]
    return points, gaps


def daily_history(token_id):
    """The whole daily series since the token started trading: the one request that reaches back further than 15 days."""
    history = _get({"market": token_id, "interval": "max", "fidelity": 1440})
    return [(datetime.fromtimestamp(p["t"], timezone.utc), float(p["p"])) for p in history[:-1]]
Standard library only. Long windows are read in 15-day pieces; the current-price point is dropped; gaps are reported, not filled.
Output, Flávio Bolsonaro's Yes token, read 19 October 2026
100 minute points, 0 gaps
  20:00 UTC  0.6365
  20:22 UTC  0.643
  20:59 UTC  0.8545
1885 hourly points, 01 Aug 00:00 to 18 Oct 23:00 UTC, 11 gaps
322 daily points, 19 Nov 2025 to 19 Oct 2026
First-round night minute by minute, August to October hourly, and the whole daily series. The full chart of that night is in our Brazil results guide.

Add research without inventing a join

The third request downloads the activity table from our Polymarket trader study: the share of wallets in profit by how many trades they closed, across 7,178 wallets in July 2026. It is saved as research.csv beside the market files and is never merged into them. It describes a historical population, not the market you selected.

The export carries its article address, section, observation window, population, sources and a content version. The version changes only when the published table changes, so a stored copy can be checked against the page later. Cite the article and its window when you use a figure.

Research access lists every format: Markdown for each study, CSV and JSON for each table and chart, and read-only tools for AI assistants. These are published research. They are not account data, a complete trade history or an order-routing API.

When it stops, it says why

Every failure ends in one line that names the cause. Nothing is saved on an error, and a request that failed is never repeated automatically.

Messages shortened. Each was produced by running the script on 20 September 2026 UTC, except HTTP 429, which did not occur.
What you seeWhyWhat to do
'…' is an event with 4 markets, not one marketThe slug names an event, which groups several marketsPass one of the market slugs the message lists
No market has the exact slug '…'A typing slip, or the market has been removedRun --list 10 and copy a slug from the output
--outcome 'Maybe' does not name exactly one outcomeThe label is not one of this market's outcomesUse one of the labels the message prints
Order book: unavailableThe market is closed or not accepting orders, or the reply failed an identity checkRead the reason stored under receipts in snapshot.json
HTTP 429 from the address requestedRequests are arriving too quicklyWait, for as long as the message says when it gives a time, before running it again
Output directory already existsA folder of that name is already therePass a new --output path

Download table figures:CSVJSON with sources

An event slug, refused with the alternatives
error: 'fed-decision-in-october' is an event with 4 markets, not one market. Pass one of its market slugs:
  fed-decreases-interest-rates-by-50-bps-after-october-2025-meeting
  fed-decreases-interest-rates-by-25-bps-after-october-2025-meeting
  no-change-in-fed-interest-rates-after-october-2025-meeting
  fed-increases-interest-rates-by-25-bps-after-october-2025-meeting

What to build next

A good first project is a dated watchlist. Keep the exact market and token IDs, run the snapshot on a schedule and compare the same token with itself over time. Record a run that failed as a gap. A missing hour drawn as a flat line is the most common way a home-made price history goes wrong.

For more than a handful of markets, read Polymarket's pagination and real-time documentation before raising the request rate, and checkpoint what has been saved so a restart does not begin again from nothing. The Vultax terminal does this continuously across the venue; the script is for the question you want to own end to end.

Downloads reproduce this page’s tables and charts with their source notes. Live market widgets are separate. Use Vultax research with an AI assistant. Press note.

Questions this page answers

Does the Polymarket API need an API key?
Not for market data. Gamma's market records and CLOB's order books are public, and the example script reads both without a key, a wallet or an account. Placing or cancelling orders is different: that requires authenticating with a wallet, and the script does neither.
What is the difference between Polymarket's Gamma API and CLOB API?
Gamma describes markets: the question, its outcomes, their token IDs, summary prices, status and fee settings. CLOB is the exchange: it returns the order book, with every bid and ask, for one outcome's token ID. Use Gamma to find and identify a market and CLOB to see what can be traded.
How do I find a Polymarket market's token ID?
Request the market from Gamma by its slug and read clobTokenIds, a JSON-encoded list in the same order as outcomes. Pair the two lists by position after checking they are the same length. Keep token IDs as text: they are more than 70 digits long and lose precision as numbers.
Why is the best bid, best ask or spread missing for a Polymarket market?
Because one side of the book is empty. It is common just before and after a market settles, when nobody will sell a near-certain winner or bid for a loser. A missing side is unknown, not zero, and a spread exists only when both sides do.
How do I read a Polymarket market's fee from the API?
Read feesEnabled and feeSchedule on the market's Gamma record. If feesEnabled is not true, or feeSchedule.rate is 0, a taker pays nothing. Otherwise the taker fee is shares × rate × price × (1 − price). On 20 September 2026 the rate was 0, 0.03, 0.04, 0.05 or 0.07 across the 1,000 most-traded open markets.
How do I get Polymarket historical prices from the API?
Call CLOB's prices-history endpoint with the outcome's token ID. For the whole history use interval=max with fidelity=1440, which returns one point a day from the token's first day. For finer steps pass startTs and endTs no more than 15 days apart and join the pieces; drop the last point of each reply, which is the current price.
Why does Polymarket's prices-history endpoint say 'interval is too long'?
Because startTs and endTs are more than 15 days apart. On 19 October 2026 the limit applied at every fidelity. Read a long window in 15-day pieces, or use interval=max with fidelity=1440 for daily points.

Sources and evidence

A teaching example built on public data: it does not trade, and provider responses can change without notice. Not financial advice.

Watch this measurement live in Vultax.

Every open market with its price, volume and source time, kept current so you do not have to poll for it.

New studies, by email.

Measured, sourced, and sent once per study. No digests, no promotions.

One email when a study publishes. Unsubscribe anytime. Privacy