Skip to main content
TL;DR — fetch open markets from Kalshi
import httpx

resp = httpx.get(
    "https://trading-api.kalshi.com/trade-api/v2/markets",
    headers=kalshi_auth_headers("GET", "/trade-api/v2/markets"),
    params={"limit": 10, "status": "open"},
)
markets = resp.json()["markets"]
for m in markets:
    print(f"{m['ticker']}  YES: {m['yes_bid']}  NO: {100 - m['yes_ask']}  Vol: {m['volume']}")

What this page covers

  • What Kalshi is and how its markets work
  • Ticker format and how to read market identifiers
  • RSA-PSS authentication flow with working code
  • Core REST endpoints for market data, orders, and portfolio
  • Market response format and price interpretation
  • Rate limits and fee structure
  • Python SDK options and HTTP client patterns
  • What Kalshi’s API does not provide out of the box
  • How to layer AI analysis and trading signals on top of raw Kalshi data

Overview

Kalshi is the first CFTC-regulated prediction market exchange in the United States. It launched in 2021 and offers binary event contracts that settle to 1(yes)or1 (yes) or 0 (no). Traders buy and sell contracts representing the probability of real-world events. Markets cover a wide range of categories:
CategoryExample markets
EconomicsFed rate decisions, CPI prints, GDP growth, jobless claims
PoliticsElection outcomes, congressional votes, government shutdowns
CryptoBitcoin and Ethereum price milestones
WeatherTemperature records, hurricane landfalls
EntertainmentAward shows, box office results, streaming top 10
SportsNFL, NBA, UFC outcomes
All API documentation lives at trading-api.kalshi.com. The base URL for all API requests is:
https://trading-api.kalshi.com/trade-api/v2
Prices on Kalshi are expressed in cents (integers from 1 to 99), representing the implied probability. A YES price of 62 means the market prices the event at roughly 62% likely.

Ticker format

Every Kalshi market has a ticker that encodes the series and expiration:
KXFED-26MAR19
^     ^
|     └── Expiration: March 19, 2026
└── Series prefix: KXFED = Federal Reserve rate decisions
Common series prefixes:
PrefixCategory
KXFEDFederal Reserve rate decisions
KXBTCBitcoin price milestones
KXETHEthereum price milestones
KXINFLATIONCPI / inflation
KXGDPGDP growth
KXJOBLESSWeekly jobless claims
KXNFLMVPNFL MVP
KXUFCUFC fight outcomes
KXNETFLIXNetflix Top 10
KXOSCARAcademy Awards
A ticker like KXBTC-26MAR14-100000 means: Bitcoin price series, expiring March 14, 2026, with a strike of $100,000. You can list all active series by querying the events endpoint:
GET /trade-api/v2/events

Authentication

Kalshi uses RSA-PSS signed request headers. There is no simple API key header; every request must be cryptographically signed.

Step 1: Generate an RSA key pair

openssl genrsa -out kalshi_private_key.pem 4096
openssl rsa -in kalshi_private_key.pem -pubout -out kalshi_public_key.pem

Step 2: Upload your public key

Log in to your Kalshi account at kalshi.com, go to Settings > API Keys, and upload the contents of kalshi_public_key.pem. Kalshi gives you back an API Key ID (a UUID).

Step 3: Sign each request

Every API request includes three headers:
HeaderValue
KALSHI-ACCESS-KEYYour API Key ID (UUID from step 2)
KALSHI-ACCESS-TIMESTAMPCurrent time in milliseconds since epoch
KALSHI-ACCESS-SIGNATURERSA-PSS signature of timestamp + method + path
The signing message is the concatenation of timestamp (as string), the HTTP method (GET, POST, etc.), and the request path (including query string for GET requests).
import time
import base64
import httpx
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

# Load your private key once at startup
with open("kalshi_private_key.pem", "rb") as f:
    private_key = serialization.load_pem_private_key(f.read(), password=None)

API_KEY_ID = "YOUR_KALSHI_API_KEY_ID"
BASE_URL = "https://trading-api.kalshi.com"


def kalshi_auth_headers(method: str, path: str) -> dict:
    """Build signed headers for a Kalshi API request."""
    timestamp = str(int(time.time() * 1000))
    message = f"{timestamp}{method}{path}".encode()

    signature = private_key.sign(
        message,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH,
        ),
        hashes.SHA256(),
    )

    return {
        "KALSHI-ACCESS-KEY": API_KEY_ID,
        "KALSHI-ACCESS-TIMESTAMP": timestamp,
        "KALSHI-ACCESS-SIGNATURE": base64.b64encode(signature).decode(),
        "Content-Type": "application/json",
    }


# Example: list open markets
path = "/trade-api/v2/markets"
resp = httpx.get(
    f"{BASE_URL}{path}",
    headers=kalshi_auth_headers("GET", path),
    params={"limit": 10, "status": "open"},
)
print(resp.json())
The signing path must match exactly what you send to the server. For GET requests with query parameters, sign the path without the query string. The params are passed separately.

Core API endpoints

List markets

Retrieve a paginated list of markets, filtered by status, series, or cursor.
GET /trade-api/v2/markets
Query parameters:
ParameterTypeDescription
limitintegerResults per page (max 200)
cursorstringPagination cursor from previous response
statusstringopen, closed, determined
series_tickerstringFilter by series (e.g., KXFED)
event_tickerstringFilter by parent event
path = "/trade-api/v2/markets"
resp = httpx.get(
    f"{BASE_URL}{path}",
    headers=kalshi_auth_headers("GET", path),
    params={"limit": 20, "status": "open"},
)
data = resp.json()
for market in data["markets"]:
    print(f"{market['ticker']}  {market['title']}  YES: {market['yes_bid']}")
# Paginate: pass data["cursor"] in the next request

Get a single market

GET /trade-api/v2/markets/{ticker}
ticker = "KXFED-26MAR19"
path = f"/trade-api/v2/markets/{ticker}"
resp = httpx.get(f"{BASE_URL}{path}", headers=kalshi_auth_headers("GET", path))
market = resp.json()["market"]
print(f"Title: {market['title']}")
print(f"YES bid/ask: {market['yes_bid']}/{market['yes_ask']}")
print(f"Volume: {market['volume']}")

Get the order book

GET /trade-api/v2/markets/{ticker}/orderbook
Returns the full order book with bids and asks at each price level. Useful for assessing liquidity before placing larger orders.
ticker = "KXFED-26MAR19"
path = f"/trade-api/v2/markets/{ticker}/orderbook"
resp = httpx.get(f"{BASE_URL}{path}", headers=kalshi_auth_headers("GET", path))
book = resp.json()["orderbook"]
print(f"Best YES bid: {book['yes'][-1][0]} ({book['yes'][-1][1]} contracts)")
print(f"Best NO bid: {book['no'][-1][0]} ({book['no'][-1][1]} contracts)")

Place an order

POST /trade-api/v2/portfolio/orders
path = "/trade-api/v2/portfolio/orders"
order = {
    "ticker": "KXFED-26MAR19",
    "action": "buy",
    "side": "yes",
    "type": "limit",
    "count": 10,       # Number of contracts
    "yes_price": 62,   # Limit price in cents
}
resp = httpx.post(
    f"{BASE_URL}{path}",
    headers=kalshi_auth_headers("POST", path),
    json=order,
)
result = resp.json()["order"]
print(f"Order ID: {result['order_id']}")
print(f"Status: {result['status']}")
Order parameters:
FieldTypeRequiredDescription
tickerstringYesMarket ticker
actionstringYesbuy or sell
sidestringYesyes or no
typestringYeslimit or market
countintegerYesNumber of contracts
yes_priceintegerFor limitLimit price in cents (1-99)
Kalshi requires a funded account to place orders. Deposits are made via ACH bank transfer or wire from the Kalshi web app.

Other portfolio endpoints

EndpointMethodDescription
/trade-api/v2/portfolio/positionsGETAll open positions (ticker, yes/no counts, average price)
/trade-api/v2/portfolio/balanceGETAccount balance in cents (available_balance, total_deposited)
/trade-api/v2/portfolio/ordersGETOrder history with status and fill details
/trade-api/v2/portfolio/settlementsGETSettlement history for resolved markets
All portfolio endpoints use the same RSA-PSS authentication headers.

Market data format

A market object from the Kalshi API looks like this:
{
  "ticker": "KXFED-26MAR19",
  "event_ticker": "KXFED-26MAR19",
  "title": "Will the Fed cut rates at the March 2026 meeting?",
  "subtitle": "Federal Reserve",
  "yes_bid": 61,
  "yes_ask": 63,
  "no_bid": 37,
  "no_ask": 39,
  "last_price": 62,
  "volume": 48250,
  "volume_24h": 12840,
  "open_interest": 34000,
  "status": "open",
  "result": "",
  "expiration_time": "2026-03-19T18:00:00Z",
  "close_time": "2026-03-19T18:00:00Z"
}
Key fields:
FieldTypeDescription
tickerstringUnique market identifier (e.g., KXFED-26MAR19)
titlestringHuman-readable question the market resolves on
yes_bidintegerBest bid for YES contracts, in cents
yes_askintegerBest ask for YES contracts, in cents
no_bidintegerBest bid for NO contracts, in cents
no_askintegerBest ask for NO contracts, in cents
last_priceintegerLast traded price, in cents
volumeintegerTotal contracts traded (lifetime)
volume_24hintegerContracts traded in the last 24 hours
open_interestintegerTotal open (unsettled) contracts
statusstringopen, closed, or determined
resultstringEmpty while open; yes or no after determination
expiration_timestringISO 8601 timestamp when the market resolves

Price interpretation

All prices are integers from 1 to 99 representing cents:
  • A YES bid of 61 means someone is willing to pay $0.61 per contract
  • A YES ask of 63 means someone is selling at $0.63 per contract
  • The spread is 63 - 61 = 2 cents
  • YES + NO prices always sum to approximately 100 (minus the spread)
  • Contracts settle at 1.00(YESwins)or1.00 (YES wins) or 0.00 (NO wins)

Rate limits and fees

Rate limits

Kalshi enforces approximately 10 requests per second per API key. The limits are not published precisely, but exceeding them returns a 429 status code. Back off with exponential retry when you see it.

Fee structure

Order typeFee
Maker (adds liquidity to the order book)Free
Taker (crosses the spread)0.07 * price * (1 - price) per contract
The taker fee is maximized at the midpoint: 0.07 * 0.50 * 0.50 = $0.0175 per contract. It decreases as prices approach 0 or 100. Example fees:
YES priceTaker fee per contract
10 cents$0.0063
25 cents$0.0131
50 cents$0.0175
75 cents$0.0131
90 cents$0.0063
Maker orders (limit orders that don’t immediately fill) are free. If you can wait for your price, you pay zero fees. This is a significant advantage for algorithmic traders.
Deposits are made via ACH (free, 3-5 days) or wire (25,sameday).Minimumdepositis25, same-day). Minimum deposit is 1. Withdrawals via ACH are free.

Python SDK options

There is no official first-party Python SDK from Kalshi. Here are your options:
OptionProsCons
httpx + custom authFull control, async support, no extra dependenciesYou manage auth signing yourself
requests + custom authFamiliar API, synchronousNo async, slightly more verbose
kalshi-python (community)Pre-built auth, basic REST wrapperNot always up-to-date, limited features
Most production systems use httpx with a reusable auth helper. Wrap the kalshi_auth_headers() function from the authentication section into a client class that handles signing automatically:
class KalshiClient:
    BASE_URL = "https://trading-api.kalshi.com"

    def __init__(self, api_key_id: str, private_key_path: str):
        self.api_key_id = api_key_id
        with open(private_key_path, "rb") as f:
            self.private_key = serialization.load_pem_private_key(f.read(), password=None)
        self.http = httpx.Client(base_url=self.BASE_URL, timeout=30)

    def _sign(self, method: str, path: str) -> dict:
        timestamp = str(int(time.time() * 1000))
        message = f"{timestamp}{method}{path}".encode()
        signature = self.private_key.sign(
            message,
            padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
            hashes.SHA256(),
        )
        return {
            "KALSHI-ACCESS-KEY": self.api_key_id,
            "KALSHI-ACCESS-TIMESTAMP": timestamp,
            "KALSHI-ACCESS-SIGNATURE": base64.b64encode(signature).decode(),
        }

    def get_market(self, ticker: str) -> dict:
        path = f"/trade-api/v2/markets/{ticker}"
        return self.http.get(path, headers=self._sign("GET", path)).json()["market"]

    def list_markets(self, status: str = "open", limit: int = 20) -> list:
        path = "/trade-api/v2/markets"
        return self.http.get(path, headers=self._sign("GET", path),
                             params={"status": status, "limit": limit}).json()["markets"]

    def place_order(self, ticker: str, side: str, count: int, price: int) -> dict:
        path = "/trade-api/v2/portfolio/orders"
        body = {"ticker": ticker, "action": "buy", "side": side,
                "type": "limit", "count": count, "yes_price": price}
        return self.http.post(path, headers=self._sign("POST", path), json=body).json()["order"]

# Usage
client = KalshiClient("YOUR_KALSHI_API_KEY_ID", "kalshi_private_key.pem")
for m in client.list_markets(limit=5):
    print(f"{m['ticker']}  {m['title']}  YES: {m['yes_bid']}")

What Kalshi’s API does not provide

Kalshi gives you raw market data and order execution. It does not include:
  • Independent probability estimates. You get the market price, which reflects the crowd’s view. There is no built-in second opinion.
  • Cross-platform data. You cannot compare Kalshi prices with Polymarket or Robinhood prices through the Kalshi API.
  • Trading signals or recommendations. The API tells you what the market thinks, not whether you should trade.
  • Position sizing. No guidance on how much to allocate to a given position based on edge and bankroll.
  • Arbitrage detection. No way to find price divergences between platforms.
  • Causal analysis. No breakdown of which real-world factors are driving a market’s price.
  • Historical trend intelligence. Order book snapshots and trade history are available, but there is no analyzed trend data or price movement context.
These gaps are where an intelligence layer adds the most value.

Adding AI analysis to Kalshi data

Raw Kalshi data tells you what the market thinks. An intelligence layer tells you what the market might be missing. Here is a complete workflow: pull a market from Kalshi via Rekko’s normalized API, run an AI analysis, and get a trading signal with position sizing.

Step 1: Look up the market

Rekko normalizes Kalshi data into a consistent format alongside Polymarket and Robinhood. Prices are returned as decimals (0.0 to 1.0) instead of raw cents.
import httpx

rekko = httpx.Client(
    base_url="https://api.rekko.ai/v1",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)

# Fetch normalized market data
market = rekko.get("/markets/kalshi/KXFED-26MAR19").json()
print(f"Market: {market['title']}")
print(f"YES price: {market['yes_price']}")
print(f"Volume (24h): ${market['volume_24h']:,.0f}")

Step 2: Run an AI analysis

The analysis pipeline researches the market question, synthesizes findings from multiple sources, and produces an independent probability estimate. It runs for 30-90 seconds.
import time

# Trigger the analysis
resp = rekko.post("/markets/kalshi/KXFED-26MAR19/analyze")
analysis_id = resp.json()["analysis_id"]

# Poll for completion
while True:
    status = rekko.get(
        f"/markets/kalshi/KXFED-26MAR19/analyze/{analysis_id}/status"
    ).json()
    if status["status"] == "complete":
        break
    time.sleep(5)

# Get the completed analysis
analysis = rekko.get("/markets/kalshi/KXFED-26MAR19/analysis").json()
print(f"AI probability estimate: {analysis['probability']:.0%}")
print(f"Market price: {market['yes_price']:.0%}")
print(f"Edge: {analysis['edge']:+.0%}")
print(f"Recommendation: {analysis['recommendation']}")
See the quickstart for the full trigger-poll-get pattern in all three languages.

Step 3: Get a trading signal with position sizing

A signal combines the analysis with Kelly criterion sizing to tell you how much to allocate.
signal = rekko.post(
    "/signals",
    params={"wait": "true"},
    json={"platform": "kalshi", "market_id": "KXFED-26MAR19"},
).json()
print(f"Recommendation: {signal['recommendation']}")
print(f"Edge: {signal['edge']:+.1%}")
print(f"Kelly fraction: {signal['size_pct']:.1%} of bankroll")
print(f"Risk rating: {signal['risk_rating']}")

Full comparison: raw Kalshi data vs. Rekko intelligence

CapabilityKalshi APIKalshi + Rekko
Real-time market pricesYesYes (normalized)
Order book depthYesYes
Order placement and executionYesVia Kalshi directly
Independent probability estimateNoYes
Edge detectionNoYes
Kelly-criterion position sizingNoYes
Cross-platform price comparisonNoYes (Kalshi + Polymarket + Robinhood)
Arbitrage detectionNoYes
Causal factor decompositionNoYes
Resolution timing intelligenceNoYes
Market screening and filteringBasic (by status)Advanced (by edge, volume, category, freshness)

Error handling

Kalshi returns standard HTTP status codes:
StatusMeaning
200Success
400Invalid request (bad parameters)
401Authentication failed (bad signature or expired timestamp)
403Insufficient permissions
404Market or resource not found
429Rate limit exceeded
500Server error
Implement exponential backoff for 429 and 5xx responses. Most HTTP libraries (including httpx with httpx.HTTPTransport(retries=3)) can handle this automatically.

What’s next

Polymarket API guide

The same walkthrough for Polymarket’s crypto-native API.

Build a trading bot

End-to-end Python bot using Kalshi data with AI-powered signals.

API comparison

Side-by-side comparison of Kalshi, Polymarket, and Robinhood APIs.

Cross-platform arbitrage

Find price divergences across platforms and size trades with edge.

Rekko API reference

Full endpoint documentation with interactive playground.