# Kalshi API in Python: read markets, sign requests, place orders and use the demo

Kalshi's API in Python: read markets and order books with no key, sign requests with Ed25519, place an order, and test on the demo with mock funds.

Source: https://rizon.markets/learn/kalshi-api-python (updated 2026-11-05)

> **Short answer:** Market data at `https://external-api.kalshi.com/trade-api/v2` needs no key. Orders need a request signed with your private key. To test, use the [demo](https://docs.kalshi.com/getting_started/demo_env), which has its own keys and mock funds, at `https://external-api.demo.kalshi.co/trade-api/v2`.

We ran the read code below on Nov 5, 2026. We ran the signing code with a throwaway key, and the demo answered "could not authenticate" as it should. We didn't place an order, because that needs an account. Kalshi's own samples use plain `requests` and `cryptography`, so we do too.

## Read markets and an order book

```python
import requests

BASE = "https://external-api.kalshi.com/trade-api/v2"

markets = requests.get(f"{BASE}/markets", params={"series_ticker": "KXNFLTOTAL", "status": "open", "limit": 3}).json()["markets"]
for market in markets:
    print(market["ticker"], market["yes_bid_dollars"], market["yes_ask_dollars"])

ticker = markets[0]["ticker"]
book = requests.get(f"{BASE}/markets/{ticker}/orderbook").json()["orderbook_fp"]

best_yes_bid = max(float(price) for price, size in book["yes_dollars"])
best_no_bid = max(float(price) for price, size in book["no_dollars"])
print("bid", best_yes_bid, "ask", round(1 - best_no_bid, 4))
```

Kalshi's book lists only bids, for Yes and for No. A No bid at 96¢ is the same as a Yes ask at 4¢, so the Yes ask is 1 minus the best No bid. Prices and sizes arrive as strings.

Older examples use `api.elections.kalshi.com`. It still answered on Nov 5, but [the docs](https://docs.kalshi.com/getting_started/quick_start_market_data) now show `external-api.kalshi.com`.

## Sign a request

Each private request carries three headers: your key ID, a millisecond timestamp and a signature. You sign the timestamp, the method and the path, without its query string. [Kalshi recommends Ed25519 keys](https://docs.kalshi.com/getting_started/api_keys), and RSA-PSS also works.

```python
import base64
import os
import time

import requests
from cryptography.hazmat.primitives import serialization

BASE = "https://external-api.demo.kalshi.co"
KEY_ID = os.environ["KALSHI_KEY_ID"]
with open(os.environ["KALSHI_KEY_FILE"], "rb") as f:
    private_key = serialization.load_pem_private_key(f.read(), password=None)

def signed(method: str, path: str) -> dict:
    timestamp = str(int(time.time() * 1000))
    message = timestamp + method + path.split("?")[0]
    signature = base64.b64encode(private_key.sign(message.encode())).decode()
    return {
        "KALSHI-ACCESS-KEY": KEY_ID,
        "KALSHI-ACCESS-TIMESTAMP": timestamp,
        "KALSHI-ACCESS-SIGNATURE": signature,
    }

path = "/trade-api/v2/portfolio/balance"
print(requests.get(BASE + path, headers=signed("GET", path)).json())
```

Kalshi shows the private key once, when you create it, so save it then. The demo and production have separate accounts and separate keys.

## Place an order

The [current order endpoint](https://docs.kalshi.com/api-reference/orders/create-order-v2) takes `bid` or `ask` on the Yes side, with prices and sizes as strings:

```python
import uuid

path = "/trade-api/v2/portfolio/events/orders"
order = {
    "ticker": ticker,
    "client_order_id": str(uuid.uuid4()),
    "side": "bid",
    "count": "10.00",
    "price": "0.5600",
    "time_in_force": "good_till_canceled",
    "self_trade_prevention_type": "taker_at_cross",
    "post_only": True,
}
print(requests.post(BASE + path, headers=signed("POST", path), json=order).json())
```

`client_order_id` lets you spot a duplicate if a request times out and you send it again. `time_in_force` can also be `fill_or_kill` or `immediate_or_cancel`, and to make an order expire you set `expiration_time`. The demo has different tickers from production, so read a market from the host you're trading on.

## Rate limits

[Kalshi's limits](https://docs.kalshi.com/getting_started/rate_limits) are budgets of tokens per second, and most requests cost 10 tokens.

| Tier | Read (tokens a second) | Write (tokens a second) | Reads a second | Writes a second |
|---|---:|---:|---:|---:|
| Basic | 200 | 100 | 20 | 10 |
| Advanced | 300 | 300 | 30 | 30 |
| Expert | 600 | 600 | 60 | 60 |

Basic comes with sign-up, and Advanced is one call to an upgrade endpoint. Higher tiers depend on your share of trading volume.

## Fees

The taker fee is `0.07 × contracts × price × (1 − price)`, rounded up to the cent. Makers pay nothing by default, though some markets charge them. The [fee calculator](https://rizon.markets/learn/polymarket-fee-calculator) works it out at any price. The fee is only part of what a trade costs, because you also pay the spread.

## Test your bot

The demo is the place to test your signing and your orders. Kalshi warns that its prices and behavior "may not be reflective of those in real markets", so it can't tell you whether your strategy has an edge. To run a quoting bot against live-moving prices with practice money and no sign-up, try [Rizon's API](https://rizon.markets/docs), or compare the options in [where to test a trading bot without real money](https://rizon.markets/learn/test-a-trading-bot-without-real-money). It's a different contract, so it tests your logic rather than Kalshi's matching.

## FAQ

### Do I need a key to read Kalshi's market data?

No. Series, events, markets and order books are public. The WebSocket needs a signed connection, even for market data.

### Does Kalshi have a demo API?

Yes, at `https://external-api.demo.kalshi.co/trade-api/v2`, with mock funds and its own [sign-up](https://demo.kalshi.co/).

### Why does the order book only show bids?

Because Yes and No mirror each other. An offer to sell Yes at 57¢ is the same as a bid for No at 43¢, so Kalshi lists both as bids.

### Is there an official Kalshi Python SDK?

The `kalshi-python` package exists on PyPI, but the current release we found is about a year old and uses the older order format, and we couldn't confirm who maintains it. The docs' samples work without it.

### Which key type should I use?

Ed25519, which Kalshi recommends. Use RSA only if your client can't do anything else.
