rivomarkets

Kalshi API

Kalshi gives you a free REST API, a WebSocket, a FIX gateway and a demo exchange with fake money. This guide takes you from your first unauthenticated request to a signed order, a live order book stream and the gotchas behind most 401s.

Updated September 27, 2026

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:

InterfaceAddressUse for
RESThttps://external-api.kalshi.com/trade-api/v2Markets, events, order books, orders, portfolio
WebSocketwss://external-api-ws.kalshi.com/trade-api/ws/v2Live books, trades, tickers, your fills and orders
Demo RESThttps://external-api.demo.kalshi.co/trade-api/v2Order logic with fake money
Demo WebSocketwss://external-api-ws.demo.kalshi.co/trade-api/ws/v2Streaming tests with fake money
FIXFIXT.1.1 and FIX50SP2 over TLS 1.2 or laterInstitutional 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. KXHIGHNY is 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.

curl
# 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"
Python: best bid and ask from the book
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)
TypeScript
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_dollars and no_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

  1. Sign in to kalshi.com and open your profile.
  2. Open API Keys and choose Create New API Key.
  3. Save the key ID and the private key file. Kalshi shows the private key once.
Key typeSigningPick if
Ed25519Ed25519 over the messageYou write your own client. Kalshi recommends this type.
RSA 2048RSA-PSS with SHA-256, MGF1 SHA-256, salt length equal to the digestYou 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:

HeaderValue
KALSHI-ACCESS-KEYYour key ID
KALSHI-ACCESS-TIMESTAMPCurrent time in milliseconds
KALSHI-ACCESS-SIGNATUREBase64 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.

Python: signed request
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())
TypeScript (Node 18 and later): signed request
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.

FieldRequiredNotes
tickerYesThe market ticker
sideYesbid for YES exposure, ask for NO exposure
countYesFixed point string with two decimals, such as "10.00"
priceYesFixed point dollars, such as "0.3000"
time_in_forceYesfill_or_kill, good_till_canceled or immediate_or_cancel
self_trade_prevention_typeYestaker_at_cross or maker
client_order_idNoYour own ID. Resend the same one after a timeout to avoid a double order.
post_only, reduce_only, expiration_timeNoMaker-only orders, exit-only orders and expiry in Unix seconds
Python: place a limit order, then cancel
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.

Python: stream one order book
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())
ChannelAccessWhat you get
orderbook_deltaPublicA snapshot, then every change to the book
tickerPublicPrice and volume updates per market
tradePublicEvery fill on the exchange, without trader identity
market_lifecycle_v2PublicMarkets opening, closing and settling
multivariate_market_lifecyclePublicThe same for combo markets
cfbenchmarks_value, pyth_valuePublicReference prices behind crypto markets
fill, user_orders, market_positionsPrivateYour fills, order updates and positions
order_group_updates, communicationsPrivateOrder 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.

TierRead tokens per secondWrite tokens per secondHow you get the tier
Basic200100Every new account
Advanced300300Upgrade through the account API
Expert600600Volume share of 0.075%, keep at 0.05%
Premier1,2001,200Volume share of 0.125%, keep at 0.10%
Paragon2,4002,400Volume share of 0.25%, keep at 0.20%
Prime4,8004,800Volume share of 0.50%, keep at 0.40%
Prestige12,0009,600Volume 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.

OrderTaker feeFee per contract
100 contracts at 50 cents$1.751.75 cents
100 contracts at 20 cents$1.121.12 cents
100 contracts at 5 cents$0.340.34 cents
1 contract at 50 cents$0.022 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_type and fee_multiplier on 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:

  1. 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.
  2. Send an RFQ for the combo. You hold at most 100 open RFQs.
  3. Market makers answer with quotes on the communications channel. 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

  1. Pull markets and books with no key until your parsing handles fixed point strings and bids-only books.
  2. Create a demo account and a demo Ed25519 key.
  3. Sign a balance request. Fix 401s here, not in the order path.
  4. Place one resting order far from the market, then cancel. Confirm both on the user_orders channel.
  5. Add client_order_id to every order and test a forced timeout.
  6. Handle 429s with backoff and reconnect the WebSocket on drop.
  7. Log every order and fill with its timestamp. You will want the audit trail the first time a fill surprises you.
  8. 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_ranges first.
  • Fractional counts. Counts move in 0.01 steps now. Parse _fp and _dollars fields 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.

LanguagePackageInstall
Python (sync)kalshi_python_syncpip install kalshi_python_sync
Python (async)kalshi_python_asyncpip install kalshi_python_async
TypeScriptkalshi-typescriptnpm 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

JobUseWhy
A dashboard of pricesREST, no keyNothing to sign, cache for a few seconds
Alerts on price movesWebSocket tickerPush beats polling a thousand markets
A trading botREST orders plus WebSocket fillsPlace over REST, confirm fills over the stream
Market makingWebSocket orderbook_delta plus order groupsYou need every book change and fast cancels
Institutional flowFIXDrop copy, post-trade and dedicated order entry
Following named tradersRivo /v1Kalshi'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_v2 for 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

KalshiPolymarket GlobalPolymarket US
AuthRSA-PSS or Ed25519 signature per requestWallet signature, then HMAC headersEd25519 signature per request
MoneyUS dollarspUSD on PolygonUS dollars
Public market dataYesYesYes
Trader identity on tradesNoWallet address on every tradeNo
Starting rate limitAbout 20 reads a second9,000 CLOB requests per 10 seconds20 requests a second
Test environmentFull demo exchangeNoneNone 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.
Rivo: Kalshi trades by named traders
# 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_..."
Rivo: backtest a Kalshi signal
# 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.

Build a Kalshi bot without writing the API code

Pick a Kalshi trader or a signal, set your size and read the backtest. Run the bot on paper or live through your own Kalshi key.

Build a bot