API + MCP live · talk to us

// PREDICTION-MARKET APIS

Kalshi API guide: place your first order

2026-07-16 · Mithril

Kalshi is the CFTC-regulated US prediction-market exchange, and its trade API is the most conventional of the major venues: REST endpoints, an API key, and signed requests. If you have integrated any traditional exchange API before, you will feel at home. This guide walks from zero to a filled order: auth, market discovery, reading the book, placing and managing an order, and the gotchas that catch almost everyone the first time.

Everything below targets the trade API at https://api.elections.kalshi.com/trade-api/v2. Kalshi also runs a demo environment — start there, and only point at production once your order lifecycle handling works.

Step 1: create an API key

In your Kalshi account settings, generate an API key. You get two things:

  • an access key ID (a UUID that identifies the key), and
  • an RSA private key that you download once and store yourself.

Kalshi never sees your private key again, so treat it like any other trading credential: keep it out of source control, load it from an environment variable or a secrets manager, and rotate it if it ever touches a log line.

Step 2: sign every request

Kalshi's auth is per-request signing, not a bearer token. Each request carries three headers:

  • KALSHI-ACCESS-KEY — your access key ID
  • KALSHI-ACCESS-TIMESTAMP — current time in milliseconds
  • KALSHI-ACCESS-SIGNATURE — an RSA-PSS signature over timestamp + method + path

In Python:

python
import base64
import time

import requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

BASE = "https://api.elections.kalshi.com"
ACCESS_KEY = "your-access-key-id"

with open("kalshi_private_key.pem", "rb") as f:
    private_key = serialization.load_pem_private_key(f.read(), password=None)

def kalshi_headers(method: str, path: str) -> dict:
    ts = str(int(time.time() * 1000))
    msg = (ts + method + path).encode()
    sig = private_key.sign(
        msg,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.DIGEST_LENGTH,
        ),
        hashes.SHA256(),
    )
    return {
        "KALSHI-ACCESS-KEY": ACCESS_KEY,
        "KALSHI-ACCESS-TIMESTAMP": ts,
        "KALSHI-ACCESS-SIGNATURE": base64.b64encode(sig).decode(),
    }

Two details bite people here. The signed path is the API path (for example /trade-api/v2/portfolio/orders) — check the docs for exactly what string to sign, because a mismatch produces an opaque auth error. And the timestamp must be close to server time; a skewed clock on your box means every request fails signature validation even though your code is correct.

Step 3: find a market

Kalshi organizes contracts as series → events → markets. A series is the recurring template ("Fed decision"), an event is one instance ("September 2026 meeting"), and a market is one binary contract inside it. Market discovery is unauthenticated-friendly and paginated:

python
resp = requests.get(
    f"{BASE}/trade-api/v2/markets",
    params={"status": "open", "limit": 100},
    headers=kalshi_headers("GET", "/trade-api/v2/markets"),
)
markets = resp.json()["markets"]

Each market has a human-readable ticker like FED-26SEP-T4.00, which is also its API identifier. Prices are integers in cents, 1–99. A yes price of 42 means the market implies roughly a 42% probability, and one contract pays $1.00 if the market resolves yes.

Step 4: read the order book

python
path = "/trade-api/v2/markets/FED-26SEP-T4.00/orderbook"
book = requests.get(BASE + path, headers=kalshi_headers("GET", path)).json()

The book is returned as arrays of [price, quantity] levels for the yes and no sides. One structural quirk: because every contract is binary, a bid on no is economically an offer on yes at 100 − price. If the book looks one-sided at first glance, you are probably forgetting to mirror the other side. For polling, the REST book is fine; for anything latency-sensitive, use the WebSocket orderbook channel instead (covered in rate limits, order types, and websockets).

Step 5: place your first order

Orders go to POST /trade-api/v2/portfolio/orders:

python
import uuid

order = {
    "ticker": "FED-26SEP-T4.00",
    "client_order_id": str(uuid.uuid4()),
    "action": "buy",
    "side": "yes",
    "type": "limit",
    "count": 10,
    "yes_price": 42,
}
path = "/trade-api/v2/portfolio/orders"
resp = requests.post(
    BASE + path, json=order, headers=kalshi_headers("POST", path)
)
print(resp.json())

Field by field:

FieldMeaning
tickerThe market you're trading
client_order_idYour idempotency key — always send one
actionbuy or sell
sideyes or no
typelimit or market
countNumber of contracts
yes_priceLimit price in cents (use no_price when working the no side)

The client_order_id deserves emphasis. If your POST times out, you do not know whether the order was accepted. Retrying with the same client_order_id is safe; retrying without one is how bots end up with double positions. Build this in from day one — more patterns in how to build a prediction-market bot in Python.

The order lifecycle

Once accepted, an order moves through a small state machine:

  1. Resting — a limit order sitting in the book, partially or fully unfilled.
  2. Executed — matched, fully filled.
  3. Canceled — pulled by you (DELETE /portfolio/orders/{order_id}) or expired.

Partial fills are normal in thin books: a 100-lot might fill 30 and rest 70. Track fills via GET /portfolio/fills or, better, the WebSocket fill channel, and reconcile against GET /portfolio/positions on a timer. Trusting your local view of state without periodic reconciliation is the classic slow-motion bug.

Fees: a formula, not a flat rate

Kalshi's standard taker fee is 0.07 × contracts × price × (1 − price) with price in dollars, rounded up to the next cent — while resting (maker) orders generally pay no trading fee on most markets. The formula peaks at 50¢ and shrinks toward the tails, so a fill at 50¢ costs meaningfully more in fees than the same size at 90¢. Price your quotes with this in mind: crossing the spread at midprice-ish levels is where the fee hurts most. Full breakdown in Kalshi fees explained, or plug your own numbers into the Kalshi fee calculator.

Common gotchas

  • Cents, not dollars. yes_price: 42 is 42 cents. Sending 0.42 fails validation — but a bot that formats prices for Polymarket (decimals) and reuses the code for Kalshi will produce nonsense.
  • The no side mirrors yes. Buying no at 58 is the same exposure as selling yes at 42. Pick one internal representation and convert at the edge.
  • Clock skew breaks auth. Signature errors that appear randomly are usually a drifting clock, not a bad key.
  • Rate limits are real. Kalshi enforces per-tier request limits; naive polling loops hit them fast. Batch reads, prefer WebSockets, and back off on 429s.
  • Expiration and settlement lag. A market can close for trading before it settles. Your PnL is not final until settlement, and your bot should distinguish "closed" from "settled".

Where this goes next

A single-venue Kalshi bot is a genuinely tractable project — the API is clean and well documented. The complexity arrives when you add Polymarket, because almost everything above is different there: auth, IDs, price formats, fees, error semantics. If you would rather trade both venues through one interface, Mithril exposes a single REST API with unified mkt_ IDs, fee-aware routing across Kalshi and Polymarket, and server-side risk limits — docs here.

APIs change. Details above reflect public documentation as of July 2026 — always confirm against the venues' own docs before trading real money.

One API + MCP for Kalshi and Polymarket

Fee-aware routing, unified market IDs, and hard server-side risk limits — live today, free during beta.