Polymarket CLOB API Deep-Dive: Orders, Signing & Rate Limits
A developer reference for the Polymarket CLOB API: EIP-712 order signing, REST and WebSocket endpoints, USDC allowances, and rate-limit handling that survives production.
A Polymarket order isn't a JSON blob you POST and forget. It's an EIP-712 signed struct that the operator relays to an on-chain exchange contract, and every field in that struct is part of the signature. Get one field wrong — a stale nonce, a mismatched tokenId, the wrong signatureType — and the API returns a 400 with a terse message that doesn't tell you which of the twelve fields you botched. This post walks the full path: how orders are built and signed, which endpoints you actually call, how allowances gate everything, and where the rate limiter will bite you.
The two-layer auth model
The CLOB uses two distinct credential systems, and conflating them is the most common early mistake.
The first layer is your Ethereum private key. This signs orders and also signs the message that mints your API credentials. The second layer is an L2 API key — a key, secret, and passphrase triple you derive once via POST /auth/api-key (or deterministically re-derive with POST /auth/derive-api-key). The API key authenticates your REST requests through HMAC headers; the private key authenticates the orders themselves. You never send the private key anywhere. You send order signatures and HMAC headers.
Every authenticated REST call needs these headers:
POLY_ADDRESS: 0xYourWallet
POLY_SIGNATURE: HMAC-SHA256(secret, timestamp + method + path + body)
POLY_TIMESTAMP: unix seconds
POLY_API_KEY: the key uuid
POLY_PASSPHRASE: the passphrase
The HMAC signature is base64url of the digest, and the timestamp is checked server-side with a tolerance window. If your server clock drifts more than a few seconds you'll get rejected, so sync NTP before you blame your code.
Signing an order with EIP-712
Orders are signed against the CTF Exchange contract on Polygon (chain ID 137). The struct the client signs looks like this:
Order {
uint256 salt;
address maker;
address signer;
address taker; // 0x0 for public orders
uint256 tokenId; // the ERC-1155 position id
uint256 makerAmount;
uint256 takerAmount;
uint256 expiration; // unix seconds, 0 = GTC
uint256 nonce;
uint256 feeRateBps;
uint8 side; // 0 buy, 1 sell
uint8 signatureType;
}
makerAmount and takerAmount encode both price and size. For a buy of 100 shares at $0.42, you're offering 42 USDC to receive 100 conditional tokens, so makerAmount = 42_000000 (USDC has 6 decimals) and takerAmount = 100_000000. Price is implied by the ratio, and the CLOB rounds to the tick — usually $0.01, sometimes $0.001 on tighter markets. If your ratio doesn't land cleanly on a tick, the order is rejected before it reaches the book.
signatureType matters more than people expect:
0(EOA) — a plain externally-owned account signs directly.1(POLY_PROXY) — you trade through Polymarket's proxy wallet;makeris the proxy,signeris your EOA.2(POLY_GNOSIS_SAFE) — a Safe-based proxy.
If you funded your account through the Polymarket UI, your USDC almost certainly lives in a proxy, which means signatureType is 1 or 2 and maker != signer. People sign type 0, watch it get accepted, then wonder why the fill never lands — because the balance is in the proxy, not the EOA. Match the signature type to where your funds actually sit.
Most teams don't hand-roll the EIP-712 domain separator. The official py-clob-client and clob-client (TypeScript) build the typed data and manage the salt and nonce for you. Use them. Rewriting the signer to save a dependency is how you spend a Saturday debugging a domain-separator byte order.
Allowances: the setup step that fails silently
Before a single order fills, the exchange contract needs ERC-20 and ERC-1155 approvals. You approve USDC (spending) and the CTF ERC-1155 tokens (setApprovalForAll) for the exchange address. Skip this and orders sign fine, post fine, match fine — then fail at settlement. The book will happily hold an order you can never actually honor.
Do the approvals once per wallet, verify them on-chain with an allowance read, and cache that you've done it. If you're spinning up fresh trading wallets programmatically — say for isolated strategy accounts in a Polymarket market-making setup — bake the approval transaction into your wallet-provisioning flow so no account ever hits the book un-approved. The mechanics of quoting both sides once approvals are in place are covered in our CLOB market-making bot guide.
The endpoints you'll actually use
REST base is https://clob.polymarket.com. The handful that matter:
POST /order— submit a single signed order. Returns an order ID and status (live,matched,delayed).POST /orders— batch submit, up to a capped count per request.DELETE /orderandDELETE /orders— cancel by ID;DELETE /cancel-allnukes everything for the wallet.GET /book?token_id=...— the current order book snapshot.GET /pricesandGET /midpoint— quote helpers.GET /ordersandGET /trades— your open orders and fill history.
Order types are set with the orderType param: GTC rests on the book, FOK fills-or-kills, GTD expires at your expiration, and FAK fills what it can and cancels the rest. For an arbitrage leg you almost always want FOK or FAK — you don't want a half-filled hedge sitting exposed. That distinction is the whole game for a Polymarket arbitrage bot, where an unhedged resting order is a naked directional bet you didn't intend to make.
WebSocket: stop polling the book
There are two WS channels at wss://ws-subscription-address.polymarket.com/ws/:
- Market channel — public book updates, price changes, and last-trade prints for the asset IDs you subscribe to. This is your live book.
- User channel — authenticated with your API key; pushes your own order and trade events (
placement,match,cancellation).
Subscribe to the market channel for the assets you quote and the user channel for fill confirmations, and you eliminate almost all your REST read traffic. Polling /book in a loop is both slow and the fastest way to trip the rate limiter. One caveat: the WS feed can drop or lag under load, so treat it as the fast path and reconcile against a periodic GET /orders snapshot — don't trust the socket as your only source of truth for position state. A copy-trading bot that mirrors another wallet lives almost entirely on the WS market feed, since latency to the fill is the edge.
Rate limits and how to not get throttled
Limits are enforced per API key and per endpoint, not globally, and order placement is stricter than reads. The two mistakes I see:
- Cancel-and-replace storms. Re-quoting by cancelling every order and re-posting on each tick burns through the order-placement budget fast. Cancel only what moved, and batch replacements through
POST /ordersinstead of firing singles. - No backoff. A 429 comes back with a header telling you when the window resets. Honor it. A blind retry loop turns a soft throttle into a hard ban, and Polymarket has cooled off aggressive keys.
Build a token-bucket limiter on your side sized just under the published ceiling, key it per endpoint, and let batch endpoints do the heavy lifting. For a spread strategy that's constantly repricing, the difference between naive singles and batched cancel-replace is the difference between staying quoted and getting rate-limited off the book — which is exactly the plumbing a production Polymarket spread bot has to get right.
One last thing that trips people at settlement rather than order time: resolution. An order that fills perfectly can still lose to a bad oracle outcome, and understanding how UMA's optimistic oracle resolves markets — and where dispute windows create a trading edge — is what separates a bot that places orders from one that actually makes money.
If you're building order flow against the CLOB and want the signing, allowance, and rate-limit layers built right the first time, our Polymarket market-making bot service is where that work lives.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article