Skip to content

API Reference

Auto-generated from docstrings on the public surface of the kalshi package.

For narrative coverage of any of these, follow the section links — every class here also has a dedicated user-facing page elsewhere in the docs.

Clients

kalshi.client.KalshiClient

KalshiClient(
    *,
    key_id: str | None = None,
    private_key_path: str | Path | None = None,
    private_key: str | bytes | None = None,
    auth: KalshiAuth | None = None,
    config: KalshiConfig | None = None,
    demo: bool = False,
    base_url: str | None = None,
    timeout: float | None = None,
    max_retries: int | None = None,
    transport: BaseTransport | None = None
)

Synchronous client for the Kalshi prediction markets API.

Usage

with KalshiClient(key_id="...", private_key_path="~/.kalshi/key.pem") as client: markets = client.markets.list(status="open") for market in markets: print(market.ticker, market.yes_ask)

Or without context manager

client = KalshiClient(key_id="...", private_key_path="~/.kalshi/key.pem") try: markets = client.markets.list() finally: client.close()

is_authenticated property

is_authenticated: bool

Whether this client has auth credentials configured.

from_env classmethod

from_env(**kwargs: object) -> KalshiClient

Create client from environment variables.

Reads

KALSHI_KEY_ID (optional — omit for unauthenticated access) KALSHI_PRIVATE_KEY (PEM string) or KALSHI_PRIVATE_KEY_PATH (file path) KALSHI_API_BASE_URL (optional, overrides base_url) KALSHI_DEMO (optional, "true" for demo environment)

Returns an unauthenticated client if no credentials are configured.

close

close() -> None

Close the underlying HTTP connection pool.

kalshi.async_client.AsyncKalshiClient

AsyncKalshiClient(
    *,
    key_id: str | None = None,
    private_key_path: str | Path | None = None,
    private_key: str | bytes | None = None,
    auth: KalshiAuth | None = None,
    config: KalshiConfig | None = None,
    demo: bool = False,
    base_url: str | None = None,
    timeout: float | None = None,
    max_retries: int | None = None,
    transport: AsyncBaseTransport | None = None
)

Asynchronous client for the Kalshi prediction markets API.

Usage

async with AsyncKalshiClient(key_id="...", private_key_path="~/.kalshi/key.pem") as client: markets = await client.markets.list(status="open") for market in markets: print(market.ticker, market.yes_ask)

is_authenticated property

is_authenticated: bool

Whether this client has auth credentials configured.

ws property

ws: KalshiWebSocket

WebSocket client for real-time streaming.

.. note:: Each access returns a new KalshiWebSocket instance. Per-instance state (callbacks registered via .on(), pending subscriptions) does not carry across accesses — assign the property to a local variable if you need to share state across multiple operations::

    ws = client.ws  # capture once
    @ws.on("ticker_v2")
    async def handle(msg): ...
    async with ws.connect() as session: ...

The sync ``KalshiClient`` does not expose ``.ws``; WebSocket
access is async-only.

Usage::

async with client.ws.connect() as session:
    async for msg in session.subscribe_ticker(tickers=["ECON-GDP-25Q1"]):
        print(msg.msg.yes_bid)

from_env classmethod

from_env(**kwargs: object) -> AsyncKalshiClient

Create async client from environment variables.

Reads

KALSHI_KEY_ID (optional — omit for unauthenticated access) KALSHI_PRIVATE_KEY (PEM string) or KALSHI_PRIVATE_KEY_PATH (file path) KALSHI_API_BASE_URL (optional, overrides base_url) KALSHI_DEMO (optional, "true" for demo environment)

Returns an unauthenticated client if no credentials are configured.

close async

close() -> None

Close the underlying async HTTP connection pool.

Configuration

kalshi.config.KalshiConfig dataclass

KalshiConfig(
    base_url: str = PRODUCTION_BASE_URL,
    timeout: float = DEFAULT_TIMEOUT,
    max_retries: int = DEFAULT_MAX_RETRIES,
    retry_base_delay: float = 0.5,
    retry_max_delay: float = 30.0,
    extra_headers: dict[str, str] = dict(),
    ws_base_url: str = PRODUCTION_WS_URL,
    ws_max_retries: int = DEFAULT_WS_MAX_RETRIES,
    http2: bool = False,
    limits: Limits | None = None,
)

Client configuration.

Attributes:

Name Type Description
base_url str

API base URL. Defaults to production.

timeout float

Request timeout in seconds. Defaults to 30.

max_retries int

Max retry attempts for transient errors. Defaults to 3.

retry_base_delay float

Base delay in seconds for exponential backoff. Defaults to 0.5.

retry_max_delay float

Maximum delay in seconds for backoff. Defaults to 30.

http2 bool

Enable HTTP/2 for REST requests. Off by default for compat. Requires the h2 package (install httpx[http2] or h2).

limits Limits | None

Custom httpx.Limits for connection pool tuning. None uses httpx defaults.

production classmethod

production(**kwargs: object) -> KalshiConfig

Create config for Kalshi production environment.

demo classmethod

demo(**kwargs: object) -> KalshiConfig

Create config for Kalshi demo/sandbox environment.

Authentication

kalshi.auth.KalshiAuth

KalshiAuth(key_id: str, private_key: RSAPrivateKey)

RSA-PSS request signer for the Kalshi API.

The private key is loaded once at construction time and cached in memory. Thread-safe: RSA signing is stateless.

from_key_path classmethod

from_key_path(
    key_id: str, key_path: str | Path
) -> KalshiAuth

Load auth from a PEM private key file.

Supports ~ expansion (e.g., ~/kalshi.pem).

from_pem classmethod

from_pem(key_id: str, pem_data: str | bytes) -> KalshiAuth

Load auth from PEM-encoded private key content.

from_env classmethod

from_env() -> KalshiAuth

Load auth from environment variables.

Reads

KALSHI_KEY_ID (required) KALSHI_PRIVATE_KEY (PEM string) or KALSHI_PRIVATE_KEY_PATH (file path)

try_from_env classmethod

try_from_env() -> KalshiAuth | None

Load auth from environment variables, returning None if not configured.

Returns None if KALSHI_KEY_ID is not set, or if neither KALSHI_PRIVATE_KEY nor KALSHI_PRIVATE_KEY_PATH is set.

Note: Does not raise on missing variables, but may still raise KalshiAuthError if variables are set with invalid data (e.g., malformed PEM content or nonexistent key file path).

sign_request

sign_request(
    method: str, path: str, timestamp_ms: int | None = None
) -> dict[str, str]

Sign a request and return the auth headers.

Parameters:

Name Type Description Default
method str

HTTP method (GET, POST, DELETE, etc.)

required
path str

Full API path (e.g., /trade-api/v2/markets). Query params are stripped.

required
timestamp_ms int | None

Unix timestamp in milliseconds. Auto-generated if None.

None

Returns:

Type Description
dict[str, str]

Dict with KALSHI-ACCESS-KEY, KALSHI-ACCESS-SIGNATURE, KALSHI-ACCESS-TIMESTAMP.

sign_request_async async

sign_request_async(
    method: str, path: str, timestamp_ms: int | None = None
) -> dict[str, str]

Async variant of :meth:sign_request that offloads to a dedicated executor.

Uses loop.run_in_executor(self._sign_executor, ...) so the ~1-10 ms of RSA-PSS CPU does not block the asyncio event loop. The executor is dedicated (not asyncio's default pool) so signs don't queue behind getaddrinfo / file I/O / other to_thread() work — relevant during WS reconnect storms where DNS dominates.

The sync :meth:sign_request API is unchanged; callers running on the sync transport should keep using it.

close

close() -> None

Shut down the sign-offload executor if one was created.

Idempotent. Safe to call without an executor having been initialised. Long-lived clients ordinarily rely on interpreter-shutdown cleanup (atexit-joined executor threads); this hook is for tests and other callers that want deterministic teardown.

Errors

See Errors — that page is the canonical autodoc reference for every exception class.

Pagination

kalshi.models.common.Page

Bases: BaseModel, Generic[T]

A page of results from a list endpoint.

Iterable over items. Also exposes cursor metadata for manual pagination.

Usage

page = client.markets.list(limit=50) for item in page: # iterate items process(item) print(page.cursor) # access cursor print(page.has_next) # check if more pages exist

to_dataframe

to_dataframe() -> pandas.DataFrame

Return page items as a pandas DataFrame (requires kalshi-sdk[pandas]).

to_polars

to_polars() -> polars.DataFrame

Return page items as a polars DataFrame (requires kalshi-sdk[polars]).

Custom types

kalshi.types

Custom types and Pydantic field helpers for the Kalshi SDK.

DollarDecimal module-attribute

DollarDecimal = Annotated[
    Decimal,
    BeforeValidator(_coerce_decimal),
    PlainSerializer(_decimal_to_str, return_type=str),
]

A Decimal field that handles bidirectional conversion for Kalshi dollar values.

  • Parse: Accepts str/int/float/Decimal, converts via Decimal(str(value))
  • Serialize: Outputs string representation for API requests

FixedPointCount module-attribute

FixedPointCount = Annotated[
    Decimal,
    BeforeValidator(_coerce_decimal),
    PlainSerializer(_decimal_to_str, return_type=str),
]

A Decimal field that handles bidirectional conversion for Kalshi count/volume values.

  • Parse: Accepts str/int/float/Decimal, converts via Decimal(str(value))
  • Serialize: Outputs string representation for API requests

NullableList module-attribute

NullableList = Annotated[
    list[T], BeforeValidator(_none_to_empty_list)
]

A list[T] that tolerates server-returned null by coercing to [].

Use on response-model fields (extra='allow') whose spec says 'array' but where the live API has been observed to return null. Do NOT use on request bodies — those should enforce strict validation.

to_decimal

to_decimal(value: int | float | str | Decimal) -> Decimal

Convert a user-supplied price/count value to Decimal safely.

Always goes through str() to avoid float representation errors. e.g., to_decimal(0.65) returns Decimal("0.65"), not Decimal(0.65).

Request models

kalshi.models.orders.CreateOrderRequest

Bases: BaseModel

Parameters for creating an order.

Price fields serialize with _dollars suffix. count is a Decimal and serializes as count_fp (FixedPointCount string); the spec accepts either count or count_fp key, but the SDK commits to a single wire shape.

buy_max_cost is integer cents (per OpenAPI spec: "Maximum cost in cents"). Pass e.g. 500 for a $5.00 cap, NOT 5.00. Passing a decimal string like "5.00" raises ValidationError.

The SDK previously exposed a type: str = "limit" field never defined in the spec's CreateOrderRequest schema. v0.8.0 removes it. Callers passing type="market" (or similar) now get a ValidationError at construction time.

ticker, side, and action are all required by the spec. Pre-v2.3.0 the SDK defaulted action to "buy" as a convenience; that default has been removed to match the spec required-set (#172).

See kalshi.resources.orders.OrdersResource.create for the user-facing method that builds this model internally.

kalshi.models.orders.AmendOrderRequest

Bases: BaseModel

Parameters for amending an open order.

Matches spec components.schemas.AmendOrderRequest. Required fields (ticker, side, action) mirror the spec's required list. Price fields serialize with _dollars suffix; count serializes as count_fp (FixedPointCount).

Cent-form yes_price/no_price spec properties are NOT on this model — redundant with the _dollars forms. EXCLUSIONS in tests/_contract_support.py records this.

See kalshi.resources.orders.OrdersResource.amend — v0.8.0 builds this model internally; the public method signature is unchanged.

kalshi.models.orders.DecreaseOrderRequest

Bases: BaseModel

Parameters for decreasing an open order's size.

Matches spec components.schemas.DecreaseOrderRequest. Spec marks all fields optional, but the server rejects an empty body — so the model enforces XOR at construction: exactly one of reduce_by or reduce_to must be set. This matches the method-level guard in orders.decrease() and keeps model-first construction (v0.9) fail-fast rather than deferring the error to the HTTP call.

See kalshi.resources.orders.OrdersResource.decrease — v0.8.0 builds this model internally; method signature unchanged.

kalshi.models.orders.BatchCreateOrdersRequest

Bases: BaseModel

Wrapper for the POST /portfolio/orders/batched request body.

Matches spec components.schemas.BatchCreateOrdersRequest: a single orders key holding a list of CreateOrderRequest entries. Each nested entry inherits extra="forbid" from CreateOrderRequest itself, so phantom fields in items fail at construction time.

See kalshi.resources.orders.OrdersResource.batch_create — v0.8.0 wraps this model internally; method signature unchanged.

kalshi.models.orders.BatchCancelOrdersRequest

Bases: BaseModel

Wrapper for the DELETE /portfolio/orders/batched request body.

Matches spec components.schemas.BatchCancelOrdersRequest. Spec defines two fields: the preferred orders (list of BatchCancelOrdersRequestOrder) and the deprecated ids (list of string order IDs). SDK v0.8.0 commits to emitting orders only.

The previous SDK sent the deprecated ids field — BREAKING change at the wire level as of v0.8.0. Users calling the public batch_cancel(orders=[...]) method are unaffected.

See kalshi.resources.orders.OrdersResource.batch_cancel.

kalshi.models.orders.BatchCancelOrdersRequestOrder

Bases: BaseModel

A single cancellation entry in a batch cancel request.

Matches spec components.schemas.BatchCancelOrdersRequestOrder. Required: order_id. Optional: subaccount (defaults to 0, primary subaccount).

kalshi.models.api_keys.CreateApiKeyRequest

Bases: BaseModel

Body for POST /api_keys — register a caller-supplied public key.

public_key must be a PEM-encoded RSA public key (i.e. starts with -----BEGIN PUBLIC KEY-----). scopes defaults to full access (["read", "write"]) server-side when omitted. If "write" is included, "read" must be too.

kalshi.models.api_keys.GenerateApiKeyRequest

Bases: BaseModel

Body for POST /api_keys/generate — let Kalshi mint a key pair.

kalshi.models.communications.CreateRFQRequest

Bases: BaseModel

Body for POST /communications/rfqs.

Spec allows contracts (integer) or contracts_fp (fixed-point string); SDK commits to the integer form. Target cost uses _dollars wire suffix. target_cost_centi_cents is deprecated upstream — omitted here.

Spec only requires market_ticker and rest_remainder; contracts and target_cost are both optional and may both be omitted (a "shopping around" RFQ with no committed size or cost). The SDK does not enforce a contracts-or-cost invariant because the spec doesn't.

kalshi.models.communications.CreateQuoteRequest

Bases: BaseModel

Body for POST /communications/quotes.

Unlike order/amend requests, the spec names the wire fields yes_bid / no_bid (no _dollars suffix) for this request. The Quote response fields, however, use the _dollars suffix — handled on the response model.

kalshi.models.communications.AcceptQuoteRequest

Bases: BaseModel

Body for PUT /communications/quotes/{quote_id}/accept.

kalshi.models.multivariate.CreateMarketInMultivariateEventCollectionRequest

Bases: BaseModel

Parameters for POST /multivariate_event_collections/{collection_ticker}.

Matches spec components.schemas.CreateMarketInMultivariateEventCollectionRequest. Required: selected_markets. Optional: with_market_payload.

Carve-out: extra="forbid" on this model rejects unknown top-level keys but NOT unknown keys inside each TickerPair in selected_marketsTickerPair itself is extra="allow" (see its docstring for why). Phantom keys nested inside a TickerPair currently pass through to the wire. Tracked as a v0.9 follow-up.

See kalshi.resources.multivariate.MultivariateCollectionsResource.create_market — v0.8.0 builds this model internally; method signature unchanged.

kalshi.models.multivariate.LookupTickersForMarketInMultivariateEventCollectionRequest

Bases: BaseModel

Parameters for PUT /multivariate_event_collections/{collection_ticker}/lookup.

Matches spec components.schemas.LookupTickersForMarketInMultivariateEventCollectionRequest. Only selected_markets, required.

Carve-out: extra="forbid" on this model rejects unknown top-level keys but NOT unknown keys inside each TickerPair in selected_marketsTickerPair itself is extra="allow" (see its docstring for why). Phantom keys nested inside a TickerPair currently pass through to the wire. Tracked as a v0.9 follow-up.

See kalshi.resources.multivariate.MultivariateCollectionsResource.lookup_tickers — v0.8.0 builds this model internally; method signature unchanged.

kalshi.models.order_groups.CreateOrderGroupRequest

Bases: BaseModel

Create body. SDK sends integer form; spec's contracts_limit_fp string variant unused.

kalshi.models.order_groups.UpdateOrderGroupLimitRequest

Bases: BaseModel

Update-limit body. No subaccount — spec omits SubaccountQuery on /limit.

kalshi.models.subaccounts.ApplySubaccountTransferRequest

Bases: BaseModel

Body for POST /portfolio/subaccounts/transfer.

amount_cents is integer cents per spec (matches the buy_max_cost convention on CreateOrderRequest). Pass 500 for $5.00, never a Decimal. from_subaccount / to_subaccount use 0 for the primary account and a positive integer for numbered subaccounts. The server is the source of truth for the upper bound: spec describes 1-32 in prose but defines no JSON-schema maximum, and demo has been observed allocating values above 32. The SDK validates only the lower bound (ge=0) so server-assigned numbers always round-trip.

kalshi.models.subaccounts.UpdateSubaccountNettingRequest

Bases: BaseModel

Body for PUT /portfolio/subaccounts/netting.

Response models

Markets, events, series

kalshi.models.markets

Market-related models.

MarketStatusLiteral module-attribute

MarketStatusLiteral = Literal[
    "unopened", "open", "paused", "closed", "settled"
]

Market status filter for GET /markets. Spec: MarketStatusQuery enum.

MveFilterLiteral module-attribute

MveFilterLiteral = Literal['only', 'exclude']

Multivariate-event filter for GET /markets. Spec: MveFilterQuery enum.

Market

Bases: BaseModel

A Kalshi prediction market.

Price fields accept both _dollars-suffixed names from the API (e.g. yes_bid_dollars) and short names (e.g. yes_bid). Volume/count fields accept both _fp-suffixed names and short names.

OrderbookLevel

Bases: BaseModel

A single price level in the orderbook.

Quantity is FixedPointCount — fractional contract counts are sent as FixedPointCount strings like "100.50" from the API.

Orderbook

Bases: BaseModel

Orderbook for a market.

BidAskDistribution

Bases: BaseModel

OHLC data for bid/ask prices within a candlestick period.

PriceDistribution

Bases: BaseModel

OHLC data for trade prices within a candlestick period.

Fields are nullable because there may be no trades in a period.

Candlestick

Bases: BaseModel

A candlestick data point for a market.

The API returns nested OHLC objects for yes_bid, yes_ask, and price, plus volume and open interest as FixedPointCount strings.

MarketCandlesticks

Bases: BaseModel

Per-market candlestick bundle in a bulk response.

Spec schema MarketCandlesticksResponse — wraps a ticker and its candlesticks. The outer bulk response is {markets: [...]}.

kalshi.models.events

Event-related models.

EventStatusLiteral module-attribute

EventStatusLiteral = Literal[
    "unopened", "open", "closed", "settled"
]

Event status filter for GET /events. Spec: GetEvents.status query enum.

Event

Bases: BaseModel

A Kalshi event (container for one or more markets).

MarketMetadata

Bases: BaseModel

Metadata for a market within an event.

SettlementSource

Bases: BaseModel

A settlement source for an event.

EventMetadata

Bases: BaseModel

Metadata for an event including images and settlement sources.

kalshi.models.series

Series-related models.

Series

Bases: BaseModel

A Kalshi series (template for recurring events).

SeriesFeeChange

Bases: BaseModel

A scheduled fee change for a series.

EventCandlesticks

Bases: BaseModel

Event-level candlestick data spanning multiple markets.

Unlike market candlesticks (flat list for one market), event candlesticks return a nested structure: one candlestick list per market in the event.

PercentilePoint

Bases: BaseModel

A single forecast value at a given percentile.

ForecastPercentilesPoint

Bases: BaseModel

A forecast data point with percentile breakdowns.

Trading

kalshi.models.orders.Order

Bases: BaseModel

A Kalshi order.

Price/cost fields accept both _dollars-suffixed names from the API (e.g. yes_price_dollars) and short names (e.g. yes_price).

kalshi.models.orders.Fill

Bases: BaseModel

A filled trade.

Price fields accept both _dollars-suffixed names from the API and short names. Count accepts _fp-suffixed name.

kalshi.models.orders.AmendOrderResponse

Bases: BaseModel

Response from amending an order — contains both pre and post-amendment orders.

kalshi.models.orders.OrderQueuePosition

Bases: BaseModel

Queue position for a single resting order.

kalshi.models.portfolio

Portfolio-related models.

SettlementStatusLiteral module-attribute

SettlementStatusLiteral = Literal[
    "all", "unsettled", "settled"
]

Position settlement status filter for GET /fcm/positions. Spec: settlement_status query enum.

PaymentStatusLiteral module-attribute

PaymentStatusLiteral = Literal[
    "pending", "applied", "failed", "returned"
]

Status of a Deposit/Withdrawal. Spec defines two structurally-identical inline enums (Deposit.status, Withdrawal.status); the SDK shares one alias since the values are identical.

PaymentTypeLiteral module-attribute

PaymentTypeLiteral = Literal[
    "ach", "wire", "crypto", "debit", "apm"
]

Payment method used for a deposit/withdrawal.

IndexedBalance

Bases: BaseModel

Balance for a single exchange shard. Added by spec v3.18.0 alongside the balance_breakdown field on :class:Balance.

Currently only exchange_index=0 is supported per spec.

Type note: balance here is DollarDecimal (fixed-point dollar string per spec), unlike :attr:Balance.balance which is integer cents. Same field name, different semantics — be deliberate when reading from balance.balance_breakdown[i].balance versus balance.balance. The :attr:Balance.balance_dollars field rendered in dollars matches the breakdown's units.

Balance

Bases: BaseModel

Account balance.

balance is integer cents (legacy field). balance_dollars is the same value as a fixed-point dollar string, added by spec v3.18.0 and now required on every response. balance_breakdown (optional) splits the total across exchange shards when present.

TotalRestingOrderValue

Bases: BaseModel

Total value of resting orders in cents.

Spec: "intended for use by FCM members (rare)". Non-FCM accounts see 403 on this endpoint (demo audit 2026-04-18).

MarketPosition

Bases: BaseModel

A position in a single market.

EventPosition

Bases: BaseModel

A position aggregated at the event level.

PositionsResponse

Bases: BaseModel

Response from the positions endpoint containing both market and event positions.

Deposit

Bases: BaseModel

A single deposit history entry. Amounts are integer cents.

Withdrawal

Bases: BaseModel

A single withdrawal history entry. Amounts are integer cents.

Settlement

Bases: BaseModel

A settled market position.

Account, subaccounts, API keys, FCM

kalshi.models.account

Account-scoped models — API tier limits, etc.

RateLimit

Bases: BaseModel

Per-direction (read/write) token-bucket rate limit.

The server enforces a token bucket per direction: bucket_capacity tokens are allowed in a burst; refill_rate tokens are added per second up to the cap. Requests above the cap return 429.

EndpointTokenCost

Bases: BaseModel

Configured token cost for a single API endpoint.

AccountEndpointCosts

Bases: BaseModel

Response from GET /account/endpoint_costs.

Lists API v2 endpoints whose configured token cost differs from default_cost. Endpoints using the default are omitted.

AccountApiLimits

Bases: BaseModel

Rate limits associated with the authenticated user's API tier.

NOTE: The published OpenAPI spec (v3.13.0) declares read_limit and write_limit as ints, but the live server returns nested token-bucket objects under read and write. The SDK matches the server. If the spec is corrected upstream, the contract-drift test will flag it.

kalshi.models.subaccounts

Subaccount models — multi-account workflows under one authenticated user.

CreateSubaccountResponse

Bases: BaseModel

Response from POST /portfolio/subaccounts — the new subaccount number.

ApplySubaccountTransferRequest

Bases: BaseModel

Body for POST /portfolio/subaccounts/transfer.

amount_cents is integer cents per spec (matches the buy_max_cost convention on CreateOrderRequest). Pass 500 for $5.00, never a Decimal. from_subaccount / to_subaccount use 0 for the primary account and a positive integer for numbered subaccounts. The server is the source of truth for the upper bound: spec describes 1-32 in prose but defines no JSON-schema maximum, and demo has been observed allocating values above 32. The SDK validates only the lower bound (ge=0) so server-assigned numbers always round-trip.

SubaccountBalance

Bases: BaseModel

Balance for a single subaccount.

Note: updated_ts is a Unix seconds integer per spec (format: int64), not an ISO datetime. RFQ/Quote timestamps are format: date-time and surface as datetime; subaccount timestamps follow the spec's int wire format. Callers wanting a datetime can datetime.fromtimestamp(obj.updated_ts, tz=timezone.utc).

GetSubaccountBalancesResponse

Bases: BaseModel

Response from GET /portfolio/subaccounts/balances.

SubaccountTransfer

Bases: BaseModel

A past transfer between subaccounts.

created_ts is a Unix seconds integer per spec (format: int64), matching SubaccountBalance.updated_ts. This is intentionally different from RFQ/Quote timestamps, which are ISO datetime strings.

UpdateSubaccountNettingRequest

Bases: BaseModel

Body for PUT /portfolio/subaccounts/netting.

SubaccountNettingConfig

Bases: BaseModel

Netting state for a single subaccount.

GetSubaccountNettingResponse

Bases: BaseModel

Response from GET /portfolio/subaccounts/netting.

kalshi.models.api_keys

API Key models — programmatic credentials management.

API keys allow programmatic access without username/password. Each key has a unique api_key_id and a user-provided name for bookkeeping. create accepts a caller-supplied RSA public key (PEM); generate has Kalshi mint a fresh key pair and returns the private key once (store it — it cannot be retrieved again).

ApiKey

Bases: BaseModel

An API key registered on the authenticated user's account.

scopes uses NullableList for the same reason envelope-level list fields do — Kalshi has historically returned JSON null for required list fields, and silently coercing None -> [] is safer than raising a Pydantic ValidationError on a field most callers don't use.

GetApiKeysResponse

Bases: BaseModel

Response from GET /api_keys.

api_keys uses NullableList since Kalshi has returned JSON null for required list fields in other envelopes (see v0.9.0 Series fix). Coercing None -> [] matches the envelope-list pattern established across the rest of the SDK.

CreateApiKeyRequest

Bases: BaseModel

Body for POST /api_keys — register a caller-supplied public key.

public_key must be a PEM-encoded RSA public key (i.e. starts with -----BEGIN PUBLIC KEY-----). scopes defaults to full access (["read", "write"]) server-side when omitted. If "write" is included, "read" must be too.

CreateApiKeyResponse

Bases: BaseModel

Response from POST /api_keys — the new key's ID.

GenerateApiKeyRequest

Bases: BaseModel

Body for POST /api_keys/generate — let Kalshi mint a key pair.

GenerateApiKeyResponse

Bases: BaseModel

Response from POST /api_keys/generate.

private_key is a PEM-encoded RSA private key returned ONLY in this response. Store it securely — Kalshi does not retain a copy and it cannot be retrieved later. The field is typed as :class:pydantic.SecretStr so it prints as '**********' in repr()/logs; call response.private_key.get_secret_value() to retrieve the PEM string when you need to persist it.

kalshi.models.exchange

Exchange-related models.

ExchangeStatus

Bases: BaseModel

Current exchange operational status.

DailySchedule

Bases: BaseModel

A single trading session within a day.

WeeklySchedule

Bases: BaseModel

Weekly trading hours with per-day sessions.

MaintenanceWindow

Bases: BaseModel

A scheduled maintenance window.

Schedule

Bases: BaseModel

Exchange operating schedule.

Announcement

Bases: BaseModel

An exchange-wide announcement.

UserDataTimestamp

Bases: BaseModel

Timestamp when user-scoped exchange data was last validated.

Combine with WebSocket feeds for a live view — REST endpoints like GetBalance/GetOrders/GetFills/GetPositions may lag the exchange by a short delay; as_of_time is the upper bound on that lag.

kalshi.models.historical

Historical data models.

MveHistoricalFilterLiteral module-attribute

MveHistoricalFilterLiteral = Literal['exclude']

Multivariate-event filter for GET /historical/markets. Spec: MveHistoricalFilterQuery.

HistoricalCutoff

Bases: BaseModel

Timestamps defining the boundary between live and historical data.

Trade

Bases: BaseModel

A public trade on the exchange.

RFQ / Quote, multivariate, live data, milestones, structured targets, incentive programs, order groups

kalshi.models.communications

Communications / RFQ models — request-for-quote and quote subsystem.

UserFilterLiteral module-attribute

UserFilterLiteral = Literal['self']

Filter for items created by the authenticated user. Spec: UserFilter enum.

MveSelectedLeg

Bases: BaseModel

A selected leg within a multivariate event collection RFQ.

RFQ

Bases: BaseModel

An RFQ — request for quote on a market.

Quote

Bases: BaseModel

A quote responding to an RFQ.

GetCommunicationsIDResponse

Bases: BaseModel

Wraps the caller's public communications ID.

GetRFQsResponse

Bases: BaseModel

Paginated list of RFQs.

GetRFQResponse

Bases: BaseModel

Single-RFQ envelope.

CreateRFQRequest

Bases: BaseModel

Body for POST /communications/rfqs.

Spec allows contracts (integer) or contracts_fp (fixed-point string); SDK commits to the integer form. Target cost uses _dollars wire suffix. target_cost_centi_cents is deprecated upstream — omitted here.

Spec only requires market_ticker and rest_remainder; contracts and target_cost are both optional and may both be omitted (a "shopping around" RFQ with no committed size or cost). The SDK does not enforce a contracts-or-cost invariant because the spec doesn't.

CreateRFQResponse

Bases: BaseModel

Wraps the newly-created RFQ's id.

GetQuotesResponse

Bases: BaseModel

Paginated list of quotes.

GetQuoteResponse

Bases: BaseModel

Single-quote envelope.

CreateQuoteRequest

Bases: BaseModel

Body for POST /communications/quotes.

Unlike order/amend requests, the spec names the wire fields yes_bid / no_bid (no _dollars suffix) for this request. The Quote response fields, however, use the _dollars suffix — handled on the response model.

CreateQuoteResponse

Bases: BaseModel

Wraps the newly-created quote's id.

AcceptQuoteRequest

Bases: BaseModel

Body for PUT /communications/quotes/{quote_id}/accept.

kalshi.models.multivariate

Multivariate event collection models.

MultivariateCollectionStatusLiteral module-attribute

MultivariateCollectionStatusLiteral = Literal[
    "unopened", "open", "closed"
]

Multivariate collection status filter.

Spec: GetMultivariateEventCollections.status query enum.

AssociatedEvent

Bases: BaseModel

An event associated with a multivariate collection.

MultivariateEventCollection

Bases: BaseModel

A multivariate event collection (combo contract template).

TickerPair

Bases: BaseModel

A market+event ticker pair with side, used in create/lookup request bodies.

Note: extra="allow" is intentional — the spec's TickerPair schema has no additionalProperties: false, and some multivariate responses echo back extra provider-specific keys. Because extra does not inherit, request models that embed list[TickerPair] cannot rely on their own extra="forbid" to reject phantom keys inside items. See v0.9 TODO on nested-model drift coverage.

CreateMarketInMultivariateEventCollectionRequest

Bases: BaseModel

Parameters for POST /multivariate_event_collections/{collection_ticker}.

Matches spec components.schemas.CreateMarketInMultivariateEventCollectionRequest. Required: selected_markets. Optional: with_market_payload.

Carve-out: extra="forbid" on this model rejects unknown top-level keys but NOT unknown keys inside each TickerPair in selected_marketsTickerPair itself is extra="allow" (see its docstring for why). Phantom keys nested inside a TickerPair currently pass through to the wire. Tracked as a v0.9 follow-up.

See kalshi.resources.multivariate.MultivariateCollectionsResource.create_market — v0.8.0 builds this model internally; method signature unchanged.

LookupTickersForMarketInMultivariateEventCollectionRequest

Bases: BaseModel

Parameters for PUT /multivariate_event_collections/{collection_ticker}/lookup.

Matches spec components.schemas.LookupTickersForMarketInMultivariateEventCollectionRequest. Only selected_markets, required.

Carve-out: extra="forbid" on this model rejects unknown top-level keys but NOT unknown keys inside each TickerPair in selected_marketsTickerPair itself is extra="allow" (see its docstring for why). Phantom keys nested inside a TickerPair currently pass through to the wire. Tracked as a v0.9 follow-up.

See kalshi.resources.multivariate.MultivariateCollectionsResource.lookup_tickers — v0.8.0 builds this model internally; method signature unchanged.

CreateMarketResponse

Bases: BaseModel

Response from creating a market in a multivariate collection.

LookupTickersResponse

Bases: BaseModel

Response from looking up tickers in a multivariate collection.

LookupPoint

Bases: BaseModel

A point in the lookup history of a multivariate collection.

kalshi.models.live_data

Live data models — real-time event state keyed by milestone.

LiveData.details is deliberately a loose dict[str, Any]: the spec marks it additionalProperties: true with no fixed schema because the shape varies per milestone type (e.g., football vs. political race). GetGameStatsResponse.pbp (play-by-play) is similarly loose — each period/event is a free-form object.

LiveData

Bases: BaseModel

Live-data payload for a specific milestone.

GetLiveDataResponse

Bases: BaseModel

Response from GET /live_data/milestone/{milestone_id}.

GetLiveDatasResponse

Bases: BaseModel

Response from GET /live_data/batch — multiple milestones at once.

PlayByPlayPeriod

Bases: BaseModel

A single period within a game's play-by-play.

events is a loose list of free-form objects (spec has no fixed event schema) because each sport emits different event shapes. NullableList coerces server-returned null to [] (Kalshi has historically sent null for required list fields).

PlayByPlay

Bases: BaseModel

Play-by-play data organized by period.

GetGameStatsResponse

Bases: BaseModel

Response from GET /live_data/milestone/{milestone_id}/game_stats.

pbp is None for unsupported milestone types or milestones without a Sportradar ID (spec: "Returns null for unsupported milestone types or milestones without a Sportradar ID").

kalshi.models.milestones

Milestone models — structured markers for sports/elections/crypto events.

Milestones anchor live-data feeds (see :mod:kalshi.models.live_data). Each milestone has a type (e.g., football_game, political_race) that determines what the details object contains. last_updated_ts and start_date are ISO date-time strings per spec (unlike subaccount timestamps which are Unix ints).

Milestone

Bases: BaseModel

A structured event milestone (game, race, tournament, etc.).

GetMilestoneResponse

Bases: BaseModel

Response from GET /milestones/{milestone_id}.

GetMilestonesResponse

Bases: BaseModel

Response from GET /milestones — paginated list.

milestones uses NullableList since Kalshi has returned JSON null for required list fields in other envelopes (see the v0.9.0 Series fix). Coercing None -> [] here matches the pattern established on Milestone.related_event_tickers and primary_event_tickers.

kalshi.models.structured_targets

Structured targets — external entities (players, teams, etc.) markets anchor to.

Unlike most spec schemas, every StructuredTarget field is optional per spec — Kalshi returns partial records depending on the target type. The SDK mirrors that: all fields default to None / {}.

details is flexible JSON (additionalProperties: true) whose keys depend on type (e.g., a basketball_player carries different keys than a horse_race_entry). Consumers are expected to branch on type.

StructuredTarget

Bases: BaseModel

An external entity a market can be structured against.

target_type is exposed under the SDK-local name (spec wire key is type) to avoid shadowing the Python built-in — same convention as the SDK's query-param renames on this and related resources.

GetStructuredTargetsResponse

Bases: BaseModel

Paginated response for GET /structured_targets.

GetStructuredTargetResponse

Bases: BaseModel

Response for GET /structured_targets/{id}.

kalshi.models.incentive_programs

Incentive programs — market-level liquidity/volume reward programs.

Note on period_reward: spec says "Total reward for the period in centi-cents". The SDK exposes it as a plain int (caller converts to dollars by dividing by 10 000). Fractional values come through target_size_fp (FixedPointCount string).

IncentiveProgramStatusLiteral module-attribute

IncentiveProgramStatusLiteral = Literal[
    "all", "active", "upcoming", "closed", "paid_out"
]

Incentive program status filter. Spec: GetIncentivePrograms.status query enum.

IncentiveProgramTypeLiteral module-attribute

IncentiveProgramTypeLiteral = Literal[
    "all", "liquidity", "volume"
]

Incentive program type filter. Spec: GetIncentivePrograms.type query enum.

IncentiveProgram

Bases: BaseModel

A single incentive program rewarding liquidity or volume on a market.

GetIncentiveProgramsResponse

Bases: BaseModel

Paginated response for GET /incentive_programs.

Uses next_cursor (not cursor) as the pagination key — unique to this endpoint.

kalshi.models.order_groups

Order Groups models — rolling 15-second contracts-limit groups for linked orders.

OrderGroup

Bases: BaseModel

A single order group (list response entry).

GetOrderGroupResponse

Bases: BaseModel

Single-group response — omits id (path param), adds member order IDs.

CreateOrderGroupResponse

Bases: BaseModel

Create response — wraps the new group's id.

CreateOrderGroupRequest

Bases: BaseModel

Create body. SDK sends integer form; spec's contracts_limit_fp string variant unused.

UpdateOrderGroupLimitRequest

Bases: BaseModel

Update-limit body. No subaccount — spec omits SubaccountQuery on /limit.

WebSocket

See WebSocket for the narrative version.

kalshi.ws.client.KalshiWebSocket

KalshiWebSocket(
    auth: KalshiAuth,
    config: KalshiConfig,
    heartbeat_timeout: float = 30.0,
    on_state_change: _StateChangeCb | None = None,
    on_error: (
        Callable[[ErrorMessage], Awaitable[None]] | None
    ) = None,
)

WebSocket client for real-time Kalshi market data.

Usage::

ws = KalshiWebSocket(auth=auth, config=config)
async with ws.connect() as session:
    async for msg in session.subscribe_ticker(tickers=["ECON-GDP-25Q1"]):
        print(msg.msg.yes_bid)

connect

connect() -> _WebSocketSession

Return an async context manager for the WebSocket session.

on

on(channel: str) -> _CallbackDecorator

Decorator to register a callback for a channel.

Works both before and after connect(). Callbacks registered before connect are buffered and applied when the session starts.

run_forever async

run_forever() -> None

Block until the recv loop terminates. Use with the callback API.

Requires at least one prior subscribe_* (or generic :meth:subscribe) call in the same session — the recv loop is started lazily by the subscribe machinery, and without it there is nothing to drain. Registering an @ws.on(channel) callback does NOT subscribe; the server only sends frames for channels you have explicitly subscribed to, so a callback without a matching subscribe sees nothing.

:raises KalshiSubscriptionError: run_forever() was called before any subscribe_* request landed (formerly a silent no-op return — fixed in #175).

orderbook async

orderbook(
    ticker: str, *, maxsize: int = 100
) -> AsyncIterator[Orderbook]

Subscribe to orderbook_delta and yield full Orderbook on each update.

kalshi.ws.connection.ConnectionState

Bases: Enum

WebSocket connection lifecycle states.

kalshi.ws.backpressure.OverflowStrategy

Bases: Enum

What to do when the message queue is full.

DROP_OLDEST class-attribute instance-attribute

DROP_OLDEST = 'drop_oldest'

Ring buffer: evict oldest message, keep newest. Safe for latest-wins channels (ticker).

ERROR class-attribute instance-attribute

ERROR = 'error'

Raise KalshiBackpressureError. Use for stateful channels (orderbook_delta).

kalshi.ws.backpressure.MessageQueue

MessageQueue(
    maxsize: int = 1000,
    overflow: OverflowStrategy = OverflowStrategy.DROP_OLDEST,
)

Bases: Generic[T]

Bounded async queue with configurable overflow behavior.

Implements AsyncIterator so consumers can async for msg in queue. Iteration stops when a sentinel is pushed (graceful shutdown).

put async

put(item: T) -> None

Add an item to the queue, applying overflow strategy if full.

put_sentinel async

put_sentinel() -> None

Push shutdown sentinel. Causes async iteration to stop.

get async

get() -> T

Get next item, waiting if empty.

qsize

qsize() -> int

Number of items currently in the queue (excludes sentinel). O(1).

kalshi.ws.models

WebSocket message models for all 11 Kalshi channels.

BaseMessage

Bases: BaseModel

Base for all WebSocket messages.

ErrorMessage

Bases: BaseModel

Error response from the server.

ErrorPayload

Bases: BaseModel

Error message payload.

OkMessage

Bases: BaseModel

Generic success response (list_subscriptions, update_subscription).

SubscribedMessage

Bases: BaseModel

Response to a subscribe command.

SubscriptionInfo

Bases: BaseModel

Subscription confirmation payload.

UnsubscribedMessage

Bases: BaseModel

Response to an unsubscribe command.

CommunicationsMessage

Bases: BaseModel

RFQ and quote notifications. Payload varies by sub-type, so msg is a dict.

Users who want typed parsing can use the individual payload models (RfqCreatedPayload, QuoteCreatedPayload, etc.) to validate msg contents.

QuoteAcceptedPayload

Bases: BaseModel

Quote accepted notification payload.

QuoteCreatedPayload

Bases: BaseModel

Quote created notification payload.

QuoteExecutedPayload

Bases: BaseModel

Quote executed notification payload.

RfqCreatedPayload

Bases: BaseModel

RFQ created notification payload.

Wire format per AsyncAPI spec: created_ts is an RFC3339 date-time string; target_cost_dollars is a dollar string; contracts_fp is a 2-decimal fixed-point count string.

TODO(v0.15.1): created_ts is spec-aligned but lacks live capture. The v0.14.0 user_orders capture showed demo emitting created_ts_ms as integer milliseconds instead of the spec's ISO string. If Communications follows the same pattern, created_ts: str will reject frames that previously parsed as int. Capture a live frame on demo when the channel is active and confirm or adjust. extra="allow" provides a soft landing for unexpected extras.

RfqDeletedPayload

Bases: BaseModel

RFQ deleted notification payload.

deleted_ts is an RFC3339 date-time string per AsyncAPI spec. Same created_ts_ms-precedent caveat as :class:RfqCreatedPayload.

FillMessage

Bases: BaseModel

Fill update message. NO required seq.

FillPayload

Bases: BaseModel

Payload for fill messages (private channel).

Wire format per AsyncAPI spec: yes_price_dollars is a dollar-decimal string; count_fp / post_position_fp are fixed-point count strings; fee_cost is a dollar-decimal string; ts is an integer Unix timestamp (seconds).

MarketLifecycleMessage

Bases: BaseModel

Market lifecycle v2 update message. NO required seq.

MarketLifecyclePayload

Bases: BaseModel

Payload for market_lifecycle_v2 messages (public channel).

Discriminated by event_type field. Conditional fields depend on event_type: - created/activated: open_ts, close_ts, title, subtitle, series_ticker - determined: result, determination_ts - settled: settlement_value, settled_ts - deactivated: is_deactivated

MarketPositionsMessage

Bases: BaseModel

Market positions update message. NO required seq.

MarketPositionsPayload

Bases: BaseModel

Payload for market_positions messages (private channel).

Dollar-denominated fields use :data:DollarDecimal per the CLAUDE.md convention. position is a fixed-point contract count (string).

MultivariateLifecycleMessage

Bases: BaseModel

Multivariate market lifecycle message. Same payload as MarketLifecycleMessage.

MultivariateMessage

Bases: BaseModel

Multivariate update message. NO required seq.

MultivariatePayload

Bases: BaseModel

Payload for multivariate messages (public channel).

SelectedMarket

Bases: BaseModel

A selected market within a multivariate collection.

OrderGroupMessage

Bases: BaseModel

Order group update message. HAS required seq (one of only 2 channels).

OrderGroupPayload

Bases: BaseModel

Payload for order_group_updates messages (private channel).

OrderbookDeltaMessage

Bases: BaseModel

Incremental orderbook update.

OrderbookDeltaPayload

Bases: BaseModel

Payload for orderbook_delta messages.

Wire format per AsyncAPI spec and confirmed on demo: price_dollars is a dollar-decimal string (e.g. "0.0200"), delta_fp is a fixed-point count string (e.g. "10.00", may be negative), ts is an RFC3339 timestamp string (e.g. "2026-04-19T18:43:37.662364Z").

OrderbookSnapshotMessage

Bases: BaseModel

Full orderbook snapshot, sent on initial subscribe.

OrderbookSnapshotPayload

Bases: BaseModel

Payload for orderbook_snapshot messages.

Wire format per AsyncAPI spec: yes_dollars_fp and no_dollars_fp are arrays of [price_in_dollars, contract_count_fp] string pairs. Each row is two JSON strings, e.g. ["0.5500", "100.00"]. Consumers should parse both elements as :class:decimal.Decimal.

TickerMessage

Bases: BaseModel

Ticker update message. NO required seq.

TickerPayload

Bases: BaseModel

Payload for ticker messages (public channel).

Wire format per AsyncAPI spec: yes_bid_dollars / yes_ask_dollars are dollar-decimal strings; ts is an integer Unix timestamp (seconds); _fp fields are fixed-point count strings.

TradeMessage

Bases: BaseModel

Trade update message. NO required seq.

TradePayload

Bases: BaseModel

Payload for trade messages (public channel).

Wire format per AsyncAPI spec: yes_price_dollars / no_price_dollars are dollar-decimal strings; count_fp is a fixed-point count string; ts is an integer Unix timestamp (seconds).

UserOrdersMessage

Bases: BaseModel

User orders update message. NO required seq.

UserOrdersPayload

Bases: BaseModel

Payload for user_orders messages (private channel).

Wire format captured on demo 2026-04-19: yes_price_dollars is a dollar string with up to 4 decimals ("0.0100"); *_fp counts are 2-decimal fixed-point strings; taker_fill_cost_dollars / maker_fill_cost_dollars / taker_fees_dollars / maker_fees_dollars are dollar strings with up to 6 decimals. All stored as :class:decimal.Decimal via :data:DollarDecimal.

Testing helpers

See Testing for the narrative version.

kalshi.testing

Record/replay mock layer for testing against the Kalshi SDK.

Drop a :class:RecordingTransport (or :class:AsyncRecordingTransport) into the client once to capture real API calls to disk, then swap in :class:ReplayTransport (or :class:AsyncReplayTransport) to run tests offline.

Requests are matched by HTTP method + URL path + sorted query parameters. The KALSHI-ACCESS-SIGNATURE and KALSHI-ACCESS-TIMESTAMP headers are ignored, so signature drift between record and replay does not cause misses.

.. warning::

Recorded fixtures contain the full response body returned by Kalshi. If you
record against an account with real funds you will write balances, positions,
order history, and any PII the API returns to disk. **Always ``.gitignore``
the fixture directory unless you have manually scrubbed the JSON, and prefer
recording against the demo environment whenever possible.**

Usage::

from pathlib import Path
from kalshi import KalshiClient
from kalshi.testing import RecordingTransport, ReplayTransport

# Record once against the real API:
with KalshiClient.from_env(transport=RecordingTransport(Path("fixtures"))) as c:
    c.exchange.status()

# Replay in tests, no network:
with KalshiClient(transport=ReplayTransport(Path("fixtures"))) as c:
    c.exchange.status()  # served from fixtures/GET_trade-api_v2_exchange_status.json

AsyncRecordingTransport

AsyncRecordingTransport(
    dir_path: str | Path,
    *,
    real_transport: AsyncBaseTransport | None = None
)

Bases: AsyncBaseTransport

Async equivalent of :class:RecordingTransport.

RecordingTransport

RecordingTransport(
    dir_path: str | Path,
    *,
    real_transport: BaseTransport | None = None
)

Bases: BaseTransport

Sync httpx transport that proxies real requests and records each pair to disk.

Wraps any underlying httpx.BaseTransport (defaults to a fresh httpx.HTTPTransport) so real network calls go through, while every request/response pair is buffered in memory and flushed to disk on close() — see #105 for the prior O(N²) per-request rewrite this fixes.

AsyncReplayTransport

AsyncReplayTransport(dir_path: str | Path)

Bases: AsyncBaseTransport

Async equivalent of :class:ReplayTransport.

FixtureNotFoundError

Bases: LookupError

Raised when no recorded fixture matches an incoming request.

ReplayTransport

ReplayTransport(dir_path: str | Path)

Bases: BaseTransport

Sync httpx transport that serves responses from a directory of fixtures.

Reads JSON fixture files written by :class:RecordingTransport. Requests are matched by HTTP method, URL path, and sorted query parameters (body and auth headers are ignored).

Raises :class:FixtureNotFoundError when no matching fixture exists — never returns a synthetic 404 silently.