Kalshi API in Python: read markets, sign requests, place orders and use the demo
Kalshi's API in Python: read markets and order books with no key, sign requests with Ed25519, place an order, and test on the demo with mock funds.
Short answer: Market data at
https://external-api.kalshi.com/trade-api/v2needs no key. Orders need a request signed with your private key. To test, use the demo, which has its own keys and mock funds, athttps://external-api.demo.kalshi.co/trade-api/v2.
We ran the read code below on Nov 5, 2026. We ran the signing code with a throwaway key, and the demo answered "could not authenticate" as it should. We didn't place an order, because that needs an account. Kalshi's own samples use plain requests and cryptography, so we do too.
Read markets and an order book
import requests
BASE = "https://external-api.kalshi.com/trade-api/v2"
markets = requests.get(f"{BASE}/markets", params={"series_ticker": "KXNFLTOTAL", "status": "open", "limit": 3}).json()["markets"]
for market in markets:
print(market["ticker"], market["yes_bid_dollars"], market["yes_ask_dollars"])
ticker = markets[0]["ticker"]
book = requests.get(f"{BASE}/markets/{ticker}/orderbook").json()["orderbook_fp"]
best_yes_bid = max(float(price) for price, size in book["yes_dollars"])
best_no_bid = max(float(price) for price, size in book["no_dollars"])
print("bid", best_yes_bid, "ask", round(1 - best_no_bid, 4))Kalshi's book lists only bids, for Yes and for No. A No bid at 96¢ is the same as a Yes ask at 4¢, so the Yes ask is 1 minus the best No bid. Prices and sizes arrive as strings.
Older examples use api.elections.kalshi.com. It still answered on Nov 5, but the docs now show external-api.kalshi.com.
Sign a request
Each private request carries three headers: your key ID, a millisecond timestamp and a signature. You sign the timestamp, the method and the path, without its query string. Kalshi recommends Ed25519 keys, and RSA-PSS also works.
import base64
import os
import time
import requests
from cryptography.hazmat.primitives import serialization
BASE = "https://external-api.demo.kalshi.co"
KEY_ID = os.environ["KALSHI_KEY_ID"]
with open(os.environ["KALSHI_KEY_FILE"], "rb") as f:
private_key = serialization.load_pem_private_key(f.read(), password=None)
def signed(method: str, path: str) -> dict:
timestamp = str(int(time.time() * 1000))
message = timestamp + method + path.split("?")[0]
signature = base64.b64encode(private_key.sign(message.encode())).decode()
return {
"KALSHI-ACCESS-KEY": KEY_ID,
"KALSHI-ACCESS-TIMESTAMP": timestamp,
"KALSHI-ACCESS-SIGNATURE": signature,
}
path = "/trade-api/v2/portfolio/balance"
print(requests.get(BASE + path, headers=signed("GET", path)).json())Kalshi shows the private key once, when you create it, so save it then. The demo and production have separate accounts and separate keys.
Place an order
The current order endpoint takes bid or ask on the Yes side, with prices and sizes as strings:
import uuid
path = "/trade-api/v2/portfolio/events/orders"
order = {
"ticker": ticker,
"client_order_id": str(uuid.uuid4()),
"side": "bid",
"count": "10.00",
"price": "0.5600",
"time_in_force": "good_till_canceled",
"self_trade_prevention_type": "taker_at_cross",
"post_only": True,
}
print(requests.post(BASE + path, headers=signed("POST", path), json=order).json())client_order_id lets you spot a duplicate if a request times out and you send it again. time_in_force can also be fill_or_kill or immediate_or_cancel, and to make an order expire you set expiration_time. The demo has different tickers from production, so read a market from the host you're trading on.
Rate limits
Kalshi's limits are budgets of tokens per second, and most requests cost 10 tokens.
| Tier | Read (tokens a second) | Write (tokens a second) | Reads a second | Writes a second |
|---|---|---|---|---|
| Basic | 200 | 100 | 20 | 10 |
| Advanced | 300 | 300 | 30 | 30 |
| Expert | 600 | 600 | 60 | 60 |
Basic comes with sign-up, and Advanced is one call to an upgrade endpoint. Higher tiers depend on your share of trading volume.
Fees
The taker fee is 0.07 × contracts × price × (1 − price), rounded up to the cent. Makers pay nothing by default, though some markets charge them. The fee calculator works it out at any price. The fee is only part of what a trade costs, because you also pay the spread.
Test your bot
The demo is the place to test your signing and your orders. Kalshi warns that its prices and behavior "may not be reflective of those in real markets", so it can't tell you whether your strategy has an edge. To run a quoting bot against live-moving prices with practice money and no sign-up, try Rizon's API, or compare the options in where to test a trading bot without real money. It's a different contract, so it tests your logic rather than Kalshi's matching.
FAQ
Do I need a key to read Kalshi's market data?
No. Series, events, markets and order books are public. The WebSocket needs a signed connection, even for market data.
Does Kalshi have a demo API?
Yes, at https://external-api.demo.kalshi.co/trade-api/v2, with mock funds and its own sign-up.
Why does the order book only show bids?
Because Yes and No mirror each other. An offer to sell Yes at 57¢ is the same as a bid for No at 43¢, so Kalshi lists both as bids.
Is there an official Kalshi Python SDK?
The kalshi-python package exists on PyPI, but the current release we found is about a year old and uses the older order format, and we couldn't confirm who maintains it. The docs' samples work without it.
Which key type should I use?
Ed25519, which Kalshi recommends. Use RSA only if your client can't do anything else.