API · Reference

Quick answer (updated 2026-10-25)

The Polymarket CLOB API lives at https://clob.polymarket.com. Market data (GET /book, /price, /midpoint, /spread, /tick-size, /last-trade-price and their batch versions) needs no key. Trading (POST /order, POST /orders, DELETE /order, /cancel-all) and account reads need CLOB API credentials plus five POLY_* headers, and every order is signed with EIP-712 by your wallet key. Order types are GTC, GTD, FOK and FAK. The current official clients are polymarket-client (Python) and @polymarket/client (TypeScript), both 0.14.0; for Rust, polymarket_client_sdk_v2 (0.8.0 on crates.io). Combo markets trade through a separate RFQ API.

CLOB stands for Central Limit Order Book: the off-chain matching engine where Polymarket orders rest and match before settling on Polygon. This page is a working reference for developers: every CLOB endpoint grouped by purpose, how authentication and order signing work, code to read the book and place an order in Python and TypeScript, the current fee table, and the RFQ flow for combo markets. Polymarkets.co.il is an independent guide, not Polymarket. Everything was checked on 25 October 2026 against Polymarket's developer documentation and its published OpenAPI spec, and the public endpoints were called once to confirm their responses.

What this page answers

  • All CLOB API endpoints, public and authenticated, in two tables
  • How to read the order book and prices (no key needed)
  • How to place and cancel an order over the API
  • How authentication and EIP-712 order signing work in CLOB V2
  • Fees, rate limits, the geoblock endpoint, and RFQ for combo markets

CLOB API endpoints: market data (no auth)

Base URL https://clob.polymarket.com. A token_id is one outcome's ID (the Yes or the No side of a market); a condition_id identifies the whole market. Get both from the Gamma API.

EndpointReturns
GET /book?token_id=Order book: bids, asks, min_order_size, tick_size, neg_risk, last_trade_price, hash, timestamp
GET /books, POST /booksSeveral order books in one call
GET /price?token_id=&side=Best price for one side. Checked live on 25 Oct 2026: side=BUY returned the best bid and side=SELL the best ask, as the API reference states.
GET /prices, POST /pricesBest prices for several tokens and sides
GET /midpoint?token_id=, /midpoints(best bid + best ask) / 2, returned as {"mid": "0.385"}
GET /spread?token_id=, POST /spreadsBest ask minus best bid
GET /last-trade-price?token_id=, /last-trades-pricesLast trade price and side (up to 500 tokens per batch call)
GET /tick-size?token_id=Minimum price increment, e.g. {"minimum_tick_size": 0.01}
GET /neg-risk?token_id=Whether the token belongs to a negative-risk (multi-outcome) market
GET /fee-rate?token_id=The token's base fee rate
GET /clob-markets/{condition_id}All CLOB parameters for one market: tokens, tick size, fees, rewards, RFQ status
GET /markets-by-token/{token_id}The parent market of a token
GET /prices-historyPrice history (the Data API's /v2/prices-history is now the main route)
GET /timeServer Unix time, useful for signing timestamps
GET /builder/trades?builder_code=Trades attributed to a builder code

CLOB API endpoints: trading and account (auth)

EndpointDoes
POST /orderPlace one signed order (orderType GTC, GTD, FOK or FAK; optional postOnly)
POST /ordersPlace 1 to 15 orders; each is accepted or rejected on its own
DELETE /order, DELETE /ordersCancel one order, or up to 1,000 by ID
DELETE /cancel-all, DELETE /cancel-market-ordersCancel everything, or everything in one market or asset (these work even in cancel-only mode)
GET /data/orders, GET /data/order/{id}Your open orders, or one order by ID
GET /data/tradesYour trades
GET /balance-allowance, GET /balance-allowance/updateRead, or refresh, the CLOB's view of your pUSD and outcome-token balances and approvals
POST /auth/api-key, GET /auth/derive-api-keyCreate or re-derive API credentials (L1 signature)
POST /heartbeatsOrder dead man's switch: after the first accepted call, send one every 5 s or open orders get a best-effort cancel after about 10 s
GET /order-scoringWhether an order currently scores for liquidity rewards
GET /notificationsAccount notifications
GET /v1/user/rate-limit-tierYour signer's trading rate-limit tier

Rate limits are per IP through Cloudflare, and over-limit requests are throttled rather than rejected: 9,000 requests per 10 s overall, 1,500 per 10 s for /book, /price and /midpoint, and 5,000 per 10 s burst (120,000 per 10 minutes sustained) for POST /order. Order and cancel requests also draw on per-signer token buckets. Polymarket's docs list a staging host, https://clob-staging.polymarket.com, in the OpenAPI spec.

Read the order book and prices

No key is needed. In the response, bids are sorted ascending and asks descending, so the best bid and best ask are the last entries of each array.

curl -s "https://clob.polymarket.com/book?token_id=$TOKEN_ID"
{
  "market": "0x747dc809...",
  "asset_id": "1075058827...",
  "timestamp": "1782753357257",
  "hash": "a1b2c3d4...",
  "bids": [ { "price": "0.01", "size": "2151131.59" }, "...", { "price": "0.08", "size": "33343.4" } ],
  "asks": [ { "price": "0.99", "size": "218442.27" }, "...", { "price": "0.09", "size": "163939.58" } ],
  "min_order_size": "5",
  "tick_size": "0.01",
  "neg_risk": false,
  "last_trade_price": "0.090"
}

Python (polymarket-client 0.14.0)

# pip install polymarket-client
import asyncio
from polymarket import AsyncPublicClient

async def main():
    async with AsyncPublicClient() as client:
        token = "<token_id>"
        book = await client.get_order_book(asset_id=token)
        print("best bid", book.bids[-1], "best ask", book.asks[-1])
        print("mid", await client.get_midpoint(asset_id=token))

asyncio.run(main())

TypeScript (@polymarket/client 0.14.0)

// npm install @polymarket/client@latest
import { createPublicClient } from "@polymarket/client";

const client = createPublicClient();
const assetId = "<token_id>";
const book = await client.fetchOrderBook({ assetId });
console.log("best bid", book.bids.at(-1), "best ask", book.asks.at(-1));
console.log("mid", await client.fetchMidpoint({ assetId }));

Watch the side convention: Polymarket's SDK docs say fetchPrice / get_price with BUY returns the lowest ask (what a buyer pays), while the raw /price endpoint with side=BUY returned the best bid in our test. When it matters, read the book. For a live stream instead of polling, use the market WebSocket.

Authentication: L1 and L2

Private CLOB calls use two layers, unchanged by the V2 upgrade on 28 April 2026:

  • L1 (wallet signature): your wallet key signs an EIP-712 ClobAuth message (domain ClobAuthDomain, version "1", chain ID 137). You use it once to create or derive API credentials: a key, a secret and a passphrase.
  • L2 (API credentials): each private request carries POLY_ADDRESS, POLY_API_KEY, POLY_PASSPHRASE, POLY_TIMESTAMP and POLY_SIGNATURE, an HMAC-SHA256 of timestamp + method + path + body keyed with the base64-decoded secret, encoded as URL-safe base64.

Placing an order needs both: L2 authenticates the HTTP request, and a separate EIP-712 signature by the wallet authorizes the order itself. The SDKs do all of this for you. Wallet types, signature_type and the funder address are explained in authentication and wallets.

Place and cancel an order via the API

The unified SDKs pick the right exchange contract, signing domain and approvals from the market. A limit order without expiration is GTC; FOK and FAK go through the market-order method. The four order types are compared in detail on the FOK, FAK, GTC and GTD page.

Python

import asyncio, os
from polymarket import AsyncSecureClient

async def main():
    async with await AsyncSecureClient.create(
        private_key=os.environ["POLYMARKET_PRIVATE_KEY"],
        wallet=os.environ["POLYMARKET_WALLET_ADDRESS"],   # account wallet from the profile menu
    ) as client:
        market = await client.get_market(slug="your-market-slug")
        yes = market.outcomes.yes
        asset_id = yes.position_id if market.version == "v2" else yes.token_id

        r = await client.place_limit_order(
            asset_id=asset_id, side="BUY", price="0.52", size="10",   # GTC
        )
        if not r.ok:
            raise RuntimeError(f"{r.code}: {r.message}")
        print(r.order_id, r.status)                 # live, matched or delayed
        await client.cancel_order(order_id=r.order_id)

asyncio.run(main())

TypeScript

import { createSecureClient, OrderSide } from "@polymarket/client";
import { privateKey } from "@polymarket/client/viem";

const client = await createSecureClient({
  wallet: process.env.POLYMARKET_WALLET_ADDRESS,
  signer: privateKey(process.env.POLYMARKET_PRIVATE_KEY),
});
const market = await client.fetchMarket({ slug: "your-market-slug" });
const { yes } = market.outcomes;
const assetId = market.version === "v2" ? yes.positionId : yes.tokenId;

const r = await client.placeLimitOrder({ assetId, side: OrderSide.BUY, price: "0.52", size: "10" });
if (!r.ok) throw new Error(`${r.code}: ${r.message}`);
console.log(r.orderId, r.status);
await client.cancelOrder({ orderId: r.orderId });

Still on py-clob-client-v2 1.2.1 or @polymarket/clob-client-v2 1.2.0? They work but are deprecated in favour of the unified SDK. The V1 packages (py-clob-client 0.34.6, @polymarket/clob-client) have been rejected by the exchange since 28 April 2026. In Rust, the official crate is polymarket_client_sdk_v2 (repository rs-clob-client-v2); crates.io listed 0.8.0 on 25 October 2026, while Polymarket's migration page still shows 0.7.0.

Signing an order without an SDK

Read GET /book first: it tells you which exchange signs the order. The EIP-712 domain name is Polymarket CTF Exchange on chain 137:

Book responseDomain versionverifyingContract
"version": "v2" (Polymarket Protocol V2 market)"3"0xe3333700cA9d93003F00f0F71f8515005F6c00Aa
No version, neg_risk: false"2"0xE111180000d2663C0091e4f400237545B87B996B
No version, neg_risk: true"2"0xe2222d279d744050d28e00520010520000310F59

The signed Order has eleven fields: salt, maker, signer, tokenId, makerAmount, takerAmount, side (0 buy, 1 sell), signatureType, timestamp (milliseconds), metadata and builder. The old nonce, feeRateBps and taker fields are gone. Amounts are six-decimal integers: buying 10 shares at 0.52 means makerAmount 5200000 (USD) and takerAmount 10000000 (shares). Deposit Wallets (signature type 3) sign a wrapped TypedDataSign payload validated through ERC-7739. Then post it:

POST https://clob.polymarket.com/order
{
  "deferExec": false,
  "order": { "salt": 479249096354, "maker": "0x...", "signer": "0x...", "tokenId": "...",
             "makerAmount": "5200000", "takerAmount": "10000000", "side": "BUY",
             "signatureType": 3, "timestamp": "<ms>", "expiration": "0",
             "metadata": "0x00...00", "builder": "0x00...00", "signature": "0x..." },
  "orderType": "GTC",
  "owner": "<your CLOB API key>"
}

A success returns {"success": true, "orderID": "0x...", "status": "live"} (or matched / delayed); a failure returns "success": false with the reason in errorMsg. Every error string and its fix is on the API errors page.

Fees: only takers pay

Makers are never charged. Takers pay a fee set by the protocol at match time, in USDC, that peaks at a 50% price:

fee = shares x feeRate x p x (1 - p)      # p = share price, 0 to 1
Category (fee table, 25 Oct 2026)Taker feeRateMaker rebate share
Crypto0.0720%
Sports0.05 (raised from 0.03 on 10 July 2026)15%
Economics, Culture, Weather, Other0.0525%
Finance, Politics, Mentions, Tech0.0425%
Geopolitics and world events0 (fee-free)None
Combos (any legs)0.050%

Example: 100 shares of a crypto market taken at 0.50 cost 100 x 0.07 x 0.5 x 0.5 = 1.75 USDC in fees; at 0.90 the same trade costs 0.63. Fees round to 5 decimals, so the smallest charge is 0.00001 USDC. For a given market, read feesEnabled and the feeSchedule object rather than hardcoding the category. Maker rebates are paid daily in pUSD; see liquidity rewards.

Negative-risk (multi-outcome) markets

Events with several mutually exclusive outcomes ("who wins the election?") are negative-risk markets: a No share in one outcome can be converted into a Yes share of every other outcome, which makes them capital-efficient. They settle on a different exchange contract, so the book reports neg_risk: true and the signing domain changes (table above). The unified SDKs handle this from the market object; with the legacy clients you must pass neg_risk=True yourself. More in negative-risk execution.

Conditional tokens: split, merge, redeem

Under every market sits the Conditional Token Framework (CTF): outcome shares are ERC-1155 tokens, and a complete Yes plus No set is backed by exactly 1 pUSD. Three operations work outside the order book:

OperationWhat it doesWhy a bot uses it
SplitLock 1 pUSD, receive 1 Yes and 1 NoCreate inventory on both sides without crossing the spread
MergeReturn 1 Yes and 1 No, receive 1 pUSDExit a complete set at once, with no taker fee
RedeemAfter resolution, winning shares pay 1 pUSD eachCash out a resolved position

The match engine uses the same mechanics: depending on the two sides, settlement can move existing shares, split pUSD into new sets, or merge sets back. The Data API reports a wallet's splits, merges and redemptions on /v2/activity, and marks claimable positions redeemable.

The geoblock endpoint: GET https://polymarket.com/api/geoblock

Polymarket documents one location-check endpoint for builders, and it is not on the CLOB host. GET https://polymarket.com/api/geoblock takes no parameters and describes the IP address that made the request:

curl -s https://polymarket.com/api/geoblock
{ "blocked": false, "ip": "203.0.113.42", "country": "XX", "region": "YY" }
FieldTypeMeaning
blockedbooleanWhether this IP may place orders
ipstringThe IP address the server saw
countrystringISO 3166-1 alpha-2 country code
regionstringRegion or state code

The docs recommend calling it before placing orders so an application can show clear feedback; orders the exchange does not accept are rejected regardless. The check applies to the IP your request comes from, so run it from the same server that sends orders.

RFQ and combo markets

Combos bundle outcomes from two or more markets into one Yes/No position. They do not trade on the order book: Polymarket runs a request-for-quote (RFQ) auction between the user asking for a price (the requester) and connected market makers (quoters). The timing, from the docs:

  1. The requester sends an unsigned request for a combo price.
  2. Connected market makers have 400 ms to submit signed quotes.
  3. The best quote goes back to the requester, who has 10 seconds to accept by signing.
  4. If last look is enabled, the chosen maker has 1 second to confirm or decline.
  5. The trade executes and both sides get execution updates.
Endpoint (base https://combos-rfq-api.polymarket.com)AuthDoes
GET /v1/rfq/combo-marketsNoneMarkets usable as combo legs, by volume; limit (default 50, max 100), cursor, exclude. In position_ids, index 0 is Yes and 1 is No.
POST /v1/maker/quotesCLOB L2Submit a signed quote: your own quote_id, rfq_id, signer_address, maker_address, signature_type, price_e6 and size_e6 (six-decimal fixed-point strings, so 0.45 is "450000"), signed_order, optional valid_until in ms
Cancel a quoteCLOB L2Withdraw a quote before it is selected
POST /v1/maker/confirmationsCLOB L2Last look: decision is CONFIRM or DECLINE
wss://combos-rfq-gateway-quoter.polymarket.com/ws/rfqRequiredQuoter gateway: receive requests, quote, confirm, follow execution

An RFQ moves through CREATED, COLLECTING_QUOTES, AWAITING_REQUESTER_ACCEPTANCE, AWAITING_MAKER_CONFIRMATION, EXECUTING and FILLED, or ends as FAILED, EXPIRED, CANCELED or REJECTED. Combo position IDs are separate from CLOB token IDs: the same market can trade on the book and also serve as a combo leg. Combos pay a 0.05 taker fee rate and earn no maker rebate. This is specialist market-maker work; most bots only need the order book above.

CLOB trading basics for bots

  • Round to the tick. Prices must sit on tick_size (0.01 on most markets, finer on some) or the order is rejected; listen for tick_size_change.
  • Respect the minimum. min_order_size from the book (often 5).
  • Approvals first. The CLOB rejects buys without a pUSD approval and sells without an outcome-token approval; the SDK's setup_trading_approvals() handles them. See not enough balance / allowance.
  • Restarts happen. During a matching-engine restart the CLOB answers 425, then runs two minutes in post-only mode. Back off and retry; cancels keep working.
  • Do not hardcode Yes and No. Map token IDs by the market's outcomes array.
Sources checked on 25 October 2026 (named, not linked): Polymarket docs pages API (getting started), Prices and Order Books, Place Orders, Manage Orders, Order Lifecycle, Fees, Maker Rebates, Rate Limits, CLOB Trading Rate Limits, Geographic Restrictions (endpoint section), Matching Engine Restarts, How Combos Work, Get Combo Markets, Submit a Quote, Confirm or Decline Last Look and the Predictions changelog (all under docs.polymarket.com), the CLOB and Combos RFQ OpenAPI specs, package versions on PyPI, npm and crates.io, and live calls to /book, /price, /midpoint and /tick-size.

Next: Authentication and wallets · WebSocket: stream the book · Data API · Fix a CLOB error.