This page walks you from a blank environment to your first authenticated request against Kalshi's demo API.
pip install kalshi-sdkRequires Python 3.12 or newer. Optional extras: pandas, polars, or all.
- Sign in to your Kalshi account. Demo and production are separate accounts with
separate API keys — pick one for now and create the account there:
- Demo (recommended for development): demo.kalshi.co
- Production: kalshi.com
- From the same environment you signed into, open your account settings and create
an API key:
- Demo: demo.kalshi.co/account/profile
- Production: kalshi.com/account/profile
- Download the private key PEM file and store it somewhere safe, e.g.
~/.kalshi/private_key.pem. Treat it like a password.
A demo key cannot authenticate against production and vice versa. The rest of this
guide assumes a demo key and demo=True.
You can also mint keys programmatically once authenticated — see API keys.
You do not need an API key to read most public market data — skip ahead to "Hello, markets" (no auth) if you just want to browse.
from kalshi import KalshiClient
with KalshiClient(
key_id="your-key-id",
private_key_path="~/.kalshi/private_key.pem",
demo=True, # use the sandbox while learning
) as client:
page = client.markets.list(status="open", limit=5)
for market in page:
print(market.ticker, market.yes_bid, market.yes_ask)The client is a context manager — the underlying httpx.Client is closed on
exit. If you can't use a with block, call client.close() yourself.
The constructor is keyword-only; passing an empty key_id raises ValueError.
Most public endpoints work without credentials. The client is "unauthenticated" but read-only resource methods on public endpoints still function:
from kalshi import KalshiClient
with KalshiClient(demo=True) as client:
assert client.is_authenticated is False
markets = client.markets.list(status="open", limit=5)
for market in markets:
print(market.ticker)A small number of "public-looking" endpoints actually require auth — notably
markets.orderbook(), markets.bulk_orderbooks(), and
series.forecast_percentile_history(). Calling those on an unauthenticated
client raises AuthRequiredError before the network. Calling a
private endpoint (orders, portfolio, …) on an authenticated-but-wrong-scope
client comes back as a server 401/403 mapped to
KalshiAuthError.
The async client mirrors the sync client. list_all() returns an
AsyncIterator directly, so async for just works:
import asyncio
from kalshi import AsyncKalshiClient
async def main() -> None:
async with AsyncKalshiClient(
key_id="your-key-id",
private_key_path="~/.kalshi/private_key.pem",
demo=True,
) as client:
async for market in client.markets.list_all(status="open"):
print(market.ticker, market.yes_bid)
asyncio.run(main())Order writes go through the V2 /portfolio/events/orders family. There is no
keyword overload — you always build a request model and pass it as request=:
import uuid
from decimal import Decimal
from kalshi import KalshiClient
from kalshi.models import CreateOrderV2Request
with KalshiClient.from_env() as client:
order = client.orders.create_v2(
request=CreateOrderV2Request(
ticker="EXAMPLE-25-T",
client_order_id=str(uuid.uuid4()), # idempotency key (see below)
side="bid", # book side: "bid"/"ask", not "yes"/"no"
count=Decimal("10"),
price=Decimal("0.65"), # 65¢ — strings or Decimals, never float
time_in_force="good_till_canceled",
self_trade_prevention_type="taker_at_cross",
)
)
print(order.order_id, order.fill_count, order.remaining_count)!!! warning "POST is never retried automatically"
The transport never retries POST (or DELETE) requests, to avoid duplicate
orders. Pass a fresh client_order_id on every create_v2() call so you can safely
retry from your application layer without double-filling. See
Retries & idempotency.
!!! note "side is the book side, not the outcome"
CreateOrderV2Request.side is "bid" or "ask" (the resting book side), not
"yes"/"no". client_order_id is required on the model, and count and
price are Decimal — passing a float for a price is rejected.
Prices are decimal dollars per the Kalshi spec. Internally the SDK uses
Decimal via the DollarDecimal type — never float.
- Authentication — all the ways to provide credentials.
- Configuration — timeouts, retries, HTTP/2, custom transports.
- Concepts — RFQs, milestones, multivariate, subaccounts.
- Resources overview — every resource grouped by area.
- API Reference — full auto-generated reference.