Trading on Kalshi through the API

Kalshi's Trade API serves public market data over REST with no key. To trade, you need an API key, and you sign every private request with Ed25519 or RSA-PSS over the timestamp, method and path. There is no market order type: every order is a limit order sent to POST /portfolio/events/orders, priced as a dollar string.

On this page
  1. What the Kalshi API covers
  2. Reading market data without a key
  3. Creating a Kalshi API key
  4. How Kalshi API authentication works
  5. How to place an order with the Kalshi API
  6. Prices, ticks and fractional contracts
  7. Kalshi API rate limits
  8. WebSocket basics
  9. Habits that keep API trading safe
  10. Questions traders ask
  11. Sources

What the Kalshi API covers

Kalshi runs one Trade API with three ways in. REST covers market data, your portfolio and order entry. A WebSocket feed streams book changes, fills and positions. A FIX interface covers order entry, market data and drop copy. Production and demo are separate environments with these base URLs. A key made in one fails in the other.

Production REST       https://external-api.kalshi.com/trade-api/v2
Production WebSocket  wss://external-api-ws.kalshi.com/trade-api/ws/v2
Demo REST             https://external-api.demo.kalshi.co/trade-api/v2
Demo WebSocket        wss://external-api-ws.demo.kalshi.co/trade-api/ws/v2

The older hosts, api.elections.kalshi.com and demo-api.kalshi.co, still work. Kalshi recommends the external-api hosts for API traders. Despite its name, the elections host serves every Kalshi market.

Calling the API counts as accepting Kalshi's Developer Agreement. It limits API use to a member's own trading. It bars collecting or storing API data except to support that trading, and bars sharing it with third parties without Kalshi's written permission. It also requires bounded retries with exponential backoff.

Reading market data without a key

Series, events, markets, trades and order books are public REST endpoints. They need no account and no headers. This request lists the open markets in the series Kalshi's docs use as their example, the daily high temperature in New York:

curl "https://external-api.kalshi.com/trade-api/v2/markets?series_ticker=KXHIGHNY&status=open"

Fields ending in _dollars are prices as fixed-point strings, so 61c arrives as "0.6100". Fields ending in _fp are contract counts with two decimals, such as "25.00". Kalshi removed the old integer-cent fields in stages between January and April 2026. Older code that reads yes_bid as cents is broken. A decimal type keeps these strings exact. Each market also carries its settlement terms in rules_primary and rules_secondary, which the settlement guide explains. The glossary defines fields such as volume and open interest.

List endpoints page with a cursor. Pass each response's cursor into the next request until none comes back.

The order book returns bids only

GET /markets/{ticker}/orderbook returns two arrays of bids, yes_dollars and no_dollars. Each level is a price and size pair sorted from low to high, so the best bid is the last element. Asks are implied by the other side. A NO bid at 37c is an offer to sell YES at 63c.

Suppose the best YES level is ["0.6100", "250.00"] and the best NO level is ["0.3700", "400.00"]. The YES ask is $1 minus 37c, or 63c, with 400 contracts behind it. The spread is 2c. Buying YES now costs 63c and selling YES now gets 61c. The odds converter turns a price like 63c into implied probability and sportsbook odds. The guide to the Kalshi order book covers depth and spreads.

One YES price scale for a Kalshi order bookAn illustrative book with a best YES bid of 0.61 and a best NO bid of 37 cents. On the YES scale the NO bid sits at 0.63, which is the YES ask, leaving a 2 cent spread.YES0.550.600.650.70NO45c40c35c30c2c spreadYES bid 0.61bid at 0.61 joins itYES ask 0.63from the 37c NO bidask at 0.63 joins it
An illustrative book. The order book endpoint returns bids only. The best NO bid of 37c sits at 0.63 on the YES scale, which makes it the YES ask. V2 orders use this single YES scale with a bid or ask side.

Creating a Kalshi API key

Keys are made in the API Keys section of your Kalshi account settings. Since October 1, 2026, the web app generates Ed25519 keys by default, with RSA (2048-bit) as the option. Kalshi's docs recommend Ed25519, which has a lower signing cost and 64-byte signatures. RSA remains for clients that only support RSA-PSS, including Kalshi's own SDKs before version 3.31.0.

You get a key ID and a private key in PEM format. Kalshi does not store the private key and shows it only once, so a lost key means a new key. You can also generate the pair yourself and paste only the public key. Through the API, POST /api_keys/generate still makes an RSA key unless you pass "key_type": "ed25519".

Scopes decide what a key can do

A key created through the API with no scopes list gets read and write: full access, transfers included.

ScopeWhat it grants
readEvery read endpoint
writeEvery write endpoint, orders and transfers included. Requires read on the same key.
write::tradeOrders, order groups and RFQ quotes, without transfers
write::transferTransfer endpoints only
read::portfolio_balanceBalance reads only

A trading bot needs read and write::trade. Leaving out write keeps transfers off that key. A key can also be locked to one subaccount, numbered 0 to 63. It then reads and trades only that subaccount. GET /api_keys returns each key's scopes and lock, so you can confirm what a key can do before the first order.

How Kalshi API authentication works

Every private request carries three headers. KALSHI-ACCESS-KEY holds the key ID. KALSHI-ACCESS-TIMESTAMP holds the current time in milliseconds. KALSHI-ACCESS-SIGNATURE holds a base64 signature of one string:

message = timestamp + METHOD + path

The path starts at /trade-api/v2 and stops before any ?. A request to /trade-api/v2/portfolio/orders?limit=5 signs /trade-api/v2/portfolio/orders. The host and the body stay out of the message. A balance check at timestamp 1760900000000 signs 1760900000000GET/trade-api/v2/portfolio/balance.

Ed25519 keys sign those bytes directly. RSA keys use RSA-PSS with SHA-256, MGF1 with SHA-256 and a salt as long as the digest. This Python client handles both types and needs the requests and cryptography packages. Its signatures verify against the matching public key for both an Ed25519 and an RSA key.

import base64, time, uuid
import requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

BASE = "https://external-api.demo.kalshi.co/trade-api/v2"
KEY_ID = "your-key-id"
with open("kalshi.key", "rb") as f:
    KEY = serialization.load_pem_private_key(f.read(), password=None)

def sign(ts, method, path):
    msg = (ts + method + path.split("?")[0]).encode()
    if isinstance(KEY, Ed25519PrivateKey):
        sig = KEY.sign(msg)
    else:
        sig = KEY.sign(msg,
                       padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                                   salt_length=padding.PSS.DIGEST_LENGTH),
                       hashes.SHA256())
    return base64.b64encode(sig).decode()

def call(method, endpoint, body=None):
    path = "/trade-api/v2" + endpoint
    ts = str(int(time.time() * 1000))
    headers = {
        "KALSHI-ACCESS-KEY": KEY_ID,
        "KALSHI-ACCESS-TIMESTAMP": ts,
        "KALSHI-ACCESS-SIGNATURE": sign(ts, method, path),
    }
    return requests.request(method, BASE + endpoint,
                            headers=headers, json=body, timeout=10)

for k in call("GET", "/api_keys").json()["api_keys"]:
    print(k["api_key_id"], k["scopes"])

The code checks the parsed key because a PKCS#8 RSA key also begins with BEGIN PRIVATE KEY. Kalshi's troubleshooting table lists the usual failures: a wrong key ID or key file, the wrong signing algorithm, a timestamp in seconds, a query string in the signed path, and /trade-api/v2 added to the URL twice. Keep the clock synced, since the timestamp is part of what you sign.

Kalshi also publishes SDKs: kalshi_python_sync, kalshi_python_async and kalshi-typescript. Ed25519 keys need version 3.31.0 or later. The old kalshi-python package is deprecated.

How to place an order with the Kalshi API

Orders go to POST /portfolio/events/orders, which Kalshi calls Create Order (V2). It requires ticker, side, count, price, time_in_force and self_trade_prevention_type. client_order_id is optional, and Kalshi strongly recommends it.

V2 quotes everything on the YES side. bid buys YES and ask sells YES. Selling YES is the same exposure as buying NO at $1 minus the price, so a NO trade is an ask priced on the YES scale. With the book above, rows that trade now use immediate_or_cancel. Rows that wait use good_till_canceled:

You want tosidepriceResult
Buy YES nowbid0.6300Takes the 63c ask
Buy YES and waitbid0.6100Rests at the 61c bid
Buy NO nowask0.6100Hits the 61c YES bid, so NO costs 39c
Buy NO and waitask0.6300Rests with the 37c NO bids
Sell YES you hold nowask0.6100Receives 61c
Sell NO you hold nowbid0.6300Receives 37c

There is no market order. Kalshi retired that type in September 2025, and V2 has no type field. To trade at once, send immediate_or_cancel at the worst price you will accept. Kalshi cancels whatever cannot fill at that price or better. The guide to Kalshi limit orders covers the choices.

This order rests one YES contract at 1c in demo, using the call helper above, then cancels it. Nothing fills.

order = {
    "ticker": "TICKER-FROM-GET-MARKETS",
    "side": "bid",                     # bid buys YES, ask sells YES
    "count": "1",
    "price": "0.0100",                 # always the YES price, in dollars
    "time_in_force": "good_till_canceled",
    "post_only": True,
    "self_trade_prevention_type": "taker_at_cross",
    "client_order_id": str(uuid.uuid4()),
}
r = call("POST", "/portfolio/events/orders", order)
oid = r.json()["order_id"]
call("DELETE", f"/portfolio/events/orders/{oid}?market_ticker={order['ticker']}")

A 201 response returns order_id, fill_count and remaining_count, plus average_fill_price and average_fee_paid when something filled. Passing market_ticker on a cancel lets Kalshi route it to the right exchange shard. Without it, the cancel goes to shard 0. Kalshi deprecated the older /portfolio/orders write endpoints in June 2026. GET /portfolio/orders still lists your orders.

Order options and their rules

FieldRule
time_in_forceRequired. good_till_canceled, immediate_or_cancel or fill_or_kill. fill_or_kill fills the whole count at once or nothing.
expiration_timeUnix seconds. Pair it with good_till_canceled for an order that expires at that time. It cannot be combined with immediate_or_cancel.
post_onlyAn order that would cross is canceled instead of filled, so it can only rest as a maker.
reduce_onlyCaps the size at your current position. Rejected unless the order is immediate_or_cancel.
self_trade_prevention_typeRequired. taker_at_cross cancels the incoming order if it would hit your own resting order. maker cancels the resting one and keeps matching.
cancel_order_on_pauseCancels the resting order if exchange trading pauses.
order_group_idJoins an order group. When fills in a rolling 15-second window pass the group's limit, Kalshi cancels its resting orders and blocks new ones until a reset.
subaccount0 is the primary account.

Fees depend on the series. GET /series/{ticker} returns fee_type. With quadratic, only takers pay. With quadratic_with_maker_fees, resting orders also pay when they fill. Two other values, quadratic_with_combo_maker_fees and flat, cover combos and series on Kalshi's specific fee table. The Kalshi fees guide has the formula. The fee calculator prices any fill.

Prices, ticks and fractional contracts

Each market publishes its price grid in price_ranges, a list of bands with start, end and step in dollars. Kalshi calls this field the source of truth. Kalshi accepts a price on the grid and rejects a price off it. linear_cent markets step by 1c. tapered_deci_cent markets step by $0.001 below 10c and above 90c. Combo markets go down to $0.0001 at the extremes. Whole-cent prices are valid in every structure.

On a tapered_deci_cent market, "0.0450" is valid, "0.4550" is rejected and "0.4600" is valid. A check against the bands takes a few lines:

from decimal import Decimal

def on_grid(price, price_ranges):
    p = Decimal(price)
    for band in price_ranges:
        start, end, step = (Decimal(band[k]) for k in ("start", "end", "step"))
        if start <= p <= end and (p - start) % step == 0:
            return True
    return False

Counts take up to two decimals, with a minimum of 0.01 contracts. Fills can arrive fractional even if you never send a fractional order.

Kalshi API rate limits

Kalshi meters private requests in tokens. Most cost 10. Your tier sets the refill rate of two buckets, in tokens per second. Write covers orders, amends, cancels, order groups, the RFQ quote flow and block trade accepts. Read covers the rest. REST and FIX draw from the same buckets.

TierReadWriteOrders a second
Basic20010010
Advanced30030030
Expert60060060
Premier1,2001,200120
Paragon2,4002,400240
Prime4,8004,800480
Prestige12,0009,600960

On Basic that is 20 ordinary reads and 10 orders a second. The Basic Write bucket holds one second of budget, so it never banks a burst. Write buckets above Basic hold three seconds, and so do the Read buckets on Basic and Advanced. They allow a burst of three times the rate after a quiet spell.

Every account starts on Basic. Advanced comes from an upgrade endpoint. Kalshi grants Expert and above daily, based on your share of exchange volume. GET /account/limits shows your tier. GET /account/endpoint_costs lists endpoints that cost more or less than 10. Batches bill every item, so 25 orders in one batch cost 250 tokens. GET /portfolio/balance has had its own bucket since October 15, 2026.

An empty bucket returns 429 with no Retry-After header and no penalty. Kalshi asks clients to back off exponentially.

WebSocket basics

The WebSocket needs a key even for public data. Sign the handshake with GET and the path /trade-api/ws/v2, send the same three headers, then subscribe:

{"id": 1, "cmd": "subscribe",
 "params": {"channels": ["orderbook_delta"],
            "market_tickers": ["TICKER-FROM-GET-MARKETS"],
            "use_yes_price": true}}

Useful channels include orderbook_delta, ticker, trade, fill, user_orders and market_positions. The book channel sends a snapshot and then deltas, each with a seq number. A gap in the sequence means you missed an update. NO levels arrive on the NO price scale unless you set use_yes_price to true, which matches V2 pricing. Kalshi plans to make that the default.

Habits that keep API trading safe

Six habits guard against the common expensive errors, such as double orders, stale prices, unit bugs and cancels stuck behind a 429.

  1. A fresh client_order_id on every new order. Kalshi's docs say a second submission with the same ID is rejected with a 409. If a response is lost, the ID is how you find the order.
  2. No retry under a new ID. A timeout does not say whether the order reached the book. Resend with the same client_order_id, or list your orders on that ticker and match the ID first. A retry under a fresh ID can place the order twice.
  3. Prices from the book. last_price_dollars is the last trade, which can be hours old. A buy fills against the ask and its size, so the ask is the price that belongs in any expected value math for a buy.
  4. Dollar strings checked against the grid. Mixing up cents and dollars is the classic bug in code ported from the integer-cent API.
  5. Headroom in the Write bucket. Cancels share it with new orders, so a client that spends it all on new orders can find a cancel stuck behind a 429.
  6. Demo first. Kalshi warns that demo prices and behavior may differ from production, so demo tests the plumbing.

GET /api_keys also returns api_key_region_expiration_ts: the time your location attestation for API key requests lapses. Kalshi's changelog says that after that time, keys cannot trade Sports, Elections and Entertainment markets.

Questions traders ask

Is the Kalshi API free?

Kalshi's documentation lists no charge for API access. Every account starts on the Basic rate limit tier. Advanced comes from an upgrade endpoint, and higher tiers come from trading volume. What costs money is trading. Each fill pays the series' normal exchange fee, which depends on its fee type and on whether your order took or made liquidity. The fee calculator prices a fill.

Do I need an API key to get Kalshi market data?

No key is needed for REST market data. Series, events, markets, trades and order books are public endpoints that take no headers. The WebSocket is different. Kalshi authenticates every WebSocket connection, even one that only carries public channels such as ticker and trade.

Does Kalshi have a sandbox or demo API?

Yes. The demo environment at demo.kalshi.co runs on mock funds, and its REST base is external-api.demo.kalshi.co/trade-api/v2. Demo and production credentials are separate, so a demo key works only in demo. Kalshi warns that demo prices and behavior may not match real markets.

Can you place a market order through the Kalshi API?

No. Kalshi retired the market order type in September 2025, and the V2 order endpoint requires a price on every order. To trade immediately, send an immediate_or_cancel order at the worst price you will accept. Kalshi cancels anything that cannot fill at that price or better.

What are the Kalshi API rate limits?

Basic accounts get 200 read tokens and 100 write tokens per second. Most requests cost 10 tokens, so that is about 20 reads and 10 orders a second. Higher tiers run up to Prestige at 12,000 read and 9,600 write tokens. A 429 response means the bucket is empty.

Is there an official Kalshi Python SDK?

Yes. Kalshi publishes kalshi_python_sync and kalshi_python_async on PyPI, plus kalshi-typescript on npm. Ed25519 keys need version 3.31.0 or later. The old kalshi-python package is deprecated. Kalshi says the SDKs can lag the API and treats its OpenAPI specification as the source of truth.

Sources

EVDESK is research and trading software. It is not a broker, not an exchange, not a fund and not investment advice. Prices in examples are illustrative. The arithmetic on this page is general information for your own decisions. Prediction-market trading can lose money. See the Risk Disclosure. Not affiliated with Kalshi, Inc.