kalshi ยท v1.0.1 ยท 2026-05-21 ยท sha256 3bceecbcbe31f8ea
kalshi v1.0.1F
Immutable. This exact content is served forever at /api/v1/blob/3bceecbcbe31f8ea.
---
name: kalshi
version: 1.0.1
description: |
Kalshi prediction markets: binary event contracts on politics, economy, sports, weather.
Use when trading or browsing US regulated event markets (e.g. CPI above 3%, NHL Edmonton vs Florida, jobs report > 200k, election odds).
allowed-tools: Bash, Read, Write, WebFetch
homepage: https://kalshi.com
metadata:
openclaw:
emoji: "๐ฏ"
requires:
env:
- KALSHI_ACCESS_KEY
- KALSHI_PRIVATE_KEY
primaryEnv: KALSHI_ACCESS_KEY
homepage: https://kalshi.com
tags:
- prediction-market
- event-contracts
- binary-options
- trading
- orderbook
- politics
- economics
- weather
- sports
- crypto
- implied-probability
- cftc-regulated
- elections
- forecasting
---
# Kalshi API
Trade and query the first CFTC-regulated prediction market exchange. Binary yes/no contracts on real-world events priced 1-99 cents.
**Base URL:** `https://external-api.kalshi.com/trade-api/v2`
**Demo URL:** `https://external-api.demo.kalshi.co/trade-api/v2`
Get your API key at https://kalshi.com/account/api-keys (Premier or Market Maker tier required)
## Key Concepts
### Hierarchy: Series > Events > Markets
- **Series** โ recurring event templates (e.g., "Monthly Jobs Report", "Weekly Jobless Claims")
- **Events** โ specific instances within a series (e.g., "May 2026 Jobs Report")
- **Markets** โ individual binary outcomes within an event (e.g., "Will jobs added be above 200k?")
### Binary Contract Pricing
- Prices are in **cents** (1-99), representing implied probability
- A Yes contract at 65c = market implies 65% probability
- Yes bid at price X is equivalent to No ask at (100 - X) โ orderbooks show Yes bids and No bids only
- Settlement: pays $1.00 (100 cents) if Yes, $0 if No
- **All monetary values (balance, prices, settlements) are in cents**
### Ticker Format
- **Series:** `KXBTC`, `KXJOBLESS`, `KXINX`
- **Event:** `KXBTC-25MAY30` (series + date)
- **Market:** `KXBTC-25MAY30-T100000` (event + threshold/outcome)
**Sports game tickers** follow a different pattern: `{SERIES}-{YYMONDD}{TEAM1}{TEAM2}-{TEAM}`
- Example: `KXNHLGAME-25MAY12EDMORL-EDM` (NHL, May 12 2025, Edmonton vs Orlando, Edmonton to win)
- Each game event has **two mutually exclusive YES markets** โ one per team
### CRITICAL: Series-Based Market Navigation
**DO NOT use `/markets?keyword=` for sports, elections, or any series-based category.** It only surfaces multi-game parlay bundles, not actual game-level markets. Searching "NHL", "Fulham", "Premier League" etc. returns zero or irrelevant results.
**Always navigate: Series โ Events โ Markets.** Game-level markets live under the series/event hierarchy and must be accessed via `/events?series_ticker=KXXX`. Also, `/markets/{ticker}` may return 404 for game markets โ bid/ask prices only appear in the event endpoint response (`/events/{event_ticker}` with `with_nested_markets=true`).
This pattern applies to **all series-based categories**, not just sports:
- **Sports games** โ NHL, NBA, NFL, MLB, EPL, etc.
- **Political races** โ individual candidate markets within multi-candidate events
- **Company-specific markets** โ earnings, CEO changes within a series
- **Recurring economic data** โ jobs reports, jobless claims, CPI, etc.
**Known Sports Series Tickers:**
| Sport | Series Ticker | Example |
|-------|---------------|---------|
| NHL | `KXNHLGAME` | `KXNHLGAME-25MAY12EDMORL` |
| NBA | `KXNBAGAME` | `KXNBAGAME-25MAY12BOSLAL` |
| NFL | `KXNFLGAME` | `KXNFLGAME-25SEP07KCDET` |
| MLB | `KXMLBGAME` | `KXMLBGAME-25MAY12NYYLAD` |
| EPL (Premier League) | `KXEPLGAME` | `KXEPLGAME-25MAY12FULARS` |
**Correct workflow for sports:**
```bash
# 1. List open games for a sport
curl -s "https://external-api.kalshi.com/trade-api/v2/events?series_ticker=KXNHLGAME&status=open&with_nested_markets=true"
# 2. Get specific game with live bid/ask
curl -s "https://external-api.kalshi.com/trade-api/v2/events/KXNHLGAME-25MAY12EDMORL?with_nested_markets=true"
# WRONG โ do not do this:
# curl -s "https://external-api.kalshi.com/trade-api/v2/markets?keyword=NHL" โ returns parlays, not games
```
## How to Call
Kalshi uses **RSA key-pair signature authentication**. Three headers are required on authenticated requests:
| Header | Value |
|--------|-------|
| `KALSHI-ACCESS-KEY` | API key ID (`$KALSHI_ACCESS_KEY`) |
| `KALSHI-ACCESS-SIGNATURE` | RSA-PSS signature of `timestamp + method + path` |
| `KALSHI-ACCESS-TIMESTAMP` | Unix timestamp (ms) |
**Public GET endpoints** (markets, events, orderbooks) can be called without auth headers.
```bash
# Public endpoint โ no auth needed
curl -s "https://external-api.kalshi.com/trade-api/v2/markets?limit=10&status=open"
```
For authenticated endpoints, use direct RSA-PSS signing (**not** the `kalshi_python` SDK โ it doesn't work):
```python
import os, time, base64, requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from dotenv import load_dotenv
load_dotenv('/data/workspace/.env')
BASE_URL = "https://api.elections.kalshi.com"
def kalshi_headers(method: str, path: str) -> dict:
raw_pk = os.environ["KALSHI_PRIVATE_KEY"].strip().replace(' ', '')
pem = f"-----BEGIN RSA PRIVATE KEY-----\n{raw_pk}\n-----END RSA PRIVATE KEY-----"
private_key = serialization.load_pem_private_key(pem.encode(), password=None, backend=default_backend())
ts = str(int(time.time() * 1000))
msg = (ts + method + path).encode() # path must NOT include query string
sig = private_key.sign(msg, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH), hashes.SHA256())
return {
"KALSHI-ACCESS-KEY": os.environ["KALSHI_ACCESS_KEY"].strip(),
"KALSHI-ACCESS-TIMESTAMP": ts,
"KALSHI-ACCESS-SIGNATURE": base64.b64encode(sig).decode(),
"Content-Type": "application/json"
}
path = "/trade-api/v2/portfolio/balance"
resp = requests.get(BASE_URL + path, headers=kalshi_headers("GET", path))
print(resp.json()) # {"balance": 5000, ...} โ balance is in cents
```
**Signing gotchas:**
- **PSS not PKCS1v15** โ PKCS1v15 padding returns 401. Must use PSS with SHA256.
- **Strip query strings before signing** โ signature is computed on the bare path only (e.g. `/trade-api/v2/portfolio/balance`), not `/trade-api/v2/portfolio/balance?param=value`. Including query string will 401.
- **Key env var format** โ `KALSHI_PRIVATE_KEY` should be stored as the raw base64 body with no PEM headers. The helper wraps it automatically.
### API Spec
Full OpenAPI spec: https://docs.kalshi.com/openapi.yaml
## Intent Routing
Map user intent to the right endpoint. All paths are relative to the base URL.
### Events
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/events` | `limit` (1-200), `cursor`, `status`, `series_ticker`, `with_nested_markets` | List events (excludes multivariate) |
| GET | `/events/{event_ticker}` | `event_ticker`, `with_nested_markets` | Get specific event |
| GET | `/events/{event_ticker}/metadata` | `event_ticker` | Event metadata only |
| GET | `/events/multivariate` | `limit`, `cursor`, `series_ticker`, `collection_ticker`, `with_nested_markets` | List multivariate (combo) events |
### Markets
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/markets` | `limit`, `cursor`, `status`, `ticker`, `event_ticker`, `series_ticker`, `min_close_ts`, `max_close_ts` | List/filter markets |
| GET | `/markets/{ticker}` | `ticker` | Get specific market details |
| GET | `/markets/{ticker}/orderbook` | `ticker`, `depth` | Current orderbook (yes bids + no bids) |
| GET | `/markets/orderbooks` | `tickers` (array, max 100) | Batch orderbooks |
| GET | `/markets/trades` | `ticker`, `limit`, `cursor`, `min_ts`, `max_ts` | Trades across markets |
### Candlesticks
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/series/{series_ticker}/markets/{ticker}/candlesticks` | `series_ticker`, `ticker`, `start_ts`, `end_ts`, `period_interval` | Market-level candles |
| GET | `/series/{series_ticker}/events/{ticker}/candlesticks` | `series_ticker`, `ticker`, `start_ts`, `end_ts`, `period_interval` | Aggregated event-level candles |
| GET | `/markets/candlesticks` | `market_tickers`, `start_ts`, `end_ts`, `period_interval` | Batch candlesticks |
`period_interval` values: `1` (1 min), `60` (1 hour), `1440` (1 day)
### Forecast & Live Data
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/series/{series_ticker}/events/{ticker}/forecast_percentile_history` | `percentiles` (0-10000), `start_ts`, `end_ts`, `period_interval` | Historical forecast percentiles |
| GET | `/live_data/milestone/{milestone_id}` | `milestone_id`, `include_player_stats` | Live milestone data |
| GET | `/live_data/batch` | `milestone_ids` (array, max 100), `include_player_stats` | Batch live data |
| GET | `/live_data/game-stats/{milestone_id}` | `milestone_id` | Play-by-play stats (football, basketball, soccer, hockey, baseball) |
### Series
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/series/{series_ticker}` | `series_ticker`, `include_volume` | Get series template |
| GET | `/series` | `category`, `tags`, `include_volume`, `min_updated_ts` | List all series |
### Exchange Info
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/exchange/status` | โ | Current exchange status |
| GET | `/exchange/schedule` | โ | Operating hours |
| GET | `/exchange/announcements` | โ | Platform announcements |
| GET | `/exchange/user_data_timestamp` | โ | Data sync timestamp |
| GET | `/series/fee_changes` | `series_ticker`, `show_historical` | Fee change history |
### Search & Discovery
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/search/tags_by_categories` | โ | Tags organized by series categories |
| GET | `/search/filters_by_sport` | โ | Filters organized by sport |
### Orders (Authenticated)
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/portfolio/orders` | `ticker`, `event_ticker`, `status`, `limit`, `cursor`, `subaccount` | List user orders |
| GET | `/portfolio/orders/{order_id}` | `order_id` | Get specific order |
| POST | `/portfolio/orders` | Body: `ticker`, `action`, `side`, `type`, `count`, `yes_price`/`no_price` | Create order |
| DELETE | `/portfolio/orders/{order_id}` | `order_id`, `subaccount` | Cancel order |
| POST | `/portfolio/orders/{order_id}/amend` | `order_id`, Body: new `count`/`price` | Amend order |
| POST | `/portfolio/orders/{order_id}/decrease` | `order_id`, Body: reduction amount | Decrease order count |
| POST | `/portfolio/orders/batched` | Body: array of orders | Batch create (max size scales with tier) |
| DELETE | `/portfolio/orders/batched` | Body: array of order IDs | Batch cancel |
| GET | `/portfolio/orders/queue_positions` | `market_tickers`, `event_ticker`, `subaccount` | All resting order queue positions |
| GET | `/portfolio/orders/{order_id}/queue_position` | `order_id` | Specific order queue position |
### Orders V2 โ Event-Market Orders (Authenticated, Fixed-Point)
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| POST | `/portfolio/events/orders` | Body: event ticker, market side, price (fixed-point), count | Create V2 order |
| POST | `/portfolio/events/orders/batched` | Body: array of V2 orders | Batch create V2 |
| DELETE | `/portfolio/events/orders/{order_id}` | `order_id`, `subaccount` | Cancel V2 order |
| DELETE | `/portfolio/events/orders/batched` | Body: array of order IDs | Batch cancel V2 |
| POST | `/portfolio/events/orders/{order_id}/amend` | `order_id`, Body: new count/price | Amend V2 order |
| POST | `/portfolio/events/orders/{order_id}/decrease` | `order_id`, Body: new remaining count | Decrease V2 order |
### Order Groups (Authenticated)
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/portfolio/order_groups` | `subaccount` | List all order groups |
| POST | `/portfolio/order_groups/create` | Body: contracts limit | Create order group |
| GET | `/portfolio/order_groups/{id}` | `id`, `subaccount` | Get specific group |
| DELETE | `/portfolio/order_groups/{id}` | `id`, `subaccount` | Delete group & cancel all orders |
| PUT | `/portfolio/order_groups/{id}/limit` | `id`, Body: new limit | Update contracts limit |
| PUT | `/portfolio/order_groups/{id}/trigger` | `id`, `subaccount` | Trigger group (cancel all orders) |
| PUT | `/portfolio/order_groups/{id}/reset` | `id`, `subaccount` | Reset matched contracts counter |
### Portfolio (Authenticated)
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/portfolio/balance` | `subaccount` | Balance & portfolio value (in cents) |
| GET | `/portfolio/positions` | `cursor`, `limit`, `count_filter`, `ticker`, `event_ticker`, `subaccount` | User positions |
| GET | `/portfolio/fills` | `ticker`, `order_id`, `min_ts`, `max_ts`, `limit`, `cursor`, `subaccount` | All trade fills |
| GET | `/portfolio/settlements` | `limit`, `cursor`, `ticker`, `event_ticker`, `min_ts`, `max_ts`, `subaccount` | Settlement history |
| GET | `/portfolio/deposits` | `limit`, `cursor` | Deposit history |
| GET | `/portfolio/withdrawals` | `limit`, `cursor` | Withdrawal history |
| GET | `/portfolio/summary/total_resting_order_value` | โ | Total resting order value (FCM only) |
### Subaccounts (Authenticated โ Institutions/Market Makers)
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| POST | `/portfolio/subaccounts` | โ | Create subaccount (max 32) |
| POST | `/portfolio/subaccounts/transfer` | Body: from, to, amount | Transfer between subaccounts |
| GET | `/portfolio/subaccounts/balances` | โ | All subaccount balances |
| GET | `/portfolio/subaccounts/transfers` | `limit`, `cursor` | Transfer history |
| GET | `/portfolio/subaccounts/netting` | โ | Netting settings |
| PUT | `/portfolio/subaccounts/netting` | Body: subaccount, enabled | Update netting |
### RFQ โ Request for Quote (Authenticated)
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/communications/rfqs` | `cursor`, `limit` (1-100), `event_ticker`, `market_ticker`, `status`, `user_filter` | List RFQs |
| POST | `/communications/rfqs` | Body: market ticker, side, count | Create RFQ (max 100 open) |
| GET | `/communications/rfqs/{rfq_id}` | `rfq_id` | Get specific RFQ |
| DELETE | `/communications/rfqs/{rfq_id}` | `rfq_id` | Delete RFQ |
| GET | `/communications/quotes` | `cursor`, `limit` (1-500), `event_ticker`, `market_ticker`, `status`, `rfq_id` | List quotes |
| POST | `/communications/quotes` | Body: RFQ ID, side, price, count | Create quote response |
| GET | `/communications/quotes/{quote_id}` | `quote_id` | Get specific quote |
| DELETE | `/communications/quotes/{quote_id}` | `quote_id` | Delete quote |
| PUT | `/communications/quotes/{quote_id}/accept` | `quote_id` | Accept quote |
| PUT | `/communications/quotes/{quote_id}/confirm` | `quote_id` | Confirm quote (starts execution timer) |
| GET | `/communications/id` | โ | Get user's communications ID |
### API Keys (Authenticated)
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/api_keys` | โ | List all API keys |
| POST | `/api_keys` | Body: public key, name | Create key with user RSA public key |
| POST | `/api_keys/generate` | Body: name | Generate key pair automatically |
| DELETE | `/api_keys/{api_key}` | `api_key` | Delete API key |
### Account (Authenticated)
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/account/limits` | โ | API tier rate limits |
| GET | `/account/endpoint_costs` | โ | Non-default endpoint token costs |
### Historical Data
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/historical/cutoff-timestamps` | โ | Boundary between live and historical data |
| GET | `/historical/markets/{ticker}` | `ticker` | Specific historical market |
| GET | `/historical/markets` | mutually exclusive filters | Historical markets |
| GET | `/historical/markets/{ticker}/candlesticks` | `ticker`, `start_ts`, `end_ts`, `period_interval` | Archived candlesticks |
| GET | `/historical/orders` | filters | Archived orders |
| GET | `/historical/fills` | filters | All historical fills |
| GET | `/historical/trades` | filters | All historical trades |
### Milestones & Structured Targets
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/milestones/{id}` | `id` | Specific milestone |
| GET | `/milestones` | RFC3339 start date filters | List milestones |
| GET | `/structured-targets/{id}` | `id` | Specific structured target |
| GET | `/structured-targets` | pagination (max 2000) | List targets |
### Multivariate Collections
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/multivariate/collections` | โ | List multivariate event collections |
| GET | `/multivariate/collections/{ticker}` | `ticker` | Specific collection |
| POST | `/multivariate/collections/{ticker}/markets` | `ticker`, Body: market params | Create market in collection (5000/week limit) |
### Incentives
| Method | Endpoint | Primary Params | Description |
|--------|----------|----------------|-------------|
| GET | `/incentives` | optional filters | List incentive programs |
## Rate Limits
Token-based system that scales by API tier (Standard, Premier, Market Maker).
| Endpoint Type | Default Cost |
|---------------|-------------|
| Most endpoints | 10 tokens |
| GetOrder | 2 tokens |
| CancelOrder | 2 tokens |
| CreateQuote / DeleteQuote | 2 tokens |
| Batch operations | N x per-item cost |
Check your limits with `GET /account/limits`. Batch operation max size scales with your tier's write budget.
## Pagination
All list endpoints use **cursor-based pagination**. The response includes a `cursor` field โ pass it back as a query param to get the next page.
| Param | Description |
|-------|-------------|
| `limit` | Max results per page (varies by endpoint, typically 1-200) |
| `cursor` | Opaque cursor from previous response for next page |
## WebSocket Channels
WebSocket connection at `wss://external-api.kalshi.com/trade-api/ws/v2` (auth required at handshake).
**Public channels:**
- Market Ticker โ price, volume, open interest updates
- Public Trades โ trade notifications
- Market & Event Lifecycle โ state changes, new markets/events
- Multivariate Market & Event Lifecycle โ MVE state changes
**Authenticated channels:**
- User Orders โ order created/updated notifications
- User Fills โ fill notifications
- Market Positions โ real-time position updates
- Order Group Updates โ lifecycle and limit notifications
- Communications โ RFQ and quote notifications
- Orderbook Updates โ incremental price level changes
## Order Fields Reference
| Field | Values | Description |
|-------|--------|-------------|
| `action` | `buy`, `sell` | Buy or sell contracts |
| `side` | `yes`, `no` | Which outcome side |
| `type` | `limit`, `market` | Order type |
| `count` | integer | Number of contracts |
| `yes_price` | 1-99 (cents) | Price for yes side |
| `no_price` | 1-99 (cents) | Price for no side |
| `expiration_ts` | unix timestamp | Optional order expiration |
| `sell_position_floor` | integer | Min position to keep when selling |
| `buy_max_cost` | integer (cents) | Max total cost for market buys |
## Safety Notes
- **All prices are in cents** (1-99). A price of 65 means $0.65 per contract, NOT $65.
- **Balance is in cents.** 10000 = $100.00.
- `POST /portfolio/orders` submits real orders on production. Always verify ticker, side, and price before placing.
- Batch operations execute atomically โ all succeed or all fail.
- **Demo environment** available at `https://external-api.demo.kalshi.co/trade-api/v2` for risk-free testing.
- Before placing orders, always confirm the market ticker, current price, and position size with the user.
- Max 200,000 open orders per user.
## Known Limitations
- **`/markets?keyword=` misses series-based markets** โ sports games, political races, and other series-based categories are invisible to keyword search. Always navigate via `/events?series_ticker=`. See "CRITICAL: Series-Based Market Navigation" above.
- **`/markets/{ticker}` returns 404 for game markets** โ bid/ask prices for sports and similar markets only appear in the event endpoint response. Use `/events/{event_ticker}?with_nested_markets=true` instead.
- **RSA signing required** for authenticated endpoints โ cannot use simple API key header like most REST APIs. Use the official Python/TypeScript SDK for signing.
- **Binary markets only** โ no multi-outcome contracts (except via multivariate events)
- **US-regulated** โ trading available to eligible US residents only
- **Market hours** โ markets have defined close times, check `close_ts` before trading
- **Historical data split** โ older data requires `/historical/*` endpoints, check `/historical/cutoff-timestamps` for the boundary
- **V1 vs V2 order endpoints** โ V2 uses fixed-point format, V1 uses cents. Both work but V2 is recommended for new integrations.
- **Subaccounts** โ only available to institutions and market makers (max 32 per user)
- **RFQ limit** โ max 100 open RFQs at a time
- **Multivariate market creation** โ max 5000 per week per collection