Kalshi vs Polymarket API: a side-by-side for bot builders
Three prediction-market APIs, one table: Kalshi, Polymarket US and international Polymarket (the CLOB). Auth, base URLs, demo environments, order types, idempotency, order books, websockets, rate limits, fees and settlement, each checked against the official docs, plus the gotchas our recipe tests hit.
The comparison table
| Kalshi | Polymarket US | Polymarket international (CLOB) | |
|---|---|---|---|
| Who it is for | Kalshi account holders | Polymarket US account holders (identity-verified in the app) | International users with a Polygon wallet; check the geoblock endpoint first |
| REST base URL | https://external-api.kalshi.com/trade-api/v2 | https://api.polymarket.us (authenticated), https://gateway.polymarket.us (public) | CLOB https://clob.polymarket.com, Gamma https://gamma-api.polymarket.com, Data https://data-api.polymarket.com |
| Demo / sandbox | Yes: https://external-api.demo.kalshi.co/trade-api/v2, mock funds, separate keys | No retail sandbox in the docs. POST /v1/order/preview previews an order without placing it | No testnet in the CLOB docs |
| Auth | API key + RSA-PSS (SHA-256) or Ed25519 signature over timestamp + METHOD + path | API key + Ed25519 signature over timestamp + method + path | L1: EIP-712 wallet signature creates API credentials. L2: HMAC-SHA256 headers on every private call. Orders are also wallet-signed |
| Auth headers | KALSHI-ACCESS-KEY, KALSHI-ACCESS-TIMESTAMP, KALSHI-ACCESS-SIGNATURE | X-PM-Access-Key, X-PM-Timestamp, X-PM-Signature | POLY_ADDRESS, POLY_SIGNATURE, POLY_TIMESTAMP, POLY_API_KEY, POLY_PASSPHRASE |
| Order types | Limit only in Create Order V2 (price required) | Limit and market (ORDER_TYPE_LIMIT, ORDER_TYPE_MARKET) | Limit (GTC, GTD) and market (FAK, FOK) |
| Time in force | fill_or_kill, good_till_canceled, immediate_or_cancel; GTC + expiration_time for expiring orders | DAY, GTC, GTD, IOC, FOK | GTC, GTD, FAK, FOK |
| Post-only | post_only | participateDontInitiate | postOnly with GTC or GTD |
| Client order ID | Yes: client_order_id. Duplicates are rejected, so resubmitting with the same ID is safe | No client order ID on retail Create Order; the exchange assigns id | No client order ID; each signed order carries a random salt |
| Order book shape | Bids only: yes_dollars and no_dollars, ascending. Best bid is the last element. YES ask = 1 − best NO bid | bids and offers with px/qty. Docs don't state the order; on our live check the best price was first | bids ascending, asks descending. Best bid and best ask are the last elements |
| Websockets | wss://external-api-ws.kalshi.com/trade-api/ws/v2; authenticated handshake, even for public channels | wss://api.polymarket.us/v1/ws/markets and /v1/ws/private; both need API-key headers | wss://ws-subscriptions-clob.polymarket.com/ws/market (public) and /ws/user (authenticated) |
| Keep-alive | Server sends Ping frames every 10 seconds | Server sends {"heartbeat":{}}; reconnect if they stop | Market channel: send PING every 10 seconds, server replies PONG |
| Rate limits | Token buckets per tier; most requests cost 10 tokens. Basic: 200 read / 100 write tokens per second | 25 requests/second general, per IP and Cloudflare location; combos and RFQs 1/second | Cloudflare IP limits (e.g. POST /order 5,000 per 10 s burst) plus per-signer order and cancel buckets (Standard: 40 order tokens/s, burst 60) |
| Batch orders | Batch create and cancel (V2), billed per order in the batch; max batch size scales with your tier | Up to 20 orders per batch create, cancel or modify | POST /orders; costs one order token per order in the batch |
| Taker fee (100 at $0.50) | 0.07 × C × P × (1 − P), rounded up: $1.75 | 0.0695 × C × p × (1 − p), banker's rounding: $1.74 | C × rate × p × (1 − p): $1.25 at 0.05, $1.75 at 0.07 |
| Maker fee | 0.0175 on series with maker fees ($0.44 for 100 at $0.50), otherwise 0 | No fee; Tier 2 to 4 makers earn a rebate | Never charged |
| Settlement | $1 per contract on the winning side, net positions only | $1.00 winning, $0.00 losing, processed by Polymarket Clearing | UMA Optimistic Oracle (Chainlink for up-down markets). Winning tokens redeem for $1; about 2 hours if undisputed |
Facts are from the docs linked under Sources, checked 2026-10-22. Exchanges change their APIs often, so confirm in the docs before you rely on a value.
Auth and base URLs
Kalshi signs each request with your private key: the string is the millisecond timestamp, the HTTP method and the path, starting at /trade-api/v2 and without the query string. RSA-PSS with SHA-256 and Ed25519 keys are both accepted. Demo has its own host (external-api.demo.kalshi.co) and its own keys; production keys don't work there.
Polymarket US uses Ed25519 keys created at polymarket.us/developer after identity verification. The secret is shown once. The timestamp must be within 30 seconds of server time. Public market data comes from gateway.polymarket.us with no key.
International Polymarket has two layers. L1 is an EIP-712 signature (domain ClobAuthDomain, chain 137) that proves you control a wallet and creates or derives API credentials. L2 is the HMAC-SHA256 header set used on every private call. Orders are signed by the wallet too, so a bot here holds a key that can move funds. Read the Wallet-Key Safety Pack before that key exists.
Starter recipes: Kalshi API starter, Polymarket US API starter kit, Polymarket CLOB API starter.
Order types and time in force
Kalshi Create Order V2 (POST /portfolio/events/orders) takes a ticker, a side (bid buys YES, ask sells YES), a count, a price and a time in force: fill_or_kill, good_till_canceled or immediate_or_cancel. There's no market order type, and GTT isn't a valid value: send good_till_canceled with an expiration_time instead. reduce_only is only accepted with IOC. The legacy /portfolio/orders endpoint is deprecated.
Polymarket US (POST /v1/orders) has limit and market orders and five TIFs: TIME_IN_FORCE_DAY, GOOD_TILL_CANCEL, GOOD_TILL_DATE, IMMEDIATE_OR_CANCEL and FILL_OR_KILL. Direction is an intent (ORDER_INTENT_BUY_LONG buys YES, BUY_SHORT buys NO), and price always refers to YES. Mark bot orders with manualOrderIndicator. synchronousExecution can make the call wait for the outcome.
International Polymarket limit orders are GTC or GTD; market orders are FAK (fill what you can, cancel the rest) or FOK (all or nothing). GTD orders expire one minute before the stated time, and the expiration must be at least three minutes ahead. Prices must match the market's tick_size, which can change mid-market.
Client order IDs and safe retries
Only Kalshi gives you an idempotency key. Set client_order_id on every order: Kalshi rejects a second submission with the same ID, so a retry after a timeout can't double your position. Neither Polymarket API has a client order ID. On Polymarket US the exchange assigns the order id, and batch results arrive on the order stream rather than in the response. On the international CLOB each signed order has a random salt, so signing again makes a new order. For both, never auto-retry an order POST. Check open orders and fills, and keep your own idempotency key in a local ledger. The kill switch and position limits recipe covers the guardrails.
Order book shape: where is the best bid?
This is where most first bots go wrong.
- Kalshi (
GET /markets/{ticker}/orderbook) returnsorderbook_fpwithyes_dollarsandno_dollars: bids only, each sorted ascending. The best YES bid is the last element, and the best YES ask is 1 − the best NO bid. Many demo markets have no ask at all. - Polymarket US (
GET /v1/markets/{slug}/book) returnsmarketData.bidsandmarketData.offers(notasks), each entry apxamount and aqty. The docs don't specify the order. On our live check on 2026-10-22, both sides had the best price first, but don't rely on it. - International Polymarket (
GET /book) lists bids ascending and asks descending, so the best of each is the last element.
The portable fix is the same everywhere: take the maximum bid and the minimum ask.
Websockets
Kalshi has a single endpoint, wss://external-api-ws.kalshi.com/trade-api/ws/v2 (demo: wss://external-api-ws.demo.kalshi.co/trade-api/ws/v2). The handshake must be signed even for public channels such as orderbook_delta and ticker. Kalshi sends a Ping frame every 10 seconds.
Polymarket US splits public and private: wss://api.polymarket.us/v1/ws/markets for market data and trades, wss://api.polymarket.us/v1/ws/private for orders, positions and balance. Both handshakes carry the API-key headers. The server sends heartbeats; reconnect if they stop.
International Polymarket has a public market channel at wss://ws-subscriptions-clob.polymarket.com/ws/market and an authenticated user channel at /ws/user. On the market channel, your client sends PING every 10 seconds. Watch for tick_size_change if you cache tick sizes.
Rate limits
- Kalshi: token buckets by tier. Most requests cost 10 tokens. Basic: 200 read and 100 write tokens per second, Advanced: 300/300, and higher tiers come with volume. A 429 has no
Retry-Afterheader, so back off yourself.GET /portfolio/balancehas its own once-per-second limit on Basic and Advanced. - Polymarket US: 25 requests per second in general, per source IP and Cloudflare location. Combos and RFQs are 1 per second. After a 429, wait at least a second and back off. Rejections reading "Global Rate Limit Exceeded" come from a temporary latency safeguard, not from your request rate.
- International Polymarket: Cloudflare IP limits per endpoint, which throttle (delay) rather than reject. On top of that, per-signer token buckets cover order and cancel requests: Standard tier is 40 order tokens per second with a burst of 60, rising with 30-day volume. A batch is admitted only if the bucket covers every order in it. The docs announced these buckets in warning mode from July 24, 2026, so check whether enforcement is live and watch for the
Poly-RateLimit-Warningheader.
Fees
All three charge takers a coefficient × contracts × p × (1 − p), so fees peak at 50¢. They differ in the coefficient and rounding:
- Kalshi: 0.07 for takers, rounded up to the cent; 0.0175 for makers on series that charge maker fees. 100 contracts at $0.50: $1.75 taker. API fills also carry sub-cent rounding rules.
- Polymarket US: Θ = 0.0695 for takers (0.10 for table tennis), with banker's rounding to the cent. Makers in Tiers 2 to 4 earn a rebate. 100 contracts at $0.50: $1.74.
- International Polymarket: the rate comes from each market's fee schedule (0 for geopolitics, up to 0.07 for crypto). Makers never pay. 100 shares at $0.50 and a 0.05 rate: $1.25.
Try your own sizes in the Kalshi & Polymarket fee calculator. If you're comparing prices across venues, the arbitrage alert monitor shows why a gap that looks real often disappears after fees and fill delay.
Settlement
Kalshi pays $1 per contract on the winning side and settles net positions only; timing varies by market. Polymarket US settles winners at $1.00 and losers at $0.00 through Polymarket Clearing, with alternative terms where a market's rules say so. International Polymarket resolves through the UMA Optimistic Oracle (Chainlink for up-down markets). An undisputed proposal resolves in about two hours, a disputed one in four to six days. Winning tokens redeem for $1, and in a rare 50/50 outcome each token redeems for $0.50. Read each market's rules, not just its title.
Gotchas bot builders hit
These are the API mistakes that come up when AI coding tools build these bots (see also how we test recipes).
- Kalshi: sign the full
/trade-api/v2/...path, not the relative one. Demo and production tickers differ. Filter lists withstatus=open(notactive) andmve_filter=excludeto skip multivariate combos.reduce_onlyorders must useimmediate_or_cancel. - Polymarket US: the preview body is wrapped as
{"request": order}. The book saysoffers, notasks. Search returnsevents[]. Expired markets return a null best bid/offer, so filter withclosed=false. Some markets quote below a 1¢ tick. - International Polymarket: sports outcomes are team names, not Yes/No. The Python SDK returns snake_case fields (
volume_24hr). Live fee rates can differ from the category table, so read each market's fee schedule. Popular open-source bots keep a raw wallet key in the code; don't copy that.
Official SDKs
- Kalshi:
pip install kalshi-python,npm install kalshi-typescript. - Polymarket US:
pip install polymarket-us(from polymarket_us import PolymarketUS), npmpolymarket-us. - International Polymarket:
pip install polymarket-client(from polymarket import PublicClient, SecureClient), npm@polymarket/client. The olderpy-clob-clientsupports the same four order types: GTC, GTD, FOK and FAK.
FAQ
Which has the easier API, Kalshi or Polymarket?
Kalshi and Polymarket US are closest to a normal exchange API: an API key plus a signed request, limit orders, and a demo (Kalshi) or order preview (Polymarket US). International Polymarket adds a wallet layer: an EIP-712 signature creates API credentials, HMAC headers authenticate requests, and every order is signed by the wallet. Pick the venue you can actually use first, then the API.
Does Polymarket have a sandbox or testnet like Kalshi demo?
Kalshi has a full demo environment at demo.kalshi.co with mock funds and separate keys. The Polymarket US retail docs list no sandbox; the closest tool is POST /v1/order/preview, which previews an order without placing it. The international CLOB docs list no testnet, so paper-trade against the live book instead.
Can I safely retry an order if the request times out?
On Kalshi, yes, if you set client_order_id: Kalshi rejects duplicate submissions with the same ID. Polymarket US and international Polymarket have no client order ID, so a blind retry can place a second order. Check open orders and fills first, and keep your own idempotency key locally.
Why does my order book look backwards?
Kalshi returns bids only, sorted ascending, so the best bid is the last element, and the YES ask is 1 minus the best NO bid. International Polymarket sorts bids ascending and asks descending, so the best of each is also last. Polymarket US uses bids and offers, and the docs don't state the order. Take the max bid and min ask instead of trusting position.
Which is cheaper for a bot, Kalshi or Polymarket?
All three use the same shape: a coefficient × contracts × p × (1 − p). For 100 contracts at $0.50, a taker pays $1.75 on Kalshi, $1.74 on Polymarket US, and $1.25 to $1.75 on international Polymarket depending on the market's rate. Makers pay nothing on Polymarket, while Kalshi charges makers on some series. Use the fee calculator for your own sizes.
What are the rate limits?
Kalshi uses token buckets by tier: Basic gets 200 read and 100 write tokens per second, and most requests cost 10 tokens. Polymarket US allows 25 requests per second in general, with 1 per second for combos and RFQs. International Polymarket has Cloudflare IP limits per endpoint plus per-signer order and cancel buckets that grow with 30-day volume.
Build it
- Kalshi API starter → Kalshi AI trading bot (demo first)
- Polymarket US API starter kit → Polymarket bot in paper mode
- Polymarket CLOB API starter → Wallet-Key Safety Pack
- Polymarket–Kalshi arbitrage alert monitor (alerts only)
- All venues: platforms · fee calculator
Sources
- Kalshi: API environments
- Kalshi: API keys and signing
- Kalshi: Create Order (V2)
- Kalshi: Quick start, create an order
- Kalshi: Order book responses
- Kalshi: Rate limits
- Kalshi: WebSocket connection
- Kalshi: WebSocket keep-alive
- Kalshi: Fee rounding
- Kalshi: Market settlement
- Kalshi: Fee schedule (PDF)
- Polymarket US: API introduction
- Polymarket US: Authentication
- Polymarket US: Create order
- Polymarket US: Orders concepts
- Polymarket US: Market book
- Polymarket US: Rate limits
- Polymarket US: WebSocket overview
- Polymarket US: Fees
- Polymarket US: Contract settlement
- Polymarket: API overview
- Polymarket: Place orders
- Polymarket: Prices and order books
- Polymarket: Real-time data
- Polymarket: Rate limits
- Polymarket: CLOB trading rate limits
- Polymarket: Fees
- Polymarket: Resolution
Get new recipes by email
When a new bot recipe passes its model tests, we'll send it to you. Free. No picks, no signals, ever.