Skip to main content

Async Python client for the Kalshi prediction market API — typed HTTP + WebSocket

Project description

PyPI License CI OpenAPI AsyncAPI

pykalshi-client

Async Python client for the Kalshi prediction market API.

Pure 1:1 reflection of the Kalshi API. Each method maps to exactly one API endpoint. Pagination loops, delta polling, and other application-level helpers belong in the consumer, not here.

Install

pip install pykalshi-client

# With OpenTelemetry support
pip install "pykalshi-client[otel]"

Note: The PyPI package is pykalshi-client, but the import name is pykalshi:

from pykalshi import KalshiHttpClient

Quick Start

HTTP Client

from pykalshi import KalshiCredentials, KalshiHttpClient, ClientConfig, Environment

creds = KalshiCredentials.from_key_file("your-key-id", "~/.kalshi/private.pem")
# Or from PEM string directly:
# creds = KalshiCredentials.from_pem_string("your-key-id", pem_string)

config = ClientConfig(environment=Environment.DEMO)

async with KalshiHttpClient(creds, config) as client:
    # Markets — returns typed GetMarketsResponse
    resp = await client.get_markets(status="open", limit=10)
    for market in resp.markets:
        print(f"{market.ticker}: {market.yes_bid_dollars}/{market.yes_ask_dollars}")

    # Place an order — returns typed CreateOrderResponse
    result = await client.create_order(
        ticker="KXBTC-100K",
        side="yes",
        action="buy",
        count=10,
        yes_price=50,
    )
    print(result.order.order_id)

    # Cancel it
    await client.cancel_order(result.order.order_id)

    # Raw dict access is still available via .model_dump()
    raw = result.model_dump()
    print(raw["order"]["order_id"])

Convenience Constructors

from pykalshi import create_http_client, create_ws_client, Environment

client = create_http_client("key-id", "/path/to/key.pem", Environment.DEMO)
ws = create_ws_client("key-id", "/path/to/key.pem", Environment.DEMO)

WebSocket Client

from pykalshi import KalshiCredentials, KalshiWebSocketClient, ClientConfig

creds = KalshiCredentials.from_key_file("your-key-id", "~/.kalshi/private.pem")

async def on_message(msg: str):
    print(msg)

ws = KalshiWebSocketClient(creds, ClientConfig(), on_message_callback=on_message)
await ws.connect()
await ws.add_market("KXBTC-100K", ["orderbook_delta", "ticker", "trade"])

# listener_loop auto-reconnects on network errors.
# Sequence gaps raise KalshiSequenceGapError — handle with resubscribe_channel:
from pykalshi.exceptions import KalshiSequenceGapError

while True:
    try:
        await ws.listener_loop()
    except KalshiSequenceGapError as e:
        await ws.resubscribe_channel(e.channel)

Typed Models

All client methods return typed Pydantic v2 response models — no manual parsing needed. Models are auto-generated from the Kalshi OpenAPI and AsyncAPI specs, giving you IDE autocomplete, field descriptions, and runtime validation.

# Responses are already typed — just use attribute access
result = await client.create_order(ticker="KXBTC-100K", side="yes", action="buy", count=1, yes_price=50)
print(result.order.order_id)
print(result.order.status)          # IDE autocomplete works here
print(result.order.yes_price_dollars)

markets = await client.get_markets(status="open", limit=5)
for market in markets.markets:
    print(f"{market.ticker}: {market.yes_bid_dollars}/{market.yes_ask_dollars}")

# Raw dict access via .model_dump() or the lower-level api.* modules
from pykalshi.api import markets as markets_api
raw = await markets_api.get_markets(client, status="open", limit=5)  # returns dict[str, Any]

144 types generated across 4 files:

File Contents Count
models/enums.py OrderStatus, ExchangeInstance, SelfTradePreventionType 3
models/core.py Order, Market, Fill, Position, ExchangeStatus, ... 49
models/requests.py CreateOrderRequest, AmendOrderRequest, ... 16
models/responses.py CreateOrderResponse, GetOrdersResponse, GetMarketsResponse, ... 76

All models use extra="ignore" (forward-compatible with spec additions) and include field descriptions from the spec for IDE tooltips.

Configuration

HTTP

Field Default Description
KALSHI_HTTP_BASE_URL (env var) Derived from environment Override HTTP base URL
environment DEMO Environment.DEMO or Environment.PROD
read_rate 20.0 Fallback read token rate (auto-configured from API on first request)
write_rate 10.0 Fallback write token rate (auto-configured from API on first request)
auto_configure_rates True Fetch actual rate limits and per-endpoint costs from /account/limits and /account/endpoint_costs on first request. Set False to use fallback values only.
max_retries 4 Retry count for 429s and network errors
base_retry_delay 0.1s Initial backoff delay
connect_timeout 5.0s Connection timeout
read_timeout 30.0s Read timeout
write_timeout 10.0s Write timeout

WebSocket

Field Default Description
KALSHI_WS_BASE_URL (env var) Derived from environment Override WS base URL

WebSocket reconnection uses exponential backoff. Sequence gaps raise KalshiSequenceGapError for consumer-controlled recovery via resubscribe_channel().

API Coverage

See API Coverage for per-endpoint test coverage (auto-updated by CI on every push to main).

Skipped (not accessible): subaccounts, FCM, summary/resting_order_value.

Architecture

pykalshi/
  Core:
    auth.py            KalshiCredentials (RSA-PSS signing)
    config.py          Environment + ClientConfig (frozen dataclass)
    protocols.py       Typed Protocol classes (consumer contract)
    exceptions.py      KalshiError hierarchy
    rate_limiter.py    ReadWriteTokenBucket (disjoint read/write)
    _observability.py  OTel no-op facade (zero overhead when not installed)

  HTTP:
    http_client.py     KalshiHttpClient (rate limiting, retry, OTel)
    api/               One module per domain (orders, markets, events, ...)

  WebSocket:
    ws_client.py       KalshiWebSocketClient (subscriptions, reconnect)

  Shared:
    models/            Pydantic v2 models — auto-generated from OpenAPI/AsyncAPI specs
      enums.py           Enum types (OrderStatus, ExchangeInstance, ...)
      core.py            Domain objects (Order, Market, Fill, Position, ...)
      requests.py        Request body schemas (CreateOrderRequest, ...)
      responses.py       Response wrappers (CreateOrderResponse, GetOrdersResponse, ...)
      ws.py              WebSocket message models (Channel enum, FillMsg, TickerMsg, ...)
    testing/           Mock transport factory + pytest fixtures

tools/
  generate_models.py      Fetch OpenAPI spec and generate HTTP models
  generate_ws_models.py   Fetch AsyncAPI spec and generate WebSocket models
  sync_docstrings.py      Sync API function docstrings from OpenAPI spec

Tooling

The tools/ directory contains scripts that sync parts of the codebase with the Kalshi OpenAPI spec:

# Regenerate HTTP models (enums, core objects, requests, responses)
uv run python tools/generate_models.py

# Regenerate WebSocket models (Channel enum, message types)
uv run python tools/generate_ws_models.py

# Sync API function docstrings (summary + description from spec)
uv run python tools/sync_docstrings.py

Models are auto-generated because they tolerate spec inaccuracies (extra="ignore"). API function signatures are hand-written and manually verified against real API behavior.

Testing

HTTP

# Unit tests (fast, no credentials needed)
uv run pytest tests/test_http_client.py tests/test_auth.py tests/test_rate_limiter.py -v

# Integration tests (requires .env with DEMO credentials)
uv run pytest tests/test_integration.py -v

# OpenAPI spec validation (fetches live spec from docs.kalshi.com)
uv run pytest tests/test_openapi_validation.py -v -s

WebSocket

# Unit tests (fast, no credentials needed)
uv run pytest tests/test_ws_client.py -v

# Integration tests (requires .env with PROD read-only credentials)
uv run pytest tests/test_ws_integration.py -v

# AsyncAPI spec validation (fetches live spec from docs.kalshi.com)
uv run pytest tests/test_asyncapi_validation.py -v -s

All

# Everything
uv run pytest tests/ -v

# Lint
uv run ruff check src/ tests/

Typed Contract

Consumers should type-annotate against Protocol classes, not concrete implementations:

HTTP

from pykalshi import KalshiHttpClientProtocol

async def fetch_balance(client: KalshiHttpClientProtocol) -> int:
    result = await client.get_balance()
    return result.balance  # typed attribute access

WebSocket

from pykalshi import KalshiWebSocketClientProtocol

async def subscribe_orderbook(ws: KalshiWebSocketClientProtocol, ticker: str) -> None:
    await ws.add_market(ticker, ["orderbook_delta"])

This enables mocking and swapping implementations freely. The library includes a py.typed marker (PEP 561) for full type checking support.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pykalshi_client-1.3.0.tar.gz (102.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pykalshi_client-1.3.0-py3-none-any.whl (76.4 kB view details)

Uploaded Python 3

File details

Details for the file pykalshi_client-1.3.0.tar.gz.

File metadata

  • Download URL: pykalshi_client-1.3.0.tar.gz
  • Upload date:
  • Size: 102.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pykalshi_client-1.3.0.tar.gz
Algorithm Hash digest
SHA256 2d04bed03d011b655b56f4958fdcffd0f2c2ac7bbb2c0cba2dbcfd42f0619564
MD5 80bbfddb122f40f38dc0156a552d76b3
BLAKE2b-256 3edb7dc56648794aaab6dd718974c399f8b3d9e3f4a3e1907296712d723065a7

See more details on using hashes here.

Provenance

The following attestation bundles were made for pykalshi_client-1.3.0.tar.gz:

Publisher: publish.yml on Kalarb/pykalshi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pykalshi_client-1.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pykalshi_client-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 aebaca3d943b149e58d4a8a4a31bf22517051ad99219bbbd9638c6b6fe29aa47
MD5 da6ad5fe10c733868789a6ddc98a0b15
BLAKE2b-256 10c7ad341ddd9184cab84128bff818365dc28cade2cafcece90990997d9fc4bc

See more details on using hashes here.

Provenance

The following attestation bundles were made for pykalshi_client-1.3.0-py3-none-any.whl:

Publisher: publish.yml on Kalarb/pykalshi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page