Kalshi Official API Reference (2026)
Source: docs.kalshi.com OpenAPI Spec: docs.kalshi.com/openapi.yaml Last Verified: 2026-01-16 Changelog: docs.kalshi.com/changelog
Base URLs
| Environment | REST API | WebSocket |
|---|---|---|
| Production | https://api.elections.kalshi.com/trade-api/v2 |
wss://api.elections.kalshi.com/trade-api/ws/v2 |
| Demo | https://demo-api.kalshi.co/trade-api/v2 |
wss://demo-api.kalshi.co/trade-api/ws/v2 |
Note: Despite the "elections" subdomain, the production API serves ALL Kalshi markets (economics, climate, tech, entertainment, etc.).
Authentication (REST + WebSocket)
Required Headers
| Header | Description |
|---|---|
KALSHI-ACCESS-KEY |
Your API Key ID (UUID format) |
KALSHI-ACCESS-TIMESTAMP |
Unix timestamp in milliseconds |
KALSHI-ACCESS-SIGNATURE |
Base64-encoded RSA-PSS signature |
Signature Algorithm
Message to sign: {timestamp}{HTTP_METHOD}{path}
Algorithm: RSA-PSS with SHA-256
Salt Length: PSS.DIGEST_LENGTH
Output: Base64 string
CRITICAL: The path excludes query parameters.
| Request | Signature Message |
|---|---|
GET /portfolio/orders?limit=5 |
1703123456789GET/trade-api/v2/portfolio/orders |
POST /portfolio/orders |
1703123456789POST/trade-api/v2/portfolio/orders |
| WebSocket connect | 1703123456789GET/trade-api/ws/v2 |
API Key Scopes (Dec 2025+)
Keys now support scopes field with read and write permissions.
Rate Limits
IMPORTANT: Rate limit tiers control request throughput. Endpoint availability can still depend on account permissions or API usage level (e.g., some API key management operations are restricted in the OpenAPI).
| Tier | Read/sec | Write/sec | Qualification |
|---|---|---|---|
| Basic | 20 | 10 | Completing signup |
| Advanced | 30 | 30 | Advanced API form |
| Premier | 100 | 100 | 3.75% monthly exchange volume + technical competency |
| Prime | 400 | 400 | 7.5% monthly exchange volume + technical competency |
Note: Per OpenAPI, creating API keys with a user-provided RSA public key (POST /api_keys) is restricted to
Premier or Market Maker API usage levels.
Write-Limited Operations ONLY
Only these count against write limits:
| Operation | Cost |
|---|---|
CreateOrder |
1 transaction |
BatchCreateOrders |
1 transaction per order item |
CancelOrder |
1 transaction |
BatchCancelOrders |
0.2 transactions per cancel |
AmendOrder |
1 transaction |
DecreaseOrder |
1 transaction |
Open Order Limit
Maximum: 200,000 open orders per user
Pagination
Cursor-Based Pagination
| Parameter | Description |
|---|---|
limit |
Page size (endpoint-specific default; commonly 100; endpoint-specific max) |
page_size |
Page size for GET /structured_targets (default: 100; max: 2000) |
cursor |
Pass from previous response to get next page |
Continue until cursor is null/empty/missing.
Maximum Limits by Endpoint
| Endpoint | Page Size Param | Max |
|---|---|---|
GET /markets |
limit |
1000 |
GET /markets/trades |
limit |
1000 |
GET /events |
limit |
200 |
GET /events/multivariate |
limit |
200 |
GET /structured_targets |
page_size |
2000 |
REST API Endpoints
Exchange (No Auth)
| Endpoint | Description |
|---|---|
GET /exchange/status |
Exchange status (exchange_active, trading_active) |
GET /exchange/announcements |
Exchange-wide announcements |
GET /exchange/schedule |
Trading schedule |
GET /series/fee_changes |
Series fee change schedule |
GET /exchange/user_data_timestamp |
User data timestamp |
Market Data (No Auth)
| Endpoint | Description |
|---|---|
GET /markets |
List markets with filters |
GET /markets/{ticker} |
Single market details |
GET /markets/{ticker}/orderbook |
Current orderbook |
GET /markets/candlesticks |
Batch candlesticks (up to 100 markets, 10,000 candlesticks max) |
GET /markets/trades |
Historical trades (paginated) |
GET /series |
List series templates (supports category, tags filters) |
GET /series/{series_ticker} |
Single series details |
GET /series/{series_ticker}/markets/{ticker}/candlesticks |
Market-level candlestick data |
GET /search/tags_by_categories |
Get tags organized by category (for discovery) |
GET /search/filters_by_sport |
Get sports-specific filters |
GET /events |
List events (excludes multivariate) |
GET /events/multivariate |
Multivariate events only |
GET /events/{event_ticker} |
Single event details |
GET /events/{event_ticker}/metadata |
Event metadata |
GET /series/{series_ticker}/events/{ticker}/candlesticks |
Event-level candlestick data |
GET /series/{series_ticker}/events/{ticker}/forecast_percentile_history |
Event forecast percentile history (auth required) |
GET /structured_targets |
List structured targets (cursor + page_size) |
GET /structured_targets/{structured_target_id} |
Structured target details |
GET /structured_targets Parameters
| Parameter | Type | Description |
|---|---|---|
type |
string | Filter by structured target type (e.g., PLAYER_STATS, GAME_EVENT) |
competition |
string | Filter by competition (e.g., NFL, NBA, EPL) |
page_size |
int | Page size (1-2000, default: 100) |
cursor |
string | Pagination cursor |
Docs conflict (market data auth): Kalshi’s quickstart docs describe market-data REST endpoints as public, but the OpenAPI spec marks some (notably orderbook) as requiring auth headers. As of 2026-01-08, unauthenticated
GET /markets/{ticker}/orderbookworks in practice; if you see 401s, retry with signed headers.
GET /markets Parameters
| Parameter | Type | Description |
|---|---|---|
status |
enum | unopened, open, paused, closed, settled |
tickers |
string | Comma-separated market tickers |
event_ticker |
string | Filter by event (up to 10 comma-separated) |
series_ticker |
string | Filter by series |
mve_filter |
enum | only (multivariate only) or exclude (no multivariate) |
limit |
int | Page size (1-1000, default: 100) |
cursor |
string | Pagination cursor |
min_created_ts / max_created_ts |
int | Unix timestamp filters |
min_close_ts / max_close_ts |
int | Close time filters |
min_settled_ts / max_settled_ts |
int | Settlement time filters |
Note: Timestamp filters are mutually exclusive. Only one status filter allowed.
GET /markets/candlesticks (Batch)
Fetch candlesticks for multiple markets in a single request.
Limits: - Up to 100 tickers per request - Up to 10,000 candlesticks total in response
| Parameter | Type | Description |
|---|---|---|
tickers |
string | Comma-separated market tickers (max 100) |
period_interval |
int | 1 (1 min), 60 (1 hour), 1440 (1 day) |
start_ts |
int | Unix timestamp start |
end_ts |
int | Unix timestamp end |
include_latest_before_start |
boolean | Include most recent candlestick before start_ts for price continuity |
GET /events Parameters
| Parameter | Type | Description |
|---|---|---|
limit |
int | Page size (1-200, default: 200) |
cursor |
string | Pagination cursor |
series_ticker |
string | Filter by series |
status |
enum | open, closed, settled |
min_close_ts |
int | Unix timestamp filter (events with a market closing after this) |
with_nested_markets |
boolean | If true, each event includes a markets field (list of Market objects) |
with_milestones |
boolean | If true, include related milestones alongside events |
Note: GET /events excludes multivariate events; use GET /events/multivariate for MVEs.
GET /series Parameters
| Parameter | Type | Description |
|---|---|---|
category |
string | Filter by category (e.g., "Politics", "Economics", "Sports") |
tags |
string | Filter by tags |
include_product_metadata |
boolean | Include product metadata (default: false) |
include_volume |
boolean | Include total volume across all events (default: false) |
Category Discovery Pattern: Use GET /search/tags_by_categories to discover available categories and their tags, then use GET /series?category=... to find series in that category, then GET /markets?series_ticker=... to get markets.
SSOT note (fixtures, 2026-01-12): Some Series objects return tags: null and additional_prohibitions: null even though the OpenAPI schema marks them as required arrays. Treat null as an empty list.
GET /search/tags_by_categories
Returns a mapping of categories to their associated tags. Useful for building category filter UIs.
Response: { "tags_by_categories": { "Politics": ["elections", ...], "Sports": [...], ... } }
SSOT note (fixtures, 2026-01-12): Some categories map to null instead of an array. Treat null as an empty list.
Market Response Settlement Fields (Dec 25, 2025+)
| Field | Type | Description |
|---|---|---|
settlement_ts |
datetime (RFC3339) | Actual settlement timestamp. Only populated for settled markets. |
settlement_value |
int | Settlement value in cents for YES side |
settlement_value_dollars |
string | Settlement value in dollars (e.g., "1.00") |
settlement_timer_seconds |
int | Duration before market settles after determination |
Note:
settlement_tsis the actual settlement time (changelog entry Dec 19, 2025; release date Dec 25, 2025). Prior to this field,expiration_timewas used as a proxy, which was inaccurate for markets that settled early (event resolved before expiration) or late (disputes, delays).
Market Provisional Flag (Jan 9, 2025+)
| Field | Type | Description |
|---|---|---|
is_provisional |
bool | If true, market will be removed if no trading activity occurs by settlement |
Note: Provisional markets are placeholders that may be deleted. Check this flag before building long-term tracking.
Market Schema: Complete Field Reference
Core Market Identifiers
| Field | Type | Description |
|---|---|---|
ticker |
string | Unique market identifier |
event_ticker |
string | Parent event identifier |
series_ticker |
string | Parent series identifier |
market_type |
enum | binary (standard yes/no) or scalar (range-based payout) |
Note on scalar markets: Scalar markets have different settlement mechanics where payout depends on where a value lands within a range, not just yes/no outcome.
Market Titles (Deprecated)
| Field | Status | Replacement |
|---|---|---|
title |
DEPRECATED | Use yes_sub_title for YES outcome description |
subtitle |
DEPRECATED | Use no_sub_title for NO outcome description |
Strike Configuration (for market mechanics)
| Field | Type | Description |
|---|---|---|
strike_type |
enum | Comparison type: greater, greater_or_equal, less, less_or_equal, between, functional, custom, structured |
floor_strike |
int | Minimum expiration value for YES outcome |
cap_strike |
int | Maximum expiration value for YES outcome |
functional_strike |
string | Mapping formula from expiration values to settlement |
custom_strike |
object | Per-target expiration value mappings |
Price Level Structure (Subpenny Pricing)
The price_level_structure field defines allowed price levels, critical for upcoming subpenny pricing:
{
"price_level_structure": "custom",
"price_ranges": [
{"start": "0.0100", "end": "0.1000", "step": "0.0100"},
{"start": "0.1000", "end": "0.9000", "step": "0.0100"},
{"start": "0.9000", "end": "0.9900", "step": "0.0100"}
]
}
| Field | Type | Description |
|---|---|---|
price_level_structure |
string | Defines pricing rules (e.g., custom) |
price_ranges |
array | Allowed price ranges with start, end, step |
⚠️ IMPORTANT: Subpenny pricing migration is underway. Per the Kalshi API changelog: subpenny fields were added to price APIs on Aug 31, 2025, added to WebSocket messages on Sep 9, 2025, and additional quote fields (e.g.,
yes_bid_dollars/no_bid_dollars) were added on Nov 21, 2025. All systems should parse*_dollarsfields now and handle non-integer prices; legacy integer cent fields are deprecated and may be removed.
Expiration & Settlement Times
| Field | Type | Description |
|---|---|---|
created_time |
datetime | When market was created |
open_time |
datetime | When trading opens |
close_time |
datetime | When trading closes |
expiration_time |
datetime | DEPRECATED - Use latest_expiration_time |
latest_expiration_time |
datetime | Latest possible expiration |
expected_expiration_time |
datetime (nullable) | Projected settlement time (distinct from latest_expiration_time) |
settlement_ts |
datetime (nullable) | Actual settlement timestamp (only for settled markets) |
settlement_timer_seconds |
int | Countdown before market settles after determination |
Fee & Promotion Fields
| Field | Type | Description |
|---|---|---|
fee_waiver_expiration_time |
datetime (nullable) | When promotional fee waiver ends |
early_close_condition |
string (nullable) | Condition under which market can close early |
Other Market Fields
| Field | Type | Description |
|---|---|---|
primary_participant_key |
string (nullable) | Primary participant identifier (internal use) |
is_provisional |
bool | If true, market may be deleted if no activity occurs |
Multivariate Market Fields
For multivariate event markets:
| Field | Type | Description |
|---|---|---|
mve_selected_legs |
array | Selected legs in a multivariate combination |
{
"mve_selected_legs": [
{
"event_ticker": "KXEVENT-A",
"market_ticker": "KXMARKET-A",
"side": "yes"
},
{
"event_ticker": "KXEVENT-B",
"market_ticker": "KXMARKET-B",
"side": "no"
}
]
}
Market status gotcha (filter vs response)
- Query filter (
GET /markets?status=...):unopened,open,paused,closed,settled - Market object field (
market.statusin responses):initialized,inactive,active,closed,determined,disputed,amended,finalized
Orders (Authenticated)
| Endpoint | Description |
|---|---|
POST /portfolio/orders |
Create single order |
POST /portfolio/orders/batched |
Create up to 20 orders |
DELETE /portfolio/orders/batched |
Cancel orders in batch |
GET /portfolio/orders |
List orders by status |
GET /portfolio/orders/{order_id} |
Single order details |
DELETE /portfolio/orders/{order_id} |
Cancel order |
POST /portfolio/orders/{order_id}/amend |
Modify price/quantity |
POST /portfolio/orders/{order_id}/decrease |
Decrease order size |
GET /portfolio/orders/{order_id}/queue_position |
Queue position for one order |
GET /portfolio/orders/queue_positions |
Queue positions for multiple orders |
Portfolio (Authenticated)
| Endpoint | Description |
|---|---|
GET /portfolio/balance |
Account balance (balance + portfolio_value in cents) |
GET /portfolio/positions |
Holdings across markets |
GET /portfolio/fills |
Trade history |
GET /portfolio/settlements |
Settlement records (includes trade fees, event ticker) |
GET /portfolio/summary/total_resting_order_value |
Total value of resting orders |
POST /portfolio/subaccounts |
Create subaccount |
GET /portfolio/subaccounts/balances |
List subaccount balances |
POST /portfolio/subaccounts/transfer |
Transfer between subaccounts |
GET /portfolio/subaccounts/transfers |
List subaccount transfers |
Note: The
/portfolio/subaccounts/transferendpoint is for internal transfers between your own subaccounts. External fiat/crypto deposits and withdrawals are NOT available via the Trading API. These must be done via the Kalshi web UI or separate banking integration (e.g., Aeropay).
GET /portfolio/balance response keys
Observed response keys:
{
"balance": 10000,
"portfolio_value": 25000,
"updated_ts": 1768231443
}
balanceandportfolio_valueare in centsupdated_tsis a Unix timestamp (seconds) (observed in production)
GET /portfolio/positions response keys
Kalshi returns both market-level and event-level aggregates:
{
"cursor": "",
"market_positions": [
{
"ticker": "KX...",
"position": 34,
"market_exposure": 952,
"market_exposure_dollars": "9.5200",
"realized_pnl": 0,
"fees_paid": 48,
"fees_paid_dollars": "0.4800",
"total_traded": 1400,
"total_traded_dollars": "14.0000",
"resting_orders_count": 0,
"last_updated_ts": "2026-01-10T16:11:11.109894Z"
}
],
"event_positions": [
{
"event_ticker": "KX...",
"event_exposure": 952,
"event_exposure_dollars": "9.5200",
"realized_pnl": 0,
"fees_paid": 48,
"total_cost": 1400,
"total_cost_dollars": "14.0000",
"total_cost_shares": 34
}
]
}
Implementation note: KalshiClient.get_positions() consumes market_positions only.
OpenAPI response keys are market_positions and event_positions. The legacy positions key is not supported
(removed in DEBT-014 Item A2).
Note:
realized_pnlis a market-level “locked in P&L” field (cents) per the OpenAPI schema. Kalshi’s docs do not specify whether/portfolio/positionsreturns closed markets (position = 0), so do not assume it is a complete “all time realized P&L” feed. For end-to-end realized P&L across your history, sync/portfolio/fillsand/portfolio/settlementsand compute from local history (handling gaps explicitly).
GET /portfolio/fills response fields
Query Parameters:
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit |
int | 100 | 200 | Results per page |
cursor |
string | - | - | Pagination cursor |
min_ts |
int64 | - | - | Unix timestamp filter (after) |
max_ts |
int64 | - | - | Unix timestamp filter (before) |
ticker |
string | - | - | Filter by market |
order_id |
string | - | - | Filter by order |
Response fields (per fill):
| Field | Type | Description |
|---|---|---|
fill_id |
string | Unique fill identifier |
trade_id |
string | Legacy field (same as fill_id) |
order_id |
string | Parent order ID |
ts |
int | Unix timestamp (seconds) of fill (observed in production) |
ticker |
string | Market ticker |
market_ticker |
string | Duplicate of ticker (legacy; observed in production) |
side |
enum | yes or no - literal side, not effective position |
action |
enum | buy or sell |
count |
int | Contracts filled |
price |
number | Decimal price representation (deprecated; observed in production) |
yes_price |
int | YES price in cents |
no_price |
int | NO price in cents |
yes_price_fixed |
string | YES price in dollars (e.g., "0.48") |
no_price_fixed |
string | NO price in dollars (e.g., "0.52") |
is_taker |
bool | True if removed liquidity |
client_order_id |
string | Client-provided order ID (if set) |
created_time |
string | RFC3339 timestamp |
⚠️ Data Retention: Kalshi does NOT document how far back fills history is retained. Do not assume complete history exists. ⚠️ Cross-Side Closing: The
sidefield is literal (the side you traded), NOT the effective position side. Selling YES to close a NO position showsside=yes, which can confuse FIFO calculations. Note: Market settlements appear in/portfolio/settlements, NOT/portfolio/fills. For complete P&L, you need both endpoints.
Orderbook Endpoint
GET /markets/{ticker}/orderbook
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
depth |
integer | 0 | Depth of orderbook (0 or negative = all levels, 1-100 for specific depth) |
Response Format
{
"orderbook": {
"yes": [[price_cents, count], ...],
"no": [[price_cents, count], ...],
"yes_dollars": [["0.1500", count], ...],
"no_dollars": [["0.8500", count], ...]
}
}
- Integer fields: Cents (0-100 scale, $0.00-$1.00)
- Dollar fields: String format like
"0.1500" - Levels: Sorted best-to-worst price (highest bid first)
- Empty sides: May be
null(observed) or omitted when no orders exist
Binary market math: A YES bid at price X = NO ask at price (100-X)
Note: The orderbook shows bids only for both sides. YES asks are implied from NO bids (and vice versa). A yes bid at 7¢ = no ask at 93¢.
WebSocket API
Connection Authentication
ALL WebSocket connections require authentication, even for public data channels.
Use the same three headers as REST:
- KALSHI-ACCESS-KEY
- KALSHI-ACCESS-TIMESTAMP
- KALSHI-ACCESS-SIGNATURE (sign: {timestamp}GET/trade-api/ws/v2)
Keep-Alive
- Kalshi sends Ping frames (0x9) every ~10 seconds with body
heartbeat - Clients must respond with Pong frames (0xA)
Subscribe Command
{
"id": 1,
"cmd": "subscribe",
"params": {
"channels": ["ticker"],
"market_tickers": ["KXBTC-25JAN-T100000"]
}
}
params supports either market_ticker (single) or market_tickers (list).
Other commands: unsubscribe, list_subscriptions, update_subscription
Available Channels
| Channel | Data Scope | Description |
|---|---|---|
orderbook_delta |
Public | Orderbook changes (requires market filter) |
ticker |
Public | Price, volume, open interest updates |
trade |
Public | Public trade notifications |
market_lifecycle_v2 |
Public | Market state changes, event creation |
multivariate |
Public | Multivariate collection notifications |
fill |
Private | Your order fills (market filter ignored) |
market_positions |
Private | Your position updates |
communications |
Private | RFQ and quote notifications |
orderbook_delta Channel Details
Subscription:
{
"id": 2,
"cmd": "subscribe",
"params": {
"channels": ["orderbook_delta"],
"market_tickers": ["KXBTC-26JAN15-T100000"]
}
}
Initial Snapshot (sent first):
{
"type": "orderbook_snapshot",
"sid": 2,
"seq": 2,
"msg": {
"market_ticker": "KXBTC-26JAN15-T100000",
"yes": [[47, 300], [46, 150]],
"yes_dollars": [["0.470", 300], ["0.460", 150]],
"no": [[53, 200], [54, 100]],
"no_dollars": [["0.530", 200], ["0.540", 100]]
}
}
Delta Updates (incremental):
{
"type": "orderbook_delta",
"sid": 2,
"seq": 3,
"msg": {
"market_ticker": "KXBTC-26JAN15-T100000",
"price": 47,
"price_dollars": "0.470",
"delta": -50,
"side": "yes"
}
}
| Field | Description |
|---|---|
seq |
Sequence number for ordering and gap detection |
delta |
Change in quantity (positive = add, negative = remove) |
side |
"yes" or "no" |
Applying deltas: Update quantity at price level. Remove level when quantity reaches 0. Track seq to detect missed messages.
Price/Value Units
CRITICAL: Units vary by channel!
| Channel/Field | Unit | Conversion |
|---|---|---|
ticker.price, ticker.yes_bid, ticker.yes_ask |
Cents | Divide by 100 for dollars |
ticker.price_dollars, ticker.yes_bid_dollars |
Dollars (string) | Direct use |
orderbook_delta.price |
Cents | Divide by 100 for dollars |
market_positions.position_cost |
Centi-cents | Divide by 10,000 for dollars |
market_positions.realized_pnl |
Centi-cents | Divide by 10,000 for dollars |
market_positions.fees_paid |
Centi-cents | Divide by 10,000 for dollars |
Example conversion:
- Ticker price: 48 → $0.48
- Position position_cost: 500000 → $50.00
Ticker Channel Additional Fields
The ticker channel includes fields not always documented:
| Field | Type | Description |
|---|---|---|
dollar_volume |
string | Volume in dollars |
dollar_open_interest |
string | Open interest in dollars |
no_bid_dollars |
string | NO bid price in dollars |
ts |
int | Unix timestamp of tick |
Update Subscription Command
Modify existing subscriptions without full resubscribe:
{
"id": 5,
"cmd": "update_subscription",
"params": {
"sid": 2,
"action": "add_markets",
"market_tickers": ["KXBTC-26FEB01"]
}
}
| Parameter | Description |
|---|---|
sid / sids |
Single subscription ID or array of IDs |
action |
add_markets or delete_markets |
market_tickers |
Markets to add/remove |
WebSocket Error Codes
| Code | Error | Description |
|---|---|---|
| 1 | Unable to process message | General processing error |
| 2 | Params required | Missing params object |
| 3 | Channels required | Missing channels array |
| 4 | Subscription IDs required | Missing sids in unsubscribe |
| 5 | Unknown command | Invalid command name |
| 7 | Unknown subscription ID | Subscription ID not found |
| 8 | Unknown channel name | Invalid channel |
| 9 | Authentication required | Private channel without auth |
| 10 | Channel error | Channel-specific error |
| 11 | Invalid parameter | Malformed parameter value |
| 12 | Exactly one subscription ID required | For update_subscription |
| 13 | Unsupported action | Invalid action |
| 14 | Market ticker required | Missing market specification |
| 15 | Action required | Missing action in update_subscription |
| 16 | Market not found | Invalid market ticker |
| 17 | Internal error | Server-side processing error |
SDKs
Python (Current)
pip install kalshi-python-sync # Synchronous
pip install kalshi-python-async # Asynchronous
Note: pip normalizes underscores to hyphens. The actual package names use hyphens (
kalshi-python-sync), but imports use underscores (import kalshi_python_sync). Kalshi docs show both conventions.Docs conflict: Kalshi's Python SDK docs currently have both
kalshi-pythonand the newerkalshi_python_sync/kalshi_python_asyncpages live. The quickstart callskalshi-pythondeprecated.
TypeScript/JavaScript
npm install kalshi-typescript
FIX Protocol
For institutional traders and high-frequency operations:
- Protocol: FIX 4.4
- Access: Gated (contact Kalshi)
- Docs: docs.kalshi.com/fix
Additional Endpoints (Less Common)
These endpoints exist in the API but are less commonly used:
Order Groups (Authenticated)
Manage groups of orders that can be modified/canceled together:
| Endpoint | Description |
|---|---|
GET /portfolio/order_groups |
List order groups |
POST /portfolio/order_groups/create |
Create order group |
GET /portfolio/order_groups/{order_group_id} |
Get order group details |
DELETE /portfolio/order_groups/{order_group_id} |
Delete order group |
PUT /portfolio/order_groups/{order_group_id}/reset |
Reset order group |
Milestones (No Auth)
| Endpoint | Description |
|---|---|
GET /milestones |
List milestones |
GET /milestones/{milestone_id} |
Get milestone details |
Query Parameters for GET /milestones:
| Parameter | Type | Description |
|---|---|---|
limit |
int | Page size (required, 1-500) |
minimum_start_date |
string | RFC3339 timestamp filter (milestones starting after this date) |
min_start_date |
string | Legacy/incorrect alias — accepted but ignored (use minimum_start_date) |
category |
string | Filter by milestone category |
competition |
string | Filter by competition |
source_id |
string | Filter by source ID |
type |
string | Filter by milestone type |
related_event_ticker |
string | Filter by related event ticker |
cursor |
string | Pagination cursor |
Linking milestones to events: Use with_milestones=true on GET /events to include related milestones.
Live Data (No Auth)
| Endpoint | Description |
|---|---|
GET /live_data/{type}/milestone/{milestone_id} |
Get live data for a milestone |
GET /live_data/batch |
Batch live data |
Query Parameters for GET /live_data/batch:
| Parameter | Type | Description |
|---|---|---|
milestone_ids |
string[] | One or more milestone IDs (repeatable; max 100) |
Incentive Programs (No Auth)
| Endpoint | Description |
|---|---|
GET /incentive_programs |
List active incentive/reward programs |
Query Parameters for GET /incentive_programs:
| Parameter | Type | Description |
|---|---|---|
status |
string | all, active, upcoming, closed, paid_out |
type |
string | all, liquidity, volume |
limit |
int | Page size (1-10000) |
cursor |
string | Pagination cursor |
Response includes: incentive_programs list and next_cursor.
FCM (Futures Commission Merchant)
| Endpoint | Description |
|---|---|
GET /fcm/orders |
FCM orders |
GET /fcm/positions |
FCM positions |
API Keys Management (Authenticated)
| Endpoint | Description |
|---|---|
GET /api_keys |
List API keys |
POST /api_keys |
Create API key |
POST /api_keys/generate |
Generate new API key |
DELETE /api_keys/{api_key} |
Delete API key |
Search (No Auth)
| Endpoint | Description |
|---|---|
GET /search/tags_by_categories |
Tags organized by category |
GET /search/filters_by_sport |
Sports-specific filters |
Communications / RFQ System (Authenticated)
The Request for Quote (RFQ) system enables negotiated trades for larger positions outside the orderbook.
Limits: - Maximum 100 open RFQs at a time per user - RFQ uses centi-cents for target cost (divide by 10,000 for dollars)
| Endpoint | Description |
|---|---|
GET /communications/id |
Get communications ID |
POST /communications/rfqs |
Create new RFQ |
GET /communications/rfqs |
List RFQs |
GET /communications/rfqs/{rfq_id} |
RFQ details |
DELETE /communications/rfqs/{rfq_id} |
Delete/cancel RFQ |
POST /communications/quotes |
Create quote response to RFQ |
GET /communications/quotes |
List quotes |
GET /communications/quotes/{quote_id} |
Quote details |
DELETE /communications/quotes/{quote_id} |
Delete/cancel quote |
PUT /communications/quotes/{quote_id}/accept |
Accept quote (RFQ creator) |
PUT /communications/quotes/{quote_id}/confirm |
Confirm quote (quote creator) |
Create RFQ Request Body
| Field | Type | Required | Description |
|---|---|---|---|
market_ticker |
string | Yes | Market for the RFQ |
contracts |
integer | No | Number of contracts |
target_cost_centi_cents |
int64 | No | Target cost in centi-cents (÷10,000 for dollars) |
rest_remainder |
boolean | Yes | Rest remaining quantity after partial execution |
replace_existing |
boolean | No | If true, deletes existing RFQs during creation |
subtrader_id |
string | No | FCM subtrader identifier |
Example:
{
"market_ticker": "KXBTC-26JAN-T100000",
"contracts": 1000,
"target_cost_centi_cents": 500000,
"rest_remainder": false
}
Note:
target_cost_centi_cents: 500000= $50.00 (500000 ÷ 10000)
WebSocket RFQ Channel
Subscribe to communications channel for real-time RFQ/quote events. Requires authentication.
Multivariate Event Collections (Mixed Auth)
| Endpoint | Description |
|---|---|
GET /multivariate_event_collections |
List multivariate event collections |
GET /multivariate_event_collections/{collection_ticker} |
Collection details (public per OpenAPI; verified via unauthenticated curl 2026-01-15) |
POST /multivariate_event_collections/{collection_ticker} |
Create market in collection (auth required) |
GET /multivariate_event_collections/{collection_ticker}/lookup |
Lookup history (public per OpenAPI; requires lookback_seconds) |
PUT /multivariate_event_collections/{collection_ticker}/lookup |
Lookup tickers for selected markets (auth required) |
Portfolio Settlements (Authenticated)
| Endpoint | Description |
|---|---|
GET /portfolio/settlements |
Your settlement history (when markets resolve) |
Query Parameters:
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit |
int | 100 | 200 | Results per page |
cursor |
string | - | - | Pagination cursor |
ticker |
string | - | - | Filter by market |
event_ticker |
string | - | - | Filter by event (comma-separated, max 10) |
min_ts |
int64 | - | - | Unix timestamp filter (after) |
max_ts |
int64 | - | - | Unix timestamp filter (before) |
Response fields (per settlement):
| Field | Type | Description |
|---|---|---|
ticker |
string | Market that settled |
event_ticker |
string | Parent event |
market_result |
enum | yes, no, scalar, or void |
yes_count |
int | YES contracts held at settlement |
no_count |
int | NO contracts held at settlement |
yes_total_cost |
int | Cost basis of YES contracts (cents) |
no_total_cost |
int | Cost basis of NO contracts (cents) |
revenue |
int | Payout received (100¢ per winning contract) |
settled_time |
string | ISO timestamp when settled |
fee_cost |
string | Fees in fixed-point dollars |
value |
int/null | Payout per contract (for scalar markets) |
⚠️ Critical for P&L: Settlements are NOT fills. When a market settles: - Your position auto-closes - This appears in
/portfolio/settlements, NOT/portfolio/fills- For complete FIFO P&L, you need BOTH endpoints - Settlements act as "sells" at the settlement price (100¢ if won, 0¢ if lost)
Create Order Safety Parameters
POST /portfolio/orders supports several safety-critical parameters:
| Parameter | Type | Description |
|---|---|---|
reduce_only |
bool | SAFETY: Only reduce position, never increase. Use for closing trades. |
cancel_order_on_pause |
bool | Auto-cancel if trading paused on exchange |
buy_max_cost |
int | Max cost in cents. Enables Fill-or-Kill behavior. |
post_only |
bool | Maker-only order (avoid taker fees, reject if would cross) |
self_trade_prevention_type |
enum | taker_at_cross or maker - prevent self-trades |
order_group_id |
string | Link order to a group (grouped cancel/modify) |
time_in_force |
enum | fill_or_kill, good_till_canceled, immediate_or_cancel |
Create Order Response (200)
Observed in production:
{
"order": { /* full Order object */ }
}
Cancel Order Response (200)
Observed in production:
{
"order": { /* full Order object */ },
"reduced_by": 10
}
reduced_by is the number of contracts canceled. Treat as optional (may be absent).
Amend Order Full Schema
Endpoint: POST /portfolio/orders/{order_id}/amend
Amend allows modifying price and/or increasing order size (not just decreasing).
Request Body
Required fields:
| Field | Type | Description |
|---|---|---|
ticker |
string | Market ticker |
side |
enum | yes or no |
action |
enum | buy or sell |
client_order_id |
string | Original client-specified order ID |
updated_client_order_id |
string | New client-specified order ID (must be unique) |
Price fields (exactly one required):
| Field | Type | Description |
|---|---|---|
yes_price |
integer | Updated YES price in cents (1-99) |
no_price |
integer | Updated NO price in cents (1-99) |
yes_price_dollars |
string | Updated YES price in dollars (e.g., "0.5600") |
no_price_dollars |
string | Updated NO price in dollars |
Optional:
| Field | Type | Description |
|---|---|---|
count |
integer | Updated quantity (min 1). Can increase size up to remaining_count + fill_count. |
Response (200)
Returns both order states:
{
"old_order": { /* Order details before amendment */ },
"order": { /* Order details after amendment */ }
}
Key insight: Unlike decrease, amend can increase order size. Max fillable is
remaining_count + fill_count(original order size).
Order Response Fields
| Field | Type | Description |
|---|---|---|
order_id |
string | Unique order identifier |
initial_count |
int | Original order size (before any fills or amendments) |
queue_position |
int | DEPRECATED - Always returns 0. Use GET /portfolio/orders/{order_id}/queue_position instead. |
taker_fees_dollars |
string | Fees paid on taker fills (dollars) |
maker_fees_dollars |
string | Fees paid on maker fills (dollars) |
taker_fill_cost |
int | Cost of taker fills in cents |
maker_fill_cost |
int | Cost of maker fills in cents |
taker_fill_cost_dollars |
string | Cost of taker fills in dollars |
maker_fill_cost_dollars |
string | Cost of maker fills in dollars |
fill_count |
int | Contracts filled so far |
remaining_count |
int | Contracts still resting |
last_update_time |
datetime | Last modification timestamp |
Deprecated Order Fields
| Deprecated | Replacement |
|---|---|
sell_position_floor |
Use reduce_only: true instead |
Recent Breaking Changes (2025-2026)
Market response field removals (planned Jan 15, 2026)
Status (last checked: 2026-01-17): Soft deprecated — cent fields may still be returned alongside
*_dollars.
Cent-denominated fields slated for removal from Market responses:
- response_price_units, notional_value, yes_bid, yes_ask, no_bid, no_ask, last_price,
previous_yes_bid, previous_yes_ask, previous_price, liquidity → Use *_dollars equivalents.
- tick_size → Use price_level_structure and price_ranges.
Known edge case: The liquidity field can return negative values (e.g., -170750) in some markets.
This is likely a calculation artifact or sentinel value. Since the field is deprecated, treat negative
values as None/unknown rather than crashing on validation.
Dollar replacements (these REMAIN in API):
| DEPRECATED (cent) | REMAINS (Use This) | Format |
|---|---|---|
yes_bid |
yes_bid_dollars |
String like "0.4500" |
yes_ask |
yes_ask_dollars |
String like "0.5500" |
no_bid |
no_bid_dollars |
String like "0.5500" |
no_ask |
no_ask_dollars |
String like "0.4500" |
last_price |
last_price_dollars |
String |
previous_price |
previous_price_dollars |
String |
liquidity |
liquidity_dollars |
String (current offer value) |
notional_value |
notional_value_dollars |
String |
tick_size |
price_level_structure, price_ranges |
Object structure |
Clarification: The
*_dollarsfields are the replacements that survive. They are NOT being deprecated - they are the new standard. Only the cent-denominated integer fields are being removed.
Series volume field (release Jan 15, 2026)
New include_volume query parameter on GET /series endpoints. When set, returns volume field with total contracts traded across all events in the series.
Market response field removals (release Jan 8, 2026)
category,risk_limit_centsremoved from Market responses.
API key scopes (release Dec 18, 2025)
- Keys support
scopes: ["read", "write"](defaults to full access if omitted; existing keys have both).
Multivariate events (release Dec 4, 2025)
GET /eventsnow excludes multivariate events- Use
GET /events/multivariatefor multivariate events - Use
mve_filterparameter on/marketsto filter
Order semantics (late 2025)
- “Pending” removed from order status enum (expected release Nov 27, 2025).
- Order expiration constraints (
expiration_tsvalidation andimmediate_or_cancelinteraction) were announced with a TBD release date in the changelog; code defensively.
Monitoring Notes
Cent Field Deprecation (Last checked: 2026-01-17)
- Kalshi announced cent-denominated field removal for Jan 15, 2026
- Status: Soft deprecated (cent fields still returned alongside dollars)
- Our code: Uses
*_dollarsonly (fallback removed) - Action: If API errors occur, check if Kalshi changed field names
Key Concepts
| Term | Definition |
|---|---|
| Market | Binary yes/no contract within an event |
| Event | Collection of related markets |
| Series | Template for recurring events |
| Multivariate Event | Event with multiple possible outcomes (combos) |
| Fill | Completed trade transaction |
| Queue Position | Contracts ahead before your order fills |
| Cents | Integer 0-100 representing $0.00-$1.00 |
| Centi-cents | Integer where 10,000 = $1.00 (used in WS positions) |
Endpoint Access Categories (2026-01-16)
Not all OpenAPI endpoints are available to all users. Here's the breakdown:
Consumer / Retail Trader (All 50 implemented endpoints)
All market data, portfolio, orders, discovery, and trading endpoints work for any verified Kalshi account.
Institutional Only
| Category | Endpoints | Who Can Use |
|---|---|---|
| RFQ/Communications | 11 | Market makers, institutional traders with large positions |
| FCM | 2 | Futures Commission Merchants (Robinhood, Webull, brokers) |
Per the changelog: "FCM endpoints require FCM member access level... only intended for use by FCM members (rare)"
New / Phased Rollout
| Endpoint | Added | Status (2026-01-16) |
|---|---|---|
| Subaccounts (4) | Jan 9, 2026 | Returns 403/404 - not yet generally available |
| Forecast percentile history (1) | Sep 11, 2025 | Returns 400 - data may not be populated |
Security Risk (Not Recommended)
| Category | Endpoints | Recommendation |
|---|---|---|
| API Keys | 4 | Use Kalshi web UI with 2FA instead |
Developer Resources
| Resource | URL |
|---|---|
| Official Docs | https://docs.kalshi.com/welcome |
| LLMs.txt (AI Discovery) | https://docs.kalshi.com/llms.txt |
| OpenAPI Spec | https://docs.kalshi.com/openapi.yaml |
| API Changelog | https://docs.kalshi.com/changelog |
| Help Center | https://help.kalshi.com/kalshi-api |
| Discord | #dev channel |
| Demo Portal | https://demo.kalshi.co |
Tip: The
llms.txtfile is a standard LLM navigation file that lists all documentation pages. Useful for AI agents exploring the API.