TOTAL VOLUME:
$150.4b
24H VOL:
$144,832,796
24H TRANSACTIONS:
2,874,375,382
OPEN INTEREST:
$1,741,334,125
469,240
Markets across
36,259
events
MATCHED EVENTS:
3,550
PLATFORM COVERAGE:
5
Polymarket:
38%
VS.
Kalshi:
62%
A developer's map of the Polymarket API: Gamma, CLOB and Data endpoints, authentication, WebSocket feeds, fees and rate limits, checked October 2026.
Jared Polites
Oct 25, 2026

TL;DR
gamma-api.polymarket.com) for events and markets, CLOB (clob.polymarket.com) for order books, prices, and trading, and the Data API (data-api.polymarket.com) for trades, holders, open interest, and leaderboards. The docs' own examples read all of it without an API key./book, and 9,000 per 10 seconds across CLOB. Over-limit requests are throttled (queued) rather than instantly rejected.wss://ws-subscriptions-clob.polymarket.com/ws/market) streams book, price, and trade events and needs a PING every 10 seconds. Prediction market data from the Data API is now on v2 (v1 is frozen), and a separate PolyBolt socket streams crypto, equity and TWAP prices.Ever pulled a Polymarket price, thought you were done, and then found the same market has three different "ids", two price fields, and token ids stored as a string that looks like an array? You are not alone, and it is the single most common reason a first Polymarket script returns garbage. The Polymarket API itself is well documented and genuinely developer-friendly. The trick is knowing which of its three services to call, in which order, and which fields to trust.
This guide goes through all of it: the architecture, the endpoints that matter, how authentication actually works, how to stream live data, how orders are built and rejected, and exactly how fast you can call everything.
It covers the Polymarket API docs at docs.polymarket.com. The docs index I checked has no pages for Polymarket US, so nothing here describes that product. For how the two differ, see our guide to Polymarket in the US.
There is no single "Polymarket API". There are several services, each with its own base URL, and the split follows the lifecycle of a market: discover it, read its live state, trade it, then analyze what happened.
| Service | Base URL | What it serves | Auth to read |
|---|---|---|---|
| Gamma API | `https://gamma-api.polymarket.com` | Events, markets, tags, series, sports, search | None |
| CLOB API | `https://clob.polymarket.com` | Order books, prices, midpoints, spreads, order placement | None for market data, L2 for private calls |
| Data API | `https://data-api.polymarket.com` | Trades, positions, holders, open interest, leaderboards | None |
| Market WebSocket | `wss://ws-subscriptions-clob.polymarket.com/ws/market` | Real-time order book and trade events | None |
| User WebSocket | `wss://ws-subscriptions-clob.polymarket.com/ws/user` | Your own order and trade updates | CLOB credentials |
| Relayer API | See Polymarket's relayer docs | Gasless wallet transactions | Builder or relayer key |
| Bridge API | `https://bridge.polymarket.com` | Funding and withdrawals | None listed |
Polymarket settles on Polygon (chain id 137), and the order book is a central limit order book (CLOB) matched off-chain and settled on-chain. That hybrid design is why price and book reads feel like a normal exchange API while ownership of positions is still verifiable on-chain. For the structural difference between this model and automated market makers, see order book versus AMM.
Polymarket also publishes machine-readable OpenAPI specs for the Gamma, CLOB, and Data APIs and AsyncAPI specs for its WebSocket channels. If you generate client code, generate it from those specs rather than from this guide, since they update first.
The fastest way to avoid wasted work is to match the job to the service.
| If you want to... | Use | Why |
|---|---|---|
| List or search markets and events | Gamma | Metadata, categories, tags, resolution dates |
| Get a current price or midpoint | CLOB | Reads straight from the live book |
| See order book depth | CLOB `/book` | Full bid and ask ladders |
| Chart implied probability over time | Data API `/v2/prices-history` | Time series per outcome token |
| Analyze who holds a market or how big it is | Data API | Holders, open interest, trades |
| Stream ticks in real time | Market WebSocket | Avoids polling entirely |
| Place or cancel orders | CLOB (authenticated) | Signed orders |
A useful rule of thumb: Gamma tells you what exists, CLOB tells you what it costs right now, and the Data API tells you what happened.
Gamma is the entry point for almost every project, because it is where you turn a human-readable slug into the identifiers the other services need. It is public and needs no authentication.
| Endpoint | Purpose |
|---|---|
| `GET /events` | List events (omitting `closed` returns open and closed) |
| `GET /events/{id}` | One event by id |
| `GET /events/slug/{slug}` | One event by slug |
| `GET /events/keyset` | Cursor-paginated events (`closed`, `limit`, `after_cursor`, `tag_id`, `game_id`) |
| `GET /markets` | List markets with extensive filters |
| `GET /markets/{id}` | One market by id |
| `GET /markets/slug/{slug}` | One market by slug |
| `GET /markets/keyset` | Cursor-paginated markets |
| `GET /tags` and `GET /tags/slug/{slug}` | Category tags and related tags |
| `GET /series` | Recurring series (for example weekly markets) |
| `GET /sports` and `GET /sports/market-types` | Sports metadata and valid market types |
| `GET /public-search?q=` | Search across events, tags, and profiles |
An event is a container. A market is a single tradable question inside it. "Who will win the election" is an event; "Will Candidate A win" is one market within it. Multi-outcome events hold many markets, one per candidate or threshold.
This matters for aggregation. If you want one row per tradable contract, query markets. If you want one row per topic, query events and read the nested markets.
Use the keyset endpoints for anything that walks a large set. GET /events/keyset and GET /markets/keyset return a next_cursor. You pass it back as after_cursor and stop when no next_cursor comes back. Keyset endpoints default to 20 results and cap at 100 per page, and they reject offset with an HTTP 422. Cursor pagination avoids the skipped and duplicated rows that offset pagination produces when markets are created while you are scanning.
The classic GET /markets supports limit and offset plus a long list of filters, including volume_num_min, volume_num_max, liquidity_num_min, end_date_min, end_date_max, tag_id, slug, condition_ids, and closed.
One filter behavior is worth knowing before you hit it. On /markets, passing closed=any removes the open or closed filter, but then requires at least one identifier (id, slug, clob_token_ids, condition_ids, and similar, up to 100 values each). closed=any with only a tag_id returns an HTTP 422. On /events, closed=any needs no identifier.
A market object has well over a hundred fields. These are the ones that matter for most data work:
| Field | What it is | Gotcha |
|---|---|---|
| `id` | Gamma's internal market id | Not the same as `conditionId` |
| `conditionId` | On-chain condition identifier | Key for the Data API and positions |
| `slug` | URL slug | Stable way to look up a market |
| `question` | The market title | |
| `outcomes` | Outcome labels, e.g. Yes and No | JSON-encoded string, parse it |
| `outcomePrices` | Price per outcome | JSON-encoded string, index-matched to `outcomes` |
| `clobTokenIds` | Token ids for each outcome | JSON-encoded string, used for v1 markets |
| `positionIds` | Position ids for each outcome | A real array, used for v2 markets |
| `version` | `v1` or `v2` | Decides which id field to use |
| `enableOrderBook` | Whether a CLOB book exists | Some markets are not CLOB-tradable |
| `acceptingOrders` | Whether the book is open | Distinct from `active` |
| `orderPriceMinTickSize` | Minimum price increment | Orders must respect it |
| `orderMinSize` | Minimum order size | |
| `volume`, `volume24hr`, `volume1wk`, `volume1mo`, `volume1yr` | Volume windows | `volumeClob` variants isolate order-book volume |
| `liquidity`, `liquidityClob` | Liquidity measures | |
| `bestBid`, `bestAsk`, `spread`, `lastTradePrice` | Top-of-book snapshot | Convenient, but can lag the live book |
| `negRiskOther` | Neg-risk membership flag at market level | `negRisk` itself lives on the event |
| `feesEnabled`, `feeSchedule` | Fee configuration | See the fees section below |
| `closed`, `archived`, `active` | Lifecycle flags | `closed` means resolved or closed to trading |
The two gotchas that break most first scripts are in that table. First, outcomes, outcomePrices, and clobTokenIds arrive as JSON-encoded strings, so you have to parse them before indexing. Second, entries at the same index across outcomes and outcomePrices describe the same outcome, and for binary markets index 0 is Yes and index 1 is No.
Always branch on version. Both clobTokenIds and positionIds can be populated on a single market, so the presence of a field does not tell you which protocol applies. Use v1 with clobTokenIds and v2 with positionIds, and treat anything else as unsupported.
The CLOB API serves live market state and needs no credentials for reads. Everything is keyed by outcome token, not by market, because each outcome trades as its own asset.
| Method | Path | Input | Returns |
|---|---|---|---|
| GET | `/book` | `token_id` | Order book for one outcome |
| POST | `/books` | Array of `{"token_id": ...}` | Array of order books |
| GET | `/price` | `token_id`, `side` (`BUY` or `SELL`) | `{"price": ...}` |
| POST | `/prices` | Array of `{"token_id", "side"}` | Map of token to side to price |
| GET | `/midpoint` | `token_id` | `{"mid": ...}` |
| POST | `/midpoints` | Array of token ids | Map of token to midpoint |
| GET | `/spread` | `token_id` | `{"spread": ...}` |
| POST | `/spreads` | Array of token ids | Map of token to spread |
| GET | `/last-trade-price` | `token_id` | `{"price", "side"}` |
| POST | `/last-trades-prices` | Array of token ids | Array of `{"token_id", "price", "side"}` |
The batch endpoints (/books, /prices, /midpoints, /spreads, /last-trades-prices) accept up to 500 items per request. If you are tracking a few hundred outcomes, batching turns hundreds of calls into one and keeps you far from the rate limits covered below.
A /book response includes market (the condition id), asset_id, timestamp, hash, bids, asks, min_order_size, tick_size, neg_risk, and last_trade_price. Each ladder entry is a price and a size.
Read the ordering carefully. Bids are sorted ascending and asks descending, which means the best bid and best ask are the last entries in their arrays, not the first. A lot of homemade "best price" code takes index 0 and reports the worst quote on the book.
Two edge cases are worth handling. For a token that has never traded and has an empty book, /last-trade-price returns "0.5" with an empty side. And /last-trades-prices simply omits tokens that have never traded, so a missing key is not an error.
Each outcome token pays $1 if that outcome resolves true and $0 otherwise. A Yes token priced at $0.63 implies a 63% market probability. For a binary market, the Yes and No prices should sum to roughly $1, with any gap reflecting spread. If you are building a probability series, the midpoint is usually a better estimate than the last trade price on thin markets. For the broader pitfalls of treating a price as a clean probability, see why prediction market odds disagree.
Price history is documented on the Data API as GET /v2/prices-history. Polymarket's market-data docs describe this version, and the CLOB /prices-history endpoint still appears in the rate-limit table, so check the CLOB reference before relying on the older one.
The v2 endpoint takes these parameters:
| Parameter | Meaning |
|---|---|
| `token_id` | The outcome token to query |
| `interval` | A relative window, for example `1d` or `max` |
| `start` and `end` | An absolute range in epoch seconds, at most 15 days |
| `as_of` | A single point in time |
| `bucket_seconds` | Bucket size, from 60 to 86,400 |
| `cursor` | Fetches later pages |
Use exactly one of interval, start with an optional end, or as_of. The minimum bucket is 600 seconds for max and 1m, and 300 seconds for 1w. The docs show 1d and max in examples and do not give a complete list of interval values, so test the ones you need.
The response puts the points in data, oldest first, each with a timestamp, a price and a resolution_seconds. A pagination object carries has_more and next_cursor.
The identifier is the outcome token, not the condition id or the slug, and passing the wrong one is the most common cause of an empty history. For a full workflow that uses this series, see how to backtest a prediction market strategy.
The Data API covers what happens around a market after discovery and trading. Version 2 launched on September 4, 2026 at https://data-api.polymarket.com/v2, and v1 is frozen. The v2 surface has 21 GET paths and an OpenAPI document at data-api.polymarket.com/v2/openapi.json.
| Path | What you get |
|---|---|
| `/v2/trades` | Trades for a market, event or user, with `taker_only` defaulting to true |
| `/v2/oi` | Open interest, or a global figure if you omit `condition` |
| `/v2/holders` | Top holders per outcome token |
| `/v2/live-volume` | Live volume for an event |
| `/v2/resolutions` | Resolution state, including expected settlement time |
| `/v2/tokens` | Reverse lookup from token ids to conditions (added October 7, 2026) |
| `/v2/leaderboard` | Trader ranking by profit and loss or volume |
| `/v2/biggest-winners` | The biggest single wins |
| `/v2/positions`, `/v2/activity` | Positions and account activity |
| `/v2/prices-history` | Price series for a token |
| `/v2/user-pnl`, `/v2/user-stats`, `/v2/user-volume`, `/v2/value` | Per-user series and portfolio figures |
| `/v2/approvals`, `/v2/status` | Wallet approvals and data freshness |
Every successful response wraps its payload in data. Paginated routes add a pagination object with next_cursor and has_more, and you follow the cursor until it is null. Passing an offset returns a 400. Errors return JSON with error, code, retryable and trace_id, and the trace id is also echoed in the x-trace-id header.
Several limits are worth knowing. /v2/trades and /v2/leaderboard return up to 1,000 rows on the first page. /v2/trades accepts up to 20 values each for condition and event_id. /v2/tokens accepts up to 50 token_id values or up to 10 condition values per call. /v2/holders returns 100 rows per outcome token by default, up to 1,000.
/v2/tokens solves the "I have a token id from a trade, which market is it" problem. Holder data has one subtle behavior: the page size applies per outcome token, not across the whole market, so merge results by asset id rather than assuming one flat list.
The leaderboard route takes time_period (day by default, week, month or all), a category (overall by default), and sort_by (PNL by default, or VOLUME). It is the same ranking you see on Polymarket's website. For what that ranking shows, what it leaves out, and where else to find prediction market data, see our guide to the sources.
Reading is open. Trading is not. Private CLOB calls use two layers, and understanding why there are two makes the flow much easier to implement.
L1 proves you control a wallet. You sign an EIP-712 typed message with your private key, and Polymarket returns a set of API credentials. L2 uses those credentials to sign every subsequent private request with HMAC-SHA256, so your private key is not needed on every call.
The L1 typed data uses the domain ClobAuthDomain, version 1, chain id 137, with a ClobAuth struct holding address, timestamp, nonce, and a fixed message: "This message attests that I control the given wallet". The nonce is 0 unless you manage multiple credential sets for one wallet.
L1 requests carry four headers: POLY_ADDRESS, POLY_SIGNATURE, POLY_TIMESTAMP, and POLY_NONCE.
| Step | Call | Notes |
|---|---|---|
| Create credentials | `POST /auth/api-key` | Use when none exist yet for that address and nonce |
| Derive credentials | `GET /auth/derive-api-key` | Use when they already exist |
Either call returns apiKey, secret, and passphrase. The secret is base64-encoded and is the HMAC key.
For each private request:
secret, use it as the key, and compute HMAC-SHA256 over the message.| Header | Value |
|---|---|
| `POLY_ADDRESS` | Your Polygon signer address |
| `POLY_SIGNATURE` | The HMAC signature |
| `POLY_TIMESTAMP` | The timestamp you signed |
| `POLY_API_KEY` | Your `apiKey` |
| `POLY_PASSPHRASE` | Your `passphrase` |
A 401 on a private call usually means a bad signature, an expired timestamp, or the wrong credentials. Use the SDK's signing helpers where you can, and keep your system clock accurate.
The signer is the key that controls the account. The account wallet is the address that actually holds funds and positions. They are not always the same address.
| Wallet type | Value | Notes |
|---|---|---|
| EOA | 0 | A plain externally owned account |
| Proxy wallet | 1 | Legacy smart wallet from Magic Link or Google sign-in |
| Safe wallet | 2 | Legacy smart wallet created with an external signer such as MetaMask |
| Deposit wallet | 3 | The default smart wallet for accounts deployed on or after May 4, 2026 |
A deposit wallet owner can give a separate signer scoped, time-limited trading access through session keys. That is the safer pattern for bots: a scoped, expiring key instead of your main private key sitting on a server.
If your account predates the deposit wallet, you are probably on a proxy or Safe wallet, which changes how you configure the signer and funder. Check which type your account is before assuming the defaults apply.
Trading collateral on Polymarket is pUSD, deployed on Polygon at 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB, according to Polymarket's contracts page. Older guides, and many third-party tutorials, still say USDC, so treat any code that hardcodes a collateral address from before 2026 with suspicion.
Polymarket's quickstart asks for at least 10 pUSD in an account created or funded at polymarket.com before you place a first order. A market order then fills against available liquidity and any unfilled amount is canceled instead of resting. The trade settles on-chain asynchronously, and the SDK offers a helper to wait for that settlement.
| Contract | Address |
|---|---|
| pUSD collateral token | `0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB` |
| CTF Exchange | `0xE111180000d2663C0091e4f400237545B87B996B` |
| Neg Risk CTF Exchange | `0xe2222d279d744050d28e00520010520000310F59` |
| ExchangeV3 (Poly v2) | `0xe3333700cA9d93003F00f0F71f8515005F6c00Aa` |
| PositionManager | `0x006F54F7f9A22e0000CC2AB60031000000ae9fEF` |
| Conditional Tokens | `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` |
Everything is on Polygon mainnet (chain id 137). The contracts page lists many more, including the collateral adapters, and notes that approving the collateral adapter does not grant the Neg Risk Adapter approvals.
Trading needs token approvals before an account can place orders, and the Data API exposes a /v2/approvals route to read a wallet's current approvals. Use the SDK's setup helpers instead of hand-writing approval transactions, and always confirm addresses against the official contracts page before sending real funds. They are listed here so you know what to look for, not as a substitute for checking.
An order is built, signed by the wallet, wrapped in an L2-authenticated request, and posted. The SDK does the construction for you, but the rules it enforces are worth knowing because they are the source of nearly every rejection.
| Type | Behavior |
|---|---|
| GTC (Good Till Cancelled) | Limit order that rests until filled or cancelled |
| GTD (Good Till Date) | Limit order that rests until a Unix expiration timestamp you set |
| FAK (Fill and Kill) | Market order that fills what it can immediately and cancels the rest (the SDK default) |
| FOK (Fill or Kill) | Market order that must fill entirely or not at all |
Market orders never rest on the book.
A post-only order, set with postOnly: true on a GTC or GTD order, is rejected if it would match immediately instead of resting. This is how market makers guarantee they never accidentally pay taker fees.
"0". Market orders must also use "0".now + 60 + N.tick_size_change on the market WebSocket rather than caching it forever.min_order_size (the example book shows 5 shares), and your rounded share quantity must meet it.| Tick size | Price decimals | Size decimals | Amount decimals |
|---|---|---|---|
| 0.1 | 1 | 2 | 3 |
| 0.01 | 2 | 2 | 4 |
| 0.005 | 3 | 2 | 5 |
| 0.0025 | 4 | 2 | 6 |
| 0.001 | 3 | 2 | 5 |
| 0.0001 | 4 | 2 | 6 |
The batch endpoint POST /orders accepts 1 to 15 signed orders per call. A rejected order returns ok: false in the SDK or success: false in the API, with a message explaining why. Cancellation batches are separate: Polymarket's changelog says DELETE /orders accepts up to 1,000 order ids per request since June 15, 2026.
Since July 24, 2026, POST /order and POST /orders return tradeIDs instead of transactionHashes on FAK and FOK matches, according to Polymarket's changelog. A successful limit order returns one of these statuses:
| Status | Meaning |
|---|---|
| `live` | Resting on the book |
| `matched` | Filled against resting liquidity |
| `delayed` | Marketable but subject to a matching delay |
| `unmatched` | Marketable but failed to delay, placement still succeeded (API only) |
Errors come back with an error field, plus HTTP codes that are worth building retry logic around:
| Code | Meaning | What to do |
|---|---|---|
| 400 | Invalid parameters, malformed payload, or a rule violation | Fix the request, do not retry blindly |
| 401 | Missing or invalid key, bad signature, or expired timestamp | Re-check signing and clock |
| 404 | Market, order, or outcome id not found | Check the identifier |
| 425 | Matching engine restarting | Retry with exponential backoff |
| 429 | Rate limit hit | Back off exponentially |
| 500 | Server error, including `order timed out` | Retry with backoff, a timed-out order never reached the book |
| 503 | Trading paused, or cancel-only or post-only mode | Wait, honor `Retry-After` |
The common business-rule rejections are not enough balance / allowance, invalid post-only order: order crosses book, tick-size and minimum-size violations, and order couldn't be fully filled. FOK orders are fully filled or killed. A 503 in post-only mode includes retry_after_seconds and a matching Retry-After header.
If you run a market-making bot, the heartbeat matters more than almost anything else on this page. You send a heartbeat every 5 seconds to POST /v1/heartbeats, starting with an empty heartbeat_id and then echoing back the latest id each time. If a valid heartbeat is not received within 10 seconds, all open orders owned by those credentials are cancelled. The check runs every 5 seconds, so cancellation can land up to 5 seconds after the timeout.
This is a deliberate safety feature. It means a crashed or disconnected bot cannot leave stale quotes resting on the book. It also means a bot that forgets the heartbeat will mysteriously lose all its orders.
Polymarket charges takers a category-based fee and never charges makers. The fee is computed at match time, so orders themselves carry no fee field.
The formula is fee = C × feeRate × p × (1 - p), where C is shares traded and p is the share price. That curve peaks at a 50-cent price and shrinks toward the extremes.
| Category | Taker rate | Maker rate | Maker rebate |
|---|---|---|---|
| Crypto | 0.07 | 0 | 20% |
| Sports | 0.05 | 0 | 15% |
| Finance | 0.04 | 0 | 25% |
| Politics | 0.04 | 0 | 25% |
| Economics | 0.05 | 0 | 25% |
| Culture | 0.05 | 0 | 25% |
| Weather | 0.05 | 0 | 25% |
| Mentions | 0.04 | 0 | 25% |
| Tech | 0.04 | 0 | 25% |
| Other / general | 0.05 | 0 | 25% |
| Geopolitics | 0 | 0 | none |
| Combos | 0.05 | 0 | 0% |
For 100 shares at $0.50 the taker fee peaks at $1.75 in Crypto, $1.25 in Sports, Economics, Culture, Weather, Combos and the general category, and $1.00 in Finance, Politics, Mentions, and Tech. Geopolitical and world events markets are fee-free, and makers are never charged. On the market object, feesEnabled says whether fees apply, and feeSchedule carries rate, exponent, takerOnly, and rebateRate.
Fee schedules have been revised more than once in 2026, so read them from the market object at trade time instead of hardcoding the table. For the business context behind these numbers, see how Polymarket makes money, and for a cross-platform view, prediction market fees compared.
Polling is the slow way to build a live dashboard. The market channel pushes changes as they happen, and it is public.
The endpoint is wss://ws-subscriptions-clob.polymarket.com/ws/market. After connecting, send a subscription frame with type set to market and your outcome token ids in an assets_ids array. You can add or remove ids later with an operation field set to subscribe or unsubscribe, with no reconnect.
| Event | Fires when |
|---|---|
| `book` | A full book snapshot is sent |
| `price_change` | Orders are placed or cancelled |
| `last_trade_price` | A trade executes |
| `tick_size_change` | A market's tick size changes |
| `best_bid_ask` | Top of book changes (with `custom_feature_enabled`) |
| `new_market` | A market is created (with `custom_feature_enabled`) |
| `market_resolved` | A market resolves (with `custom_feature_enabled`) |
Set custom_feature_enabled to true to unlock the top-of-book and market lifecycle events. The new_market and market_resolved events are especially useful for aggregators, because they let you discover and close out markets without polling Gamma.
The connection needs a keepalive: send the text frame PING every 10 seconds and expect PONG back. Miss it and the server drops you.
The user channel, wss://ws-subscriptions-clob.polymarket.com/ws/user, takes your CLOB credentials in the subscription frame and optionally a markets array of condition ids. Keep those credentials on a server, never in browser code.
Order events carry a type of PLACEMENT, UPDATE, or CANCELLATION. Trade events carry a status that tracks settlement, with values such as MATCHED, MINED, CONFIRMED, RETRYING and FAILED.
Treat only CONFIRMED as settled. The stream does not replay missed events, so after any reconnect, fetch open orders and recent trades over REST first, then apply new stream events on top of that state.
Polymarket added a separate socket on September 15, 2026 for reference prices at wss://ws-live-v2.polymarket.com/ws. It serves the price.crypto, price.equity and price.crypto.twap channels, and its channels require authentication: you send an auth message with API credentials, wait for an authed reply, then subscribe with a channel and a symbol filter.
PolyBolt replaces the older RTDS feed for reference prices, which Polymarket now marks as legacy. Live comment and reaction streaming is being retired without a replacement stream, and the docs point to GET /comments on Gamma as a fetch-only alternative. If you use RTDS today, follow Polymarket's RTDS-to-PolyBolt migration guide.
Limits are published, IP-based, and enforced by Cloudflare on sliding windows. When you exceed a limit, requests are throttled (delayed or queued) rather than rejected outright, though a 429 can still occur and should trigger exponential backoff. Trading endpoints also have separate per-signer token-bucket limits.
| API | Endpoint | Limit |
|---|---|---|
| General | All endpoints | 15,000 requests per 10 seconds |
| Gamma | General | 4,000 per 10s |
| Gamma | `/events` | 500 per 10s |
| Gamma | `/markets` | 300 per 10s |
| Gamma | `/markets` and `/events` listing | 900 per 10s |
| Gamma | `/public-search` | 350 per 10s |
| Gamma | `/tags` and `/comments` | 200 per 10s each |
| Data API v1 | General | 1,000 per 10s |
| Data API v1 | `/trades` | 200 per 10s |
| Data API v1 | `/positions`, `/closed-positions` | 150 per 10s each |
| Data API v2 | General | 800 per 10s |
| Data API v2 | `/v2/trades` | 300 per 10s |
| Data API v2 | `/v2/positions`, `/v2/activity`, `/v2/prices-history` | 200 per 10s each |
| CLOB | General | 9,000 per 10s |
| CLOB | `/book`, `/price`, `/midpoint` | 1,500 per 10s each |
| CLOB | `/books`, `/prices`, `/midpoints` | 500 per 10s each |
| CLOB | `/prices-history` | 1,000 per 10s |
| CLOB | API key endpoints | 100 per 10s |
| Bridge | General | 50 per 10s |
| Relayer | `/submit` | 25 per minute |
Trading endpoints carry both burst and sustained ceilings:
| Endpoint | Burst | Sustained |
|---|---|---|
| `POST /order` | 5,000 per 10s | 120,000 per 10 minutes |
| `DELETE /order` | 5,000 per 10s | 120,000 per 10 minutes |
| `POST /orders` | 2,000 per 10s | 21,000 per 10 minutes |
| `DELETE /orders` | 2,000 per 10s | 15,000 per 10 minutes |
| `DELETE /cancel-all` | 250 per 10s | 6,000 per 10 minutes |
| `DELETE /cancel-market-orders` | 1,500 per 10s | 21,000 per 10 minutes |
Three habits cover almost every case:
/books, /prices, and /midpoints (500 tokens per call) instead of looping single-token calls./book requests.Note that these ceilings are shared per IP. If several workers run from one machine or one cloud NAT address, they draw from the same budget.
Polymarket now documents a unified SDK in place of its older split clients. The TypeScript package is @polymarket/client, and the Python package is polymarket-client. A unified Rust SDK is in development, and the docs point to a Rust migration guide for now.
If you find a tutorial importing py-clob-client or @polymarket/clob-client, treat it as pre-V2. Polymarket's changelog says V1 SDKs and orders stopped being supported when CLOB V2 went live on April 28, 2026.
In Python, reads and authenticated trading use separate clients:
AsyncSecureClient.create(private_key=...) builds an authenticated client from a private key and your wallet address. The key should come from an environment variable and never be hardcoded.The quickstart's TypeScript example branches on market.version to pick positionId for v2 markets or tokenId otherwise, which matches the version rule above.
A typical read-only pipeline has four steps, and none of them need a key:
GET /markets/slug/{slug} on Gamma and read the market object.outcomes, outcomePrices, and the id field that matches version (clobTokenIds for v1, positionIds for v2)./midpoint or /book to get a live price, and with Data API /v2/prices-history for the series.conditionId against the Data API for trades, holders, and open interest.Gamma's outcomePrices is convenient but is a snapshot. For anything that drives a decision, such as an alert or an arbitrage signal, read the live book instead, and stream it if latency matters. For a hands-on view of monitoring prices without writing code, see how to track prediction market prices.
Everything above applies to Polymarket only. The moment you want to compare odds across venues, you inherit a normalization problem, because every platform defines its own identifiers, price formats, fee models, and resolution rules.
Kalshi, a CFTC-regulated exchange, runs its own REST API with its own schema and authentication, which our Kalshi API guide covers. Limitless, Predict.Fun, and Opinion each have their own contract structures and API conventions. None of the five normalize against each other, so the same real-world event can appear as different markets with different price formats and different resolution criteria. Matching them correctly is a real engineering task, not a join on a title string, which is part of why prediction market arbitrage is harder to execute than to describe.
This is the gap PredictionHero's aggregation layer is built for. It lines up prediction market data from Polymarket, Kalshi, Limitless, Predict.Fun, and Opinion and normalizes the output, so you compare the same event on the same terms instead of reconciling five API responses by hand.
If you are building your own integration anyway, Polymarket is a natural first venue to wire up, because its public read access is open and its documentation is thorough. Read each other platform's API documentation before you commit to a shared schema, and design your internal model around outcomes and probabilities, not around any one venue's field names. If you are new to the platform itself, start with what Polymarket is.
A short pre-flight list catches most production problems:
outcomes, outcomePrices, and clobTokenIds as JSON strings, and branch on version.min_order_size, and listen for tick_size_change.CONFIRMED trades as settled, and reconcile over REST after every WebSocket reconnect.No. Polymarket's documented examples read the Gamma API, the public CLOB market-data endpoints, the Data API, and the market WebSocket without credentials. You only need API credentials, generated by signing a message with your wallet, to place or cancel orders or to read your own private order data.
Gamma serves market and event metadata such as titles, slugs, tags, and resolution dates. The CLOB API serves live prices, order books, and order execution. The Data API serves trades, holders, open interest, and leaderboards, which together make up the prediction market data behind the site. Most read-only projects start with Gamma.
Yes. The current package is polymarket-client, which includes a public client for key-free reads and a secure client for authenticated trading. Older tutorials use py-clob-client, which Polymarket's changelog says stopped being supported when CLOB V2 went live, so method names may differ.
Call GET /v2/prices-history on the Data API with the outcome token_id and exactly one of interval, start with end, or as_of. Points come back oldest first with a timestamp and price, and you page with next_cursor. Absolute ranges are limited to 15 days per request.
They are published and IP-based, enforced by Cloudflare. Examples include 4,000 requests per 10 seconds across Gamma, 1,500 per 10 seconds on the CLOB /book endpoint, and 9,000 per 10 seconds across the CLOB overall. Excess requests are generally throttled rather than rejected, but you should still back off on a 429.
It uses two layers. L1 is an EIP-712 wallet signature that creates or derives your API key, secret, and passphrase. L2 is an HMAC-SHA256 signature over the timestamp, method, path, and body that you attach to every private request through the POLY_* headers.
Polymarket uses pUSD on Polygon as its trading collateral, at 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB on the contracts page. Polymarket's quickstart asks for at least 10 pUSD before a first order. Many older guides still reference USDC, so check the current contracts and wallet documentation before funding an account or hardcoding an address.
Yes. Connect to the market WebSocket, subscribe with the outcome token ids you want, and send a PING every 10 seconds to keep the connection alive. The feed emits book snapshots, price changes, last-trade prices, and tick-size changes, with optional market creation and resolution events.
PredictionHero aggregates publicly available prediction market data for informational purposes only. This is not financial advice. Prediction markets may not be available in all jurisdictions.
Related

How the Kalshi API works: keyless market data, signed trading, REST and WebSocket feeds, historical candlesticks, and the real rate-limit tiers.
Jared Polites · Oct 18

Where to find prediction market data, from platform APIs to the Polymarket leaderboard, what each source costs, and how to use it without being misled.
Jared Polites · Oct 22

Tracking a market means watching last price, mid price, volume, and timestamp together, across every platform that lists it. Here's what each of the five major platforms actually shows you.
Jared Polites · Sep 26