Polymarket API: Gamma, CLOB and Data API, with tested code
13 min read
By the Totalis team. We build the combo platform for prediction markets on Hyperliquid.

Polymarket has two separate APIs. The international one, for polymarket.com, is split by job: the Gamma API finds events and markets, the CLOB API serves prices and order books and takes orders, and the Data API returns trades, positions and history. Reading all three is free and needs no key. Trading needs a wallet signature to create API credentials, then signed requests. Polymarket US, the US app, has its own API with a public market data host and key-based trading after identity checks. Every example below ran against the live APIs on 11 November 2026.
Contents
This is a developer guide, not financial or legal advice. For the product itself, start with what Polymarket is.
Which Polymarket API do you need?#
Polymarket's API overview lists the international services. Polymarket US is a separate exchange with separate docs, accounts and keys.
| API | Base URL | Used for | Key needed |
|---|---|---|---|
| Gamma | https://gamma-api.polymarket.com | Events, markets, series, tags, search, sports metadata | No |
| CLOB | https://clob.polymarket.com | Order books, prices, midpoints, price history; orders and cancels | Only to trade or read your orders |
| Data | https://data-api.polymarket.com/v2 | Trades, positions, activity, holders, leaderboards | No |
| Market WebSocket | wss://ws-subscriptions-clob.polymarket.com/ws/market | Live book and price updates | No |
| User WebSocket | wss://ws-subscriptions-clob.polymarket.com/ws/user | Your orders and fills | Yes |
| Relayer and Bridge | relayer-v2.polymarket.com, bridge.polymarket.com | Gasless wallet transactions, deposits and withdrawals | Varies |
| Polymarket US public | https://gateway.polymarket.us | US markets, events, books, prices | No |
| Polymarket US trading | https://api.polymarket.us | US orders, portfolio, balances, WebSockets | Yes |
International Polymarket runs on Polygon. Its collateral is pUSD, a USDC-backed token on Polygon, per Polymarket's pUSD page. The Bridge API's withdraw route sends pUSD out to other chains and tokens; the same flow in the app is in how to withdraw from Polymarket. Polymarket US is a CFTC-regulated exchange and clearinghouse where all trading is in US dollars, per its overview.
Is the Polymarket API free?#
Yes. Polymarket's docs list no charge for the API, and the Data API reference says "No API key or authentication is required." Limits are per IP address and enforced by Cloudflare, which slows requests that go over rather than rejecting them, per Polymarket's rate limits page. What costs money is trading: the CLOB has a fee-rate endpoint that returns the base fee for each token, and our Polymarket fees guide explains the schedule.
How do I use the Polymarket API? A tested walkthrough#
The three examples below need no account. They follow one market across the APIs: "Will the Fed increase interest rates by 25 bps after the December 2026 meeting?", from Polymarket's Fed Decision in December event. We ran them on 11 November 2026 with Python 3.14, requests 2.34, websockets 15.0.1 and Node 24, which runs a .ts file as is.
Example 1: find a market on Gamma, then read its CLOB order book (Python)#
A Gamma event holds one market per outcome. Each market has two outcome IDs, Yes first and No second, which the CLOB and WebSocket use as token_id or asset_id.
import json
import requests
GAMMA = "https://gamma-api.polymarket.com"
CLOB = "https://clob.polymarket.com"
SLUG = "fed-decision-in-december-20260729232808632"
def asset_ids(market: dict) -> list[str]:
if market["version"] == "v2":
return market["positionIds"]
return json.loads(market["clobTokenIds"])
event = requests.get(f"{GAMMA}/events/slug/{SLUG}", timeout=10).json()
print(event["title"], "| ends", event["endDate"])
for m in event["markets"]:
yes_price = json.loads(m["outcomePrices"])[0]
print(f'{m["groupItemTitle"]:<18} Yes {yes_price:<7} {m["version"]}')
hike = next(m for m in event["markets"] if m["groupItemTitle"] == "25 bps increase")
yes_id = asset_ids(hike)[0]
book = requests.get(f"{CLOB}/book", params={"token_id": yes_id}, timeout=10).json()
print("best bid:", book["bids"][-1])
print("best ask:", book["asks"][-1])
print("tick:", book["tick_size"], "| min size:", book["min_order_size"])
Output at 10:06 UTC on 11 November 2026:
Fed Decision in December? | ends 2026-12-10T04:59:00Z
50+ bps decrease Yes 0.0025 v1
25 bps decrease Yes 0.0035 v1
No change Yes 0.039 v1
25 bps increase Yes 0.855 v1
50+ bps increase Yes 0.0985 v1
best bid: {'price': '0.85', 'size': '236504.52'}
best ask: {'price': '0.86', 'size': '29073.64'}
tick: 0.01 | min size: 5
Three details in that output catch first scripts out:
outcomes,outcomePricesandclobTokenIdsare JSON strings inside the JSON. Parse them again; index 0 is Yes and index 1 is No.- The ID field depends on
version. Polymarket's market details page says to readpositionIdsfor Polymarket Protocol V2 (v2) markets andclobTokenIdsfor older CTF (v1) markets, and warns that both can be filled in. This market isv1. Polymarket's market discovery guide adds that event listings default tov1events, so a scan of every market needs a second pass withversion=v2. - Both sides of the CLOB book end at the best price. Bids are sorted up and asks down, so the best bid and best ask are both the last element. The first elements are the far-away 0.01 bids and 0.99 offers.
The 0.855 that Gamma gave for the 25 bps increase is the midpoint of the 0.85 bid and 0.86 ask. Polymarket's prices and order book page says the price polymarket.com displays is that midpoint, or the last trade if the spread is wider than 0.10. For one number per outcome, the CLOB also has /midpoint and /price, and batch routes such as /books, which takes up to 500 tokens per request.
Example 2: page through trades on the Data API (TypeScript)#
The Data API v2 wraps every response in data and pages with pagination.next_cursor. Polymarket's Data API v2 reference says to resend the same filters with each cursor and stop when next_cursor is null. Filtering by condition, the market's on-chain ID from Gamma's conditionId, returns trades in both of its outcomes.
const DATA = "https://data-api.polymarket.com/v2";
const condition = "0x8a4196617dcb703e49d9bb36d3847bf6a75fd364dbd19f8d408ac944bf1a03d8";
const since = Math.floor(Date.now() / 1000) - 24 * 60 * 60;
type Trade = { outcome: string; side: string; size: number; price: number; timestamp: number };
type Page = { data: Trade[]; pagination: { has_more: boolean; next_cursor: string | null } };
const byOutcome = new Map<string, { trades: number; shares: number; usdc: number }>();
let cursor: string | null = null;
let pages = 0;
let reachedWindowStart = false;
while (!reachedWindowStart) {
const params = new URLSearchParams({ condition, limit: "200" });
if (cursor) params.set("cursor", cursor);
const res = await fetch(`${DATA}/trades?${params}`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const page: Page = await res.json();
pages += 1;
for (const t of page.data) {
if (t.timestamp < since) {
reachedWindowStart = true;
break;
}
const s = byOutcome.get(t.outcome) ?? { trades: 0, shares: 0, usdc: 0 };
s.trades += 1;
s.shares += t.size;
s.usdc += t.size * t.price;
byOutcome.set(t.outcome, s);
}
cursor = page.pagination.next_cursor;
if (!cursor) break;
}
console.log(`pages read: ${pages}, reached the start of the window: ${reachedWindowStart}`);
for (const [outcome, s] of byOutcome) {
console.log(
`${outcome}: ${s.trades} trades, ${s.shares.toFixed(2)} shares, ` +
`average price ${(s.usdc / s.shares).toFixed(4)}`,
);
}
Output at 10:07 UTC on 11 November 2026:
pages read: 4, reached the start of the window: true
Yes: 481 trades, 593616.69 shares, average price 0.8472
No: 269 trades, 289882.95 shares, average price 0.1504
The feed is newest first, and its start and end filters only work on a single wallet's trades, so the loop stops itself once it passes the 24-hour mark. Trades on No count too: summing size × price without splitting by outcome would mix a 0.85 Yes price with a 0.15 No price.
If your code or a tutorial still calls the old routes, this is what it gets now:
$ curl "https://data-api.polymarket.com/trades?limit=1"
{"error":"Data API v1 has been retired","migration":"https://data-api.polymarket.com/v2/","docs":"https://docs.polymarket.com/api-reference/data-api/overview"}
That response came with HTTP status 410 on 11 November 2026. Polymarket's v1 to v2 migration guide maps each old route: /positions and /closed-positions fold into /v2/positions with a status filter, /trades becomes /v2/trades, and responses switch to snake_case.
Example 3: stream the order book over the WebSocket (Python)#
The market channel is public. Polymarket's market channel reference says to send a subscription with assets_ids and type: "market", and to send the text PING every 10 seconds to keep the connection open.
import asyncio
import json
import websockets
URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
YES_ID = "111662129652828266889360374468657435151479595863413519836161861837506140152890"
async def heartbeat(ws) -> None:
while True:
await asyncio.sleep(10)
await ws.send("PING")
async def main() -> None:
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({"type": "market", "assets_ids": [YES_ID]}))
ping = asyncio.create_task(heartbeat(ws))
updates = 0
async for raw in ws:
if raw == "PONG":
continue
msg = json.loads(raw)
for event in msg if isinstance(msg, list) else [msg]:
if event["event_type"] == "book":
print("book:", event["bids"][-1], event["asks"][-1])
elif event["event_type"] == "price_change":
for c in event["price_changes"]:
if c["asset_id"] != YES_ID:
continue
print(f'{c["side"]} {c["price"]} now {c["size"]}, '
f'best {c["best_bid"]}/{c["best_ask"]}')
updates += 1
if updates >= 3:
break
ping.cancel()
asyncio.run(main())
Output at 10:08 UTC on 11 November 2026:
book: {'price': '0.85', 'size': '236504.5'} {'price': '0.86', 'size': '29073.64'}
BUY 0.85 now 216336.18, best 0.85/0.86
BUY 0.84 now 17202.55, best 0.85/0.86
BUY 0.85 now 236504.5, best 0.85/0.86
The first message is a full book snapshot; after that, price_change messages give the new total size at a price level, not the change. The asset_id filter matters. We subscribed to the Yes token only, yet the price_change messages we received also carried entries for the No token, mirrored: a No bid at 0.13 arrived beside a Yes offer at 0.87. Without the filter, a script mixes the two books.
How do I get a Polymarket API key?#
The CLOB trading credentials on international Polymarket come from no settings form: your wallet creates them, in two layers that Polymarket's API overview describes:
- L1, wallet signature. Sign an EIP-712
ClobAuthmessage on Polygon (chain ID 137) with the wallet's private key, then callPOST /auth/api-keyto create credentials, orGET /auth/derive-api-keyto get back ones you already made. - L2, request signature. The response holds an
apiKey, asecretand apassphrase. For each private request, signtimestamp + method + path + bodywith HMAC-SHA256 using the secret, and send fivePOLY_*headers: address, signature, timestamp, API key and passphrase. - Orders are signed twice. L2 authenticates the request, and the wallet also signs the order itself.
The SDKs do all three for you. We did not create credentials or trade for this guide. Before sending orders, check where they are allowed: Polymarket's geographic restrictions page makes the United States, the United Kingdom and other countries close-only on both the website and the API, and GET https://polymarket.com/api/geoblock tells your app whether the caller's IP can place orders. Whether you can use Polymarket at all is in is Polymarket legal.
Builder and Relayer keys are a separate kind, and those do come from a form. Apps that route orders for their users can join Polymarket's Builder Program for order attribution, builder fees and gas-free wallet transactions. Its builder tiers page says to create a builder profile in Polymarket's settings and click "+ Create New" to generate API keys, with a Relayer API key for gas-free wallet operations. New builders start unverified, with 100 relayer transactions a day, and can move up to higher tiers.
What are the Polymarket API rate limits?#
Selected IP limits from Polymarket's rate limits page, as of 11 November 2026:
| Endpoint | Limit |
|---|---|
| All APIs, general | 15,000 requests per 10 seconds |
| Gamma, general | 4,000 per 10 s |
Gamma /events | 500 per 10 s |
Gamma /markets | 300 per 10 s |
| Data API v2, general | 800 per 10 s |
Data API /v2/trades | 300 per 10 s |
CLOB /book, /price, /midpoint | 1,500 per 10 s each |
CLOB POST /order | 5,000 per 10 s burst; 120,000 per 10 minutes |
Orders and cancels also draw on token buckets per signer address. Polymarket's trading rate limits page sets the Standard tier at 40 orders a second with a burst of 60, rising with 30-day volume to 525 a second at the Diamond tier for 5 million dollars or more. The page says the limiter would first run for two weeks in warning mode from 24 July 2026, marking requests it would later reject with a Poly-RateLimit-Warning: true header, and that Polymarket would announce live enforcement. It still said that on 11 November 2026, so treat the header as a rejection to come.
Polymarket SDKs#
Polymarket now ships one unified SDK per language, covering Gamma, CLOB, Data, the WebSockets and wallet setup:
| Language | Package | Notes |
|---|---|---|
| Python | polymarket-client | Sync and async clients; pandas, Polars and Arrow exports |
| TypeScript | @polymarket/client | Signer adapters for Viem, Privy and Ethers v5 |
| Rust | In development | A migration guide covers the older Rust client |
Polymarket's SDK migration guide tells users of the older py-clob-client-v2, @polymarket/clob-client-v2 and the builder relayer packages to move. The original py-clob-client, version 0.34.6, still answered a midpoint request for us on 11 November 2026. This is the unified Python SDK, version 0.18.0, on the same market, run in the same environment as the examples above (the SDK requires websockets below 16, which is why we used 15.0.1):
from polymarket import PublicClient
with PublicClient() as client:
market = client.get_market(
slug="will-the-fed-increase-interest-rates-by-25-bps-after-the-december-2026-meeting-20260729232808636",
)
yes = market.outcomes.yes
print(market.question)
print(market.version, yes.label, yes.price, yes.token_id)
Will the Fed increase interest rates by 25 bps after the December 2026 meeting?
v1 Yes 0.855 111662129652828266889360374468657435151479595863413519836161861837506140152890
The SDK parses the nested JSON strings for you and returns prices as Decimal. Polymarket's source code is on its GitHub organization.
What is the Polymarket US API?#
Polymarket US is a separate, CFTC-regulated exchange for US customers, and nothing above works on it. Polymarket US's API introduction splits it in two:
- Public API at
https://gateway.polymarket.us: markets, events, series, sports, search, books, best bid and offer, price history. No key. - Authenticated API at
https://api.polymarket.us: orders, portfolio, balances, plus WebSockets for private updates and market data.
The same Fed question on the US exchange, read on 11 November 2026 at 10:10 UTC with no key (output trimmed):
$ curl "https://gateway.polymarket.us/v1/markets/rdc-usfed-fomc-2026-12-09-hike25/bbo"
{"marketData":{"marketSlug":"rdc-usfed-fomc-2026-12-09-hike25",
"currentPx":{"value":"0.8550","currency":"USD"},
"lastTradePx":{"value":"0.8600","currency":"USD"},
"bestAsk":{"value":"0.8600","currency":"USD"},
"bestBid":{"value":"0.8500","currency":"USD"},
"state":"MARKET_STATE_OPEN", ...}}
US markets are addressed by slug, and prices come as objects with a currency. To trade, Polymarket US's authentication page says to open an account in the Polymarket US app, pass identity verification, sign in at the developer portal and create a key. You get a Key ID and a secret shown once. Each request carries X-PM-Access-Key, X-PM-Timestamp and X-PM-Signature, an Ed25519 signature over timestamp, method and path, and the timestamp must be within 30 seconds of the server's. The official SDKs are polymarket-us on both pip and npm. Its rate limits page sets a general limit of 25 requests a second per IP, with tighter limits on combo and RFQ routes.
Firms that connect to the exchange directly use a third interface. Polymarket US's participant REST API overview points them to gRPC streams for live data, and its participant developer page lists a FIX contact and says access needs an application and sandbox testing.
One market, three APIs#
The same question, "25 bps hike at the 9 December 2026 FOMC", read through each venue's public API within one minute at 10:10 UTC on 11 November 2026. Each row is that venue's own market data:
| Source | Best bid | Best ask |
|---|---|---|
| Polymarket CLOB (international) | 0.85 | 0.86 |
| Polymarket US gateway | 0.85 | 0.86 |
Kalshi Trade API, KXFEDDECISION-26DEC-H25 | 0.84 | 0.85 |
A script like this is where cross-venue work starts, and our prediction market arbitrage guide covers what it takes to act on gaps between venues. The Kalshi side of the table comes from our Kalshi API guide, the market itself is covered in our Fed rate decision guide, and what each venue charges is in our Polymarket fees and Kalshi fees guides.
Does the Polymarket API support Combos?#
Yes, on the international API. Polymarket's Combos overview describes a request for quote: a user asks for a price on two or more legs, market makers have 400 milliseconds to quote, and the user has 10 seconds to accept the best one. A public endpoint lists the markets that can be legs. How Polymarket's Combos compare with other platforms' is in our prediction market combos guide.
An API for HIP-4 markets outside the US#
Totalis runs the same kind of Combo RFQ on Hyperliquid's HIP-4 outcome markets: 2 to 5 outcomes, quoted by market makers and held in the Totalis vault on HyperEVM. Its market reads are public too, with curl "https://hip4-api.totalis.trade/v1/markets?limit=3", and the Totalis API overview covers keys and signing. The Combo trading guide walks through the taker flow over the API. Totalis is not available to US persons, and sign-up needs an invite code; the markets are in the Totalis app. For the HyperCore layer underneath, see the Hyperliquid API guide.
Frequently asked questions#
Is the Polymarket API down?#
Polymarket posts uptime and incidents for its APIs on status.polymarket.com. The matching engine also restarts for maintenance, and Polymarket's matching engine page says that for two minutes after a restart only post-only orders are accepted, while cancels keep working.
How do I get Polymarket volume data?#
Gamma's event and market objects carry volume, volume24hr and liquidity fields, and the Data API's trade feed gives each fill. Our prediction market volume page compares venues, and Polymarket alternatives covers where else to trade.
Sources and further reading#
This article is for informational purposes only and is not financial advice. Prediction market positions can lose their entire value. Availability of markets and features depends on your jurisdiction and each platform’s terms.