Skip to content

Latest commit

 

History

History
146 lines (113 loc) · 5.35 KB

File metadata and controls

146 lines (113 loc) · 5.35 KB

Quickstart

This page walks you from a blank environment to your first authenticated request against Kalshi's demo API.

Install

pip install kalshi-sdk

Requires Python 3.12 or newer. Optional extras: pandas, polars, or all.

Create an API key

  1. 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:
  2. From the same environment you signed into, open your account settings and create an API key:
  3. 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.

Hello, world (authenticated)

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.

Hello, markets (no auth)

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.

Async

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())

Place an order (demo)

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.

Where to next