The Kalshi API in 60 seconds
- Market data is public. Series, events, markets, order books and trades need no key.
- Anything touching money needs a key. You sign every request with RSA-PSS or Ed25519.
- The base URL is
https://external-api.kalshi.com/trade-api/v2. - New order code goes to
POST /portfolio/events/orders, the V2 order endpoint. - Prices and counts travel as fixed point strings.
"0.3000"means 30 cents. - Rate limits come in token budgets. A new account gets about 20 reads a second.
- The demo exchange at demo.kalshi.co runs the same API with fake money. Test there first.
Our take: the Kalshi API is the cleanest regulated prediction market API in the US. The docs are complete, the signing scheme is simple and the demo exchange mirrors production. The hard parts are in the details: bids-only order books, sub-cent price grids and a signature rule a lot of people get wrong on day one.
Does Kalshi have an API?
Yes. Here's everything Kalshi exposes:
| Interface | Address | Use for |
|---|---|---|
| REST | https://external-api.kalshi.com/trade-api/v2 | Markets, events, order books, orders, portfolio |
| WebSocket | wss://external-api-ws.kalshi.com/trade-api/ws/v2 | Live books, trades, tickers, your fills and orders |
| Demo REST | https://external-api.demo.kalshi.co/trade-api/v2 | Order logic with fake money |
| Demo WebSocket | wss://external-api-ws.demo.kalshi.co/trade-api/ws/v2 | Streaming tests with fake money |
| FIX | FIXT.1.1 and FIX50SP2 over TLS 1.2 or later | Institutional order entry, drop copy and market data |
The older host api.elections.kalshi.com still answers, and a lot of open source code points there. Use the host from the current docs for anything new.
How Kalshi organizes markets
Three levels, and you need all three to find anything:
- Series: a recurring question.
KXHIGHNYis the daily high temperature in New York. - Event: one instance of the series, such as the high on one date.
- Market: one tradable contract inside the event, such as "above 80 degrees". Orders go to a market ticker.
Kalshi lists more than 14,000 series across sports, politics, economics, financials, crypto, weather, culture and mentions. Combos (parlays) live in their own multivariate collections with the prefix KXMVE.
Can you use the Kalshi API without a key?
Yes, for market data. Start here: no account, no signing.
# One series: the daily high temperature in New York
curl "https://external-api.kalshi.com/trade-api/v2/series/KXHIGHNY"
# Every open market in that series
curl "https://external-api.kalshi.com/trade-api/v2/markets?series_ticker=KXHIGHNY&status=open"
# The order book for one market
curl "https://external-api.kalshi.com/trade-api/v2/markets/<MARKET_TICKER>/orderbook"import requests
BASE = "https://external-api.kalshi.com/trade-api/v2"
markets = requests.get(
f"{BASE}/markets",
params={"series_ticker": "KXHIGHNY", "status": "open"},
).json()["markets"]
ticker = markets[0]["ticker"]
book = requests.get(f"{BASE}/markets/{ticker}/orderbook").json()["orderbook_fp"]
# Kalshi returns bids only. Each level is [price_dollars, count].
best_yes_bid = max(float(p) for p, _ in book["yes_dollars"]) if book["yes_dollars"] else None
best_no_bid = max(float(p) for p, _ in book["no_dollars"]) if book["no_dollars"] else None
# A NO bid at 0.62 is a YES ask at 0.38.
best_yes_ask = round(1 - best_no_bid, 4) if best_no_bid is not None else None
print(ticker, "YES bid", best_yes_bid, "YES ask", best_yes_ask)const BASE = "https://external-api.kalshi.com/trade-api/v2";
const res = await fetch(`${BASE}/markets?series_ticker=KXHIGHNY&status=open`);
const { markets } = (await res.json()) as { markets: { ticker: string; title: string }[] };
for (const market of markets.slice(0, 5)) {
console.log(market.ticker, market.title);
}Two things trip up new users of the order book:
- Kalshi returns bids only. The response holds
yes_dollarsandno_dollars, both bids. A NO bid at 62 cents equals a YES ask at 38 cents, so you derive the ask side yourself. - The price grid is not always one cent. Every market carries
price_ranges. Many markets tick in tenths of a cent below 10 cents and above 90 cents, and combos tick finer still. Round to the market's grid before you send a price.
How to get a Kalshi API key
- Sign in to kalshi.com and open your profile.
- Open API Keys and choose Create New API Key.
- Save the key ID and the private key file. Kalshi shows the private key once.
| Key type | Signing | Pick if |
|---|---|---|
| Ed25519 | Ed25519 over the message | You write your own client. Kalshi recommends this type. |
| RSA 2048 | RSA-PSS with SHA-256, MGF1 SHA-256, salt length equal to the digest | You use the official SDKs, which expect RSA. |
For Ed25519 you register your own public key or ask Kalshi to generate the pair through POST /trade-api/v2/api_keys/generate. Kalshi API keys can't withdraw, so a leaked key can trade your account but can't move cash out. Still, treat it like a password.
How to sign a Kalshi API request
Every authenticated request carries three headers:
| Header | Value |
|---|---|
| KALSHI-ACCESS-KEY | Your key ID |
| KALSHI-ACCESS-TIMESTAMP | Current time in milliseconds |
| KALSHI-ACCESS-SIGNATURE | Base64 signature of timestamp + method + path |
The signed message is the timestamp, then the HTTP method in capitals, then the path from /trade-api/v2 onward, with no query string. For a balance check at 1727470800000 milliseconds, you sign 1727470800000GET/trade-api/v2/portfolio/balance.
import base64
import time
import requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
HOST = "https://external-api.kalshi.com"
KEY_ID = "your-key-id"
with open("kalshi-key.pem", "rb") as f:
PRIVATE_KEY = serialization.load_pem_private_key(f.read(), password=None)
def sign(message: str) -> str:
data = message.encode("utf-8")
if isinstance(PRIVATE_KEY, Ed25519PrivateKey):
signature = PRIVATE_KEY.sign(data)
else:
signature = PRIVATE_KEY.sign(
data,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH),
hashes.SHA256(),
)
return base64.b64encode(signature).decode("utf-8")
def auth_headers(method: str, path: str) -> dict[str, str]:
timestamp = str(int(time.time() * 1000))
bare_path = path.split("?")[0] # sign the path without the query string
return {
"KALSHI-ACCESS-KEY": KEY_ID,
"KALSHI-ACCESS-TIMESTAMP": timestamp,
"KALSHI-ACCESS-SIGNATURE": sign(timestamp + method + bare_path),
}
path = "/trade-api/v2/portfolio/balance"
print(requests.get(HOST + path, headers=auth_headers("GET", path)).json())import { createPrivateKey, sign as edSign, createSign, constants } from "node:crypto";
import { readFileSync } from "node:fs";
const HOST = "https://external-api.kalshi.com";
const KEY_ID = "your-key-id";
const privateKey = createPrivateKey(readFileSync("kalshi-key.pem", "utf8"));
function sign(message: string): string {
if (privateKey.asymmetricKeyType === "ed25519") {
return edSign(null, Buffer.from(message, "utf8"), privateKey).toString("base64");
}
const signer = createSign("RSA-SHA256");
signer.update(message);
signer.end();
return signer
.sign({ key: privateKey, padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: constants.RSA_PSS_SALTLEN_DIGEST })
.toString("base64");
}
function authHeaders(method: string, path: string): Record<string, string> {
const timestamp = Date.now().toString();
const barePath = path.split("?")[0];
return {
"KALSHI-ACCESS-KEY": KEY_ID,
"KALSHI-ACCESS-TIMESTAMP": timestamp,
"KALSHI-ACCESS-SIGNATURE": sign(timestamp + method + barePath),
};
}
const path = "/trade-api/v2/portfolio/balance";
const res = await fetch(HOST + path, { headers: authHeaders("GET", path) });
console.log(await res.json());How to place and cancel a Kalshi order
Use the V2 order endpoint, POST /portfolio/events/orders. Kalshi is retiring the legacy /portfolio/orders endpoint, so skip old tutorials built on the legacy yes/no and buy/sell fields.
| Field | Required | Notes |
|---|---|---|
| ticker | Yes | The market ticker |
| side | Yes | bid for YES exposure, ask for NO exposure |
| count | Yes | Fixed point string with two decimals, such as "10.00" |
| price | Yes | Fixed point dollars, such as "0.3000" |
| time_in_force | Yes | fill_or_kill, good_till_canceled or immediate_or_cancel |
| self_trade_prevention_type | Yes | taker_at_cross or maker |
| client_order_id | No | Your own ID. Resend the same one after a timeout to avoid a double order. |
| post_only, reduce_only, expiration_time | No | Maker-only orders, exit-only orders and expiry in Unix seconds |
import uuid
# Reuses HOST and auth_headers from the signing example.
path = "/trade-api/v2/portfolio/events/orders"
order = {
"ticker": "<MARKET_TICKER>",
"client_order_id": str(uuid.uuid4()), # resend the same id after a timeout, never a new one
"side": "bid", # bid buys YES exposure, ask buys NO exposure
"count": "10.00", # fixed point, two decimals
"price": "0.3000", # fixed point dollars
"time_in_force": "good_till_canceled",
"self_trade_prevention_type": "taker_at_cross",
}
placed = requests.post(HOST + path, json=order, headers=auth_headers("POST", path)).json()
print(placed["order_id"], "filled", placed["fill_count"], "resting", placed["remaining_count"])
cancel_path = f"/trade-api/v2/portfolio/events/orders/{placed['order_id']}"
cancelled = requests.delete(
HOST + cancel_path,
params={"market_ticker": order["ticker"]},
headers=auth_headers("DELETE", cancel_path),
).json()
print("cancelled", cancelled["reduced_by"])The response returns order_id, fill_count and remaining_count as a 201. The cancel call is DELETE /portfolio/events/orders/{order_id} with the market ticker as a query parameter, and returns how many contracts came off the book.
Always set client_order_id. A network timeout on an order leaves you not knowing whether the order landed. Resending with the same ID lets Kalshi reject the duplicate instead of filling you twice.
Does Kalshi have a WebSocket API?
Yes. Open the socket with the same three signed headers, signed over GET/trade-api/ws/v2. You need the headers even for public channels. Then send a subscribe command.
import asyncio
import json
import websockets
# Reuses auth_headers from the signing example.
WS_URL = "wss://external-api-ws.kalshi.com/trade-api/ws/v2"
async def main() -> None:
headers = auth_headers("GET", "/trade-api/ws/v2")
async with websockets.connect(WS_URL, additional_headers=headers) as ws:
await ws.send(json.dumps({
"id": 1,
"cmd": "subscribe",
"params": {"channels": ["orderbook_delta", "trade"], "market_ticker": "<MARKET_TICKER>"},
}))
async for raw in ws:
print(json.loads(raw))
asyncio.run(main())| Channel | Access | What you get |
|---|---|---|
| orderbook_delta | Public | A snapshot, then every change to the book |
| ticker | Public | Price and volume updates per market |
| trade | Public | Every fill on the exchange, without trader identity |
| market_lifecycle_v2 | Public | Markets opening, closing and settling |
| multivariate_market_lifecycle | Public | The same for combo markets |
| cfbenchmarks_value, pyth_value | Public | Reference prices behind crypto markets |
| fill, user_orders, market_positions | Private | Your fills, order updates and positions |
| order_group_updates, communications | Private | Order group limits and RFQ traffic |
Pass market_tickers as a list to watch many markets on one subscription. Each command needs a unique id within the session, and the reply carries a sid you use to unsubscribe later.
Kalshi API rate limits
Kalshi budgets tokens, not requests. Most requests cost 10 tokens, so divide each number by 10 for a rough request rate.
| Tier | Read tokens per second | Write tokens per second | How you get the tier |
|---|---|---|---|
| Basic | 200 | 100 | Every new account |
| Advanced | 300 | 300 | Upgrade through the account API |
| Expert | 600 | 600 | Volume share of 0.075%, keep at 0.05% |
| Premier | 1,200 | 1,200 | Volume share of 0.125%, keep at 0.10% |
| Paragon | 2,400 | 2,400 | Volume share of 0.25%, keep at 0.20% |
| Prime | 4,800 | 4,800 | Volume share of 0.50%, keep at 0.40% |
| Prestige | 12,000 | 9,600 | Volume share of 1.00%, keep at 0.80% |
- Volume share is your trailing 30 day volume divided by twice the previous month's exchange volume. Kalshi checks once a day.
- A volume tier lasts 30 days and renews while you keep qualifying.
- Over the limit, you get a 429 with
{"error": "too many requests"}. No penalty, no cooldown. Back off and retry.
Our take: Basic is plenty for a personal bot polling a few dozen markets. Anything scanning the whole exchange belongs on the WebSocket, not a polling loop.
What Kalshi fees cost an API bot
The API charges the same trading fee as the app. A taker order pays round up(0.07 x C x P x (1 - P)), where C is contracts and P the price in dollars. About 160 series, mostly the big sports leagues plus some financial and economic markets, also charge makers 0.0175 x C x P x (1 - P). Most markets charge makers nothing.
| Order | Taker fee | Fee per contract |
|---|---|---|
| 100 contracts at 50 cents | $1.75 | 1.75 cents |
| 100 contracts at 20 cents | $1.12 | 1.12 cents |
| 100 contracts at 5 cents | $0.34 | 0.34 cents |
| 1 contract at 50 cents | $0.02 | 2 cents after rounding |
- Batch small orders. Rounding up to the cent turns a 1.75 cent fee into 2 cents on a single contract.
- Post resting limit orders when you're not in a hurry. They pay no fee on most markets.
- Read
fee_typeandfee_multiplieron the series. MLB props run at half the normal rate, and a handful of long-dated series trade free.
Worked examples for every price are in Kalshi fees explained.
Kalshi combos and RFQ through the API
Kalshi prices combos (parlays) by request for quote, and the API exposes the whole flow:
- Look up or create the combo market in a multivariate collection with
POST /multivariate_event_collections/{collection_ticker}. Kalshi caps creations at 5,000 a week per user. - Send an RFQ for the combo. You hold at most 100 open RFQs.
- Market makers answer with quotes on the
communicationschannel. Accept one to trade.
No quote means no fill. Popular NFL and college football combos get quotes fast, and odd pairings often get none. Since July 2026 the side hitting a quote pays a taker fee and the quoting side pays a maker fee. The trader view of combos lives in Kalshi parlays explained.
A checklist for your first Kalshi bot
- Pull markets and books with no key until your parsing handles fixed point strings and bids-only books.
- Create a demo account and a demo Ed25519 key.
- Sign a balance request. Fix 401s here, not in the order path.
- Place one resting order far from the market, then cancel. Confirm both on the
user_orderschannel. - Add
client_order_idto every order and test a forced timeout. - Handle 429s with backoff and reconnect the WebSocket on drop.
- Log every order and fill with its timestamp. You will want the audit trail the first time a fill surprises you.
- Swap to production hosts and a production key, and start with small size.
Kalshi API errors and gotchas
- 401 on a signed call. You signed the query string, used seconds instead of milliseconds, or sent the method in lower case.
- 401 on the WebSocket. You signed the REST path. Sign
/trade-api/ws/v2. - Price rejected. The price is off the market's grid. Read
price_rangesfirst. - Fractional counts. Counts move in 0.01 steps now. Parse
_fpand_dollarsfields as decimals, not integers. - Double orders after a timeout. Resend with the same
client_order_id. - Demo keys on production. Demo and production keys are separate. A demo key fails on production.
- Stale SDK. A brand new endpoint reaches the REST API before the weekly SDK release. Call REST directly when you need the new thing today.
Is there a Kalshi Python SDK?
Yes. Kalshi publishes official SDKs for Python and TypeScript.
| Language | Package | Install |
|---|---|---|
| Python (sync) | kalshi_python_sync | pip install kalshi_python_sync |
| Python (async) | kalshi_python_async | pip install kalshi_python_async |
| TypeScript | kalshi-typescript | npm install kalshi-typescript |
Skip kalshi-python. Nobody maintains the package. Our take on the official SDKs: good for a quick script, less good for a production bot. The signing code above runs 40 lines and gives you full control over retries, timeouts and the newest endpoints.
Does Kalshi have a demo API?
Yes. Create a separate account at demo.kalshi.co, generate a demo key and swap the hosts. The demo runs the same endpoints with fake money and thinner books. Run every new order path there first: order placement, partial fills, cancels and your reconnect logic.
Which Kalshi API for which job
| Job | Use | Why |
|---|---|---|
| A dashboard of prices | REST, no key | Nothing to sign, cache for a few seconds |
| Alerts on price moves | WebSocket ticker | Push beats polling a thousand markets |
| A trading bot | REST orders plus WebSocket fills | Place over REST, confirm fills over the stream |
| Market making | WebSocket orderbook_delta plus order groups | You need every book change and fast cancels |
| Institutional flow | FIX | Drop copy, post-trade and dedicated order entry |
| Following named traders | Rivo /v1 | Kalshi's API has no trader identity |
How settlement shows up in the Kalshi API
Each contract settles at $1.00 or $0. Kalshi's markets team checks the result against the source named in the market rules, such as the National Weather Service climate report for temperature or official league stats for sports. Most markets settle within about three hours of the outcome.
- Watch
market_lifecycle_v2for the close and the settlement instead of polling market status. - Read your payouts from the portfolio settlements endpoint, not from your fills.
- Some markets settle at a fair value or a split instead of $1 or $0. Combo legs for a player who did not play settle at the last fair price. Do not hardcode binary payouts.
Kalshi API vs Polymarket API
| Kalshi | Polymarket Global | Polymarket US | |
|---|---|---|---|
| Auth | RSA-PSS or Ed25519 signature per request | Wallet signature, then HMAC headers | Ed25519 signature per request |
| Money | US dollars | pUSD on Polygon | US dollars |
| Public market data | Yes | Yes | Yes |
| Trader identity on trades | No | Wallet address on every trade | No |
| Starting rate limit | About 20 reads a second | 9,000 CLOB requests per 10 seconds | 20 requests a second |
| Test environment | Full demo exchange | None | None listed |
Our take: Kalshi wins on developer experience thanks to the demo exchange and one signing scheme. Polymarket wins on transparency because every trade names a wallet. The full Polymarket side is in the Polymarket API guide.
Does the Kalshi API do backtesting?
No. Kalshi gives you raw material, not a backtester. You get markets, candlesticks, public trades and your own fills. Turning that into a backtest is your job: pull every settled market you care about, rebuild the prices you would have paid, apply Kalshi's fee and rounding, handle voids and settlement values, and keep a holdout so you don't fit the past. That is weeks of work before you learn whether an idea is any good.
And there is one backtest the Kalshi API can't support at all: copying a trader. Kalshi's public trades carry no trader identity, so no amount of API work tells you what a specific person bought or how their bets settled. Rivo records the named traders, scores every position when its market settles, and runs the backtest for you at your size.
What the Kalshi API does not give you
- A backtester. You get prices and trades. The replay, fees, settlement and overfitting checks are on you.
- Who traded. Public trades carry no trader identity. You see size and price, never the person.
- Other venues. Kalshi's API covers Kalshi. Polymarket, Polymarket US and Gemini each run their own API with their own signing scheme.
- Settled trader records. Kalshi shows profiles, not a scored history of every position against how the market settled.
Where Rivo fits next to the Kalshi API
Rivo works one layer up. We read Kalshi, Polymarket, Polymarket US and Gemini, attach named traders to their Kalshi trades, and score every position when the market settles. One bearer key gives you the full set:
- REST under /v1: the live tape across venues, ranked traders with settled records, market search and market detail.
- Backtests, built in: score a signal or a trader against markets already settled, at your size, with Kalshi's fees and an out of sample holdout to catch overfitting. Kalshi's API has nothing like it.
- SSE at /v1/stream: trades pushed to your process, up to 3 streams per key.
- Webhooks: signed deliveries to an https endpoint you own, up to 5 endpoints.
- Remote MCP: the same tools inside Claude, ChatGPT or Cursor. See the prediction market MCP server.
- Scoped keys: read, write and trade, with an optional daily spend cap. Orders go through your own connected Kalshi account.
# Named Kalshi traders, every size, newest first
curl "https://api.rivo.markets/v1/feed?platform=kalshi&lane=tracked&pageSize=10" \
-H "Authorization: Bearer rivo_live_..."
# The same trades pushed to you as server-sent events
curl -N "https://api.rivo.markets/v1/stream?lane=tracked" \
-H "Authorization: Bearer rivo_live_..."# What would $100 on every large Kalshi longshot have returned?
curl -X POST "https://api.rivo.markets/v1/backtest" \
-H "Authorization: Bearer rivo_live_..." \
-H "Content-Type: application/json" \
-d '{"criteria":{"platforms":["kalshi"],"priceMax":0.2,"eventTypes":["open","add","flip"]},"stake":100,"holdout":true}'The limit is 120 requests a minute per key, shared between REST and MCP, and $15 a month covers the whole thing. Full reference in the Rivo API docs.
Where Rivo stops: we do not replace the Kalshi API for market making, and we do not sell full order book history. If you quote both sides of a book, talk to Kalshi directly and use the WebSocket or FIX. Rivo answers a different question: who is trading, and does following them pay.
Frequently asked questions
Does Kalshi have an API?
Yes. Kalshi runs a public REST API at external-api.kalshi.com/trade-api/v2, a WebSocket at external-api-ws.kalshi.com, a FIX API for institutions and a demo environment with fake money. Market data needs no key.
Can you use the Kalshi API for free?
Yes. Kalshi charges nothing for API access. You only pay the normal trading fee on orders you place, the same fee as in the app.
How do I get a Kalshi API key?
Sign in to kalshi.com, open your profile, go to API Keys and choose Create New API Key. Kalshi shows the private key once. Save the file before you close the page.
Should I use an RSA or Ed25519 Kalshi key?
Use Ed25519 for your own code. Kalshi recommends Ed25519, the keys are smaller and signing is faster. Use RSA 2048 if you depend on the official SDKs, which still expect RSA.
What are the Kalshi API rate limits?
Every account starts on Basic: 200 read tokens and 100 write tokens per second. Most requests cost 10 tokens, so Basic works out to about 20 reads and 10 writes per second. Higher tiers come from trading volume, up to Prestige at 12,000 and 9,600 tokens per second.
Why does Kalshi return a 401 on my signed request?
The usual cause is signing the path with the query string attached. Sign the timestamp in milliseconds, the method in capitals and the bare path, starting at /trade-api/v2. The second cause is a stale timestamp from a drifting clock.
Does Kalshi have a WebSocket API?
Yes. Connect to wss://external-api-ws.kalshi.com/trade-api/ws/v2 with the same three signed headers, then send a subscribe command for channels such as orderbook_delta, ticker, trade and fill. Kalshi requires authentication even for public channels.
Is there an official Kalshi Python SDK?
Yes. kalshi_python_sync and kalshi_python_async on pip, and kalshi-typescript on npm. The older kalshi-python package is unmaintained. The SDKs are generated from the OpenAPI spec weekly and trail new endpoints.
Does Kalshi have a demo API for testing?
Yes. The demo at demo.kalshi.co uses fake money and separate keys. Point REST calls at external-api.demo.kalshi.co/trade-api/v2 and WebSocket calls at external-api-ws.demo.kalshi.co/trade-api/ws/v2.
Is there a Kalshi MCP server?
Kalshi publishes no official MCP server. Community servers exist on GitHub. Rivo runs a remote MCP server covering Kalshi, Polymarket, Polymarket US and Gemini with one key, for research, backtests and orders through your own account.
Does the Kalshi API show who placed a trade?
No. Kalshi's public trade feed carries the market, side, price and size, with no trader identity. Kalshi names some traders on public profiles, and Rivo links those profiles to their trades and scores them on settled markets.