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.2.1.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.2.1-py3-none-any.whl (76.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pykalshi_client-1.2.1.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.2.1.tar.gz
Algorithm Hash digest
SHA256 01340f673366f5ed659402325c1c4500d1bbbb5f68ccfe9888d52467c02f04ba
MD5 915a76d2f7e551435cdcc9df754b3a47
BLAKE2b-256 ec31d7665c785dbfc6578b38f75209f35d7399a3babd49ec6df2781bf64d18b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for pykalshi_client-1.2.1.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.2.1-py3-none-any.whl.

File metadata

  • Download URL: pykalshi_client-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 76.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pykalshi_client-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b86e3f144cc59270842ad401010b79767e41c2576c774d737d34cdd3dd3f5fe8
MD5 d6dcab0ea2c4f4bf6f59f93c5445db4b
BLAKE2b-256 bee06db9f4725c2aba42f649d7304f438658c5e8a5b9cd41b9bc07f498f5a411

See more details on using hashes here.

Provenance

The following attestation bundles were made for pykalshi_client-1.2.1-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