Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) predi
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Type checked: mypy strict](https://img.shields.io/badge/mypy-strict-blue.svg)](https://mypy.readthedocs.io/)

- **Full coverage** of the Kalshi REST API (99 operations across 19 resources, OpenAPI v3.20.0) and WebSocket API (11 typed `subscribe_*` channels + 2 escape-hatch).
- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (34 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (10 operations). See [Perps (margin) trading](#perps-margin-trading).
- **Full coverage** of the Kalshi REST API (99 operations across 19 resources, OpenAPI v3.20.0) and WebSocket API (12 typed `subscribe_*` channels + 2 escape-hatch).
- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (34 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (9 operations). See [Perps (margin) trading](#perps-margin-trading).
- **FIX protocol**: an async-first FIX engine (FIXT.1.1 / FIX50SP2) for both products — order-entry, drop-copy, market-data, post-trade (prediction), and RFQ (prediction) sessions (plus order-group management over the order-entry session) with typed message models, sequence recovery, and order-book / settlement reassembly. `from kalshi import FixClient` / `MarginFixClient`. See [FIX protocol](#fix-protocol-low-latency-trading).
- **V2 event-market orders**: `create_v2` / `amend_v2` / `decrease_v2` / `cancel_v2` plus batched variants on `/portfolio/events/orders/*`. Legacy `/portfolio/orders` keeps working — deprecated no earlier than May 6, 2026.
- **Funding & cost introspection**: `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`.
Expand Down Expand Up @@ -193,15 +193,16 @@ async def main() -> None:
asyncio.run(main())
```

Available channels (11 typed + 2 escape-hatch). Eleven have dedicated
Available channels (12 typed + 2 escape-hatch). Twelve have dedicated
`subscribe_*` methods — `subscribe_ticker`, `subscribe_trade`,
`subscribe_orderbook_delta`, `subscribe_fill`, `subscribe_market_positions`,
`subscribe_user_orders`, `subscribe_order_group`,
`subscribe_market_lifecycle`, `subscribe_multivariate`,
`subscribe_multivariate_lifecycle`, `subscribe_communications`. The
`subscribe_multivariate_lifecycle`, `subscribe_communications`,
`subscribe_cfbenchmarks_value`. The
AsyncAPI-declared `control_frames` and `root` channels are reachable
through the generic `subscribe(channel, ...)` escape hatch. See
[docs/websockets.md](docs/websockets.md#the-11-channels) for the full
[docs/websockets.md](docs/websockets.md#the-12-channels) for the full
channel table.

## Perps (margin) trading
Expand Down Expand Up @@ -248,7 +249,7 @@ Prices are `DollarDecimal` (FixedPointDollars, up to 6 decimals); counts are
**WebSocket timestamps are Unix epoch milliseconds** (`*_ms` fields). The
Self-Clearing-Member "Klear" settlement API (margin reports, settlement balances,
obligations, withdrawals) is a third surface exposed via `KlearClient`, which uses
**cookie-session + MFA** login (`client.login(email=..., password=..., code=...)`)
**Bearer token** auth (`KlearClient(admin_user_id=..., access_token=...)`)
rather than RSA-PSS. Full guide: [docs/perps.md](docs/perps.md).

## FIX protocol (low-latency trading)
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ See [Incentive programs](resources/incentive-programs.md).
## Subaccount

A logical wallet partition under your main account. Used to isolate strategies
or risk pools. Subaccount `0` is your primary account; `1`–`32` are numbered
or risk pools. Subaccount `0` is your primary account; `1`–`63` are numbered
extras. Most resource methods accept a `subaccount=` kwarg to route the call.

See [Subaccounts](resources/subaccounts.md).
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ markets API.
keeps working; deprecation no earlier than May 6, 2026.
- **Funding + cost introspection** — `portfolio.deposits()`,
`portfolio.withdrawals()`, `account.endpoint_costs()`.
- **Full WebSocket coverage** — 11 channels with sequence-gap detection, automatic
- **Full WebSocket coverage** — 12 channels with sequence-gap detection, automatic
reconnection (with resubscribe-window frame stashing for high-volume channels),
backpressure strategies, and an in-memory orderbook builder. Async-only —
access via `AsyncKalshiClient.ws`.
- **Perps (margin) API** — standalone `PerpsClient` / `AsyncPerpsClient` +
`PerpsWebSocket` for the perpetual-futures exchange (34 REST operations, 6 WS
channels), and a `KlearClient` for the Self-Clearing-Member settlement API
(10 operations, cookie-session + MFA auth). See [Perps](perps.md).
(9 operations, Bearer token auth). See [Perps](perps.md).
- **FIX protocol** — a hand-rolled, async-first FIX engine (FIXT.1.1 / FIX50SP2)
for both products: order-entry, drop-copy, market-data, post-trade (prediction),
and RFQ (prediction) sessions — plus order-group management over the order-entry
Expand Down
21 changes: 21 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Migration

## v3 → v4.0.0

v4.0.0 has **one breaking change** — the Self-Clearing-Member "Klear" API
migrated from cookie-session login to a pre-generated **Bearer token**, because
upstream removed `POST /log_in`. Everything else in v4.0.0 is additive
(`cfbenchmarks_value` WS channel, `AccountResource.upgrade()`,
`AccountApiLimits.grants`, perps `api_limits()`, perps market notional/leverage
fields). The prediction and perps trade-api surfaces are unchanged.

For full BEFORE/AFTER snippets see [v3-to-v4.md](migrations/v3-to-v4.md). Quick
summary of the Klear break:

| Was (v3.x) | Now (v4.0.0) |
|---|---|
| `KlearClient(demo=True)` then `client.login(email=..., password=..., code=...)` | `KlearClient(admin_user_id=..., access_token=..., demo=True)` |
| `KlearClient.from_env()` (URL routing only — no credential env vars) | `KlearClient.from_env()` reads `KALSHI_KLEAR_ADMIN_USER_ID` / `KALSHI_KLEAR_ACCESS_TOKEN` |
| `client.is_authenticated`, `client.auth`, `LogInRequest`, `LogInResponse` | removed |

Generate the token / find your admin user id at <https://klearing.kalshi.com>
(the "Security" page).

## v2.7 → v3.0

v3.0.0 is the first major release in the v3 line. It renames three groups of
Expand Down
104 changes: 104 additions & 0 deletions docs/migrations/v3-to-v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Migrating from v3.x to v4.0.0

v4.0.0 has **one breaking change**: the Self-Clearing-Member "Klear" API
(`KlearClient` / `AsyncKlearClient`) switched from cookie-session login to a
pre-generated **Bearer token**, because upstream removed `POST /log_in`.

If you do not use the Klear API, **v4.0.0 requires no code changes** — everything
else is additive. The prediction and perps trade-api surfaces (RSA-PSS auth, all
REST resources, the WebSocket and FIX clients) are unchanged.

## TL;DR — Klear cheat sheet

| Find (v3.x) | Replace (v4.0.0) |
|---|---|
| `KlearClient(demo=True)` + `client.login(email=..., password=...)` (or `code=...` for MFA) | `KlearClient(admin_user_id=..., access_token=..., demo=True)` |
| `await client.login(...)` / `client.is_authenticated` | (removed — credentials are supplied at construction) |
| `from kalshi import LogInRequest, LogInResponse` | (removed) |
| `client.auth.log_in(...)` | (removed) |

Generate the Bearer token and find your admin user id at
<https://klearing.kalshi.com> (the "Security" page). Treat the token as a secret.

## 1. Klear (SCM) Bearer authentication (breaking)

Upstream removed `POST /log_in` and the cookie-session flow. The Klear API now
authenticates with a static header on every request:

```
Authorization: Bearer <admin_user_id>:<access_token>
```

`KlearClient` / `AsyncKlearClient` now **require** `admin_user_id` and
`access_token` (keyword-only) at construction. `KlearAuth` is now a Bearer
credential holder rather than a session-state tracker.

### Before (v3.x)

```python
from kalshi import KlearClient

with KlearClient(demo=True) as klear:
resp = klear.login(email="me@example.com", password="...")
if resp.required_mfa_method: # MFA challenge
klear.login(email="me@example.com", password="...", code="123456")
assert klear.is_authenticated
reports = klear.margin.margin_reports(start_date="2026-01-01", end_date="2026-02-01")
```

### After (v4.0.0)

```python
from kalshi import KlearClient

# Credentials at construction:
with KlearClient(
admin_user_id="your-admin-user-id",
access_token="your-bearer-token",
demo=True,
) as klear:
reports = klear.margin.margin_reports(start_date="2026-01-01", end_date="2026-02-01")

# Or from the environment (KALSHI_KLEAR_ADMIN_USER_ID / KALSHI_KLEAR_ACCESS_TOKEN):
with KlearClient.from_env(demo=True) as klear:
bal = klear.margin.settlement_balance()
```

### Removed

- `KlearClient.login()` / `AsyncKlearClient.login()`
- `KlearClient.is_authenticated` property
- the `client.auth` resource (`AuthResource` / `AsyncAuthResource`)
- the `LogInRequest` / `LogInResponse` models (and their `kalshi` / `kalshi.perps`
re-exports)

### Changed

- `KlearAuth()` (no-arg session holder) → `KlearAuth(admin_user_id, access_token)`,
a Bearer-credential holder. The `access_token` is redacted from `repr()`.
- New environment variables: `KALSHI_KLEAR_ADMIN_USER_ID`, `KALSHI_KLEAR_ACCESS_TOKEN`
(read by `KlearClient.from_env()`). The old credentials were never read from the
environment.

## 2. Additive changes (no migration needed)

These are new surfaces — existing code keeps working:

- **`cfbenchmarks_value` WebSocket channel** — stream CF Benchmarks reference index
values (e.g. `BRTI`, `ETHUSD_RTI`) via
`KalshiWebSocket.subscribe_cfbenchmarks_value(index_ids=[...])`. New models
(`CFBenchmarksValueMessage`, `CFBenchmarksValuePayload`, `CFBenchmarksAvgData`,
`CFBenchmarksIndexListMessage`, `CFBenchmarksIndexListPayload`) are exported from
`kalshi.ws.models`. See [WebSocket](../websockets.md#cf-benchmarks-index-values).
- **`AccountResource.upgrade()`** — `POST /account/api_usage_level/upgrade` requests
a permanent Advanced API usage-level grant. See
[Account](../resources/account.md#api-usage-level-grants).
- **`AccountApiLimits.grants`** — a new field listing active usage-level grants,
plus a new exported `ApiUsageLevelGrant` model.
- **`MarginAccountResource.api_limits()`** — `GET /account/limits/perps` for the
Perps API tier limits (reuses `AccountApiLimits`).
- **Perps market notional/leverage fields** — `MarginMarket` gains
`leverage_estimates` and `*_notional_value` fields; `MarginMarketCandlestick`
and the margin ticker WS payload gain notional-value fields.

The subaccount range documented in prose is now 1–63 (no validation change).
21 changes: 17 additions & 4 deletions docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async with AsyncPerpsClient.from_env(demo=True) as perps:
| `orders` | `create()`, `get()`, `list()` / `list_all()`, `cancel()`, `decrease()`, `amend()`, `list_fcm()` / `list_all_fcm()` |
| `order_groups` | `list()`, `get()`, `create()`, `delete()`, `reset()`, `trigger()`, `update_limit()` |
| `portfolio` | `positions()`, `fills()` / `fills_all()`, `trades()` / `trades_all()` |
| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()` |
| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()`, `api_limits()` |
| `funding` | `rate_estimate()`, `historical_rates()`, `history()` |
| `transfers` | `transfer_instance()`, `create_subaccount()`, `transfer_subaccount()` |

Expand All @@ -82,6 +82,13 @@ Orders create/cancel/decrease/amend are POSTs/DELETEs and are **never retried**.
- **WebSocket** timestamps are Unix epoch **milliseconds** on `*_ms`-suffixed
fields (`ts_ms`, `created_ts_ms`, `next_funding_time_ms`, …) — a real parsing
difference from the event-contract WS.
- **Notional values** — `MarginMarket` (REST) and the margin ticker WS payload
carry optional `volume_notional_value` / `volume_24h_notional_value` /
`open_interest_notional_value` (`DollarDecimal | None`); `MarginMarketCandlestick`
carries the same fields as **required** (inherent to a settled historical
record). `MarginMarket.leverage_estimates` maps notional position sizes
(`"1000"`, `"10000"`, …) to `MultiplierDecimal` leverage, or `None` when margin
config / price data is unavailable.

## Funding mechanics

Expand Down Expand Up @@ -112,6 +119,11 @@ for pos in risk.positions:
print(pos.market_ticker, pos.position_leverage, pos.estimated_liquidation_price)
```

`perps.margin.api_limits()` (`GET /account/limits/perps`) returns the Perps API
tier limits in the same shape as the prediction API's `client.account.limits()`
(an `AccountApiLimits` with `usage_tier`, `read`/`write` token buckets, and the
`grants` list of `ApiUsageLevelGrant`).

## WebSocket streaming

```python
Expand All @@ -129,8 +141,8 @@ Six data channels — `orderbook_delta` (snapshot + delta, sequenced), `ticker`,
`trade`, `fill`, `user_orders`, `order_group_updates`. The connection, sequence-gap
detection, reconnect, and backpressure machinery are reused from the event-contract
WS stack (see [WebSocket](websockets.md)); the perps orderbook is `bid` / `ask`.
The equities-only channels (`market_positions`, `multivariate*`, `communications`,
`market_lifecycle_v2`) have no perps counterpart.
The prediction-only channels (`market_positions`, `multivariate*`, `communications`,
`market_lifecycle_v2`, `cfbenchmarks_value`) have no perps counterpart.

## Self-Clearing-Member "Klear" API

Expand All @@ -155,7 +167,8 @@ Money fields on the Klear margin schemas are integer **centicents** (`1 USD =
10,000 centicents`); only the withdrawal `amount` is a fixed-point dollar string.
`klear.margin.withdraw_settlement_balance(amount="500.00")` validates the amount as
positive at construction (the single real-money write) before any request is sent.
Credentials and the session cookie are never logged or shown in `repr()`.
The Bearer `access_token` is never logged and is redacted in `repr()` (only the
non-secret `admin_user_id` is shown).

## Perps over FIX

Expand Down
34 changes: 33 additions & 1 deletion docs/resources/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Auth required.
|---|---|
| `limits()` | `GET /account/limits` |
| `endpoint_costs()` | `GET /account/endpoint_costs` |
| `upgrade()` | `POST /account/api_usage_level/upgrade` |

## Read tier limits

Expand All @@ -23,7 +24,8 @@ print(limits.write.bucket_capacity, limits.write.refill_rate)
`AccountApiLimits.read` and `.write` are `RateLimit` objects with
`bucket_capacity` and `refill_rate` fields (token-bucket parameters).
Use them to drive client-side throttling if you fan out many concurrent
calls.
calls. `AccountApiLimits.grants` (new in v4.0.0) lists the caller's active
usage-level grants — see [API usage-level grants](#api-usage-level-grants) below.

!!! note "Differs from the OpenAPI spec shape"
The published spec describes `read_limit` and `write_limit` as integers;
Expand All @@ -47,6 +49,36 @@ Endpoints not present in `endpoint_costs` use `default_cost`. Batch
endpoints typically appear here with a per-item multiplier (e.g.
`POST /portfolio/orders/batched` costs ~10 tokens per order in the batch).

## API usage-level grants

New in v4.0.0. `AccountApiLimits.grants` is a list of `ApiUsageLevelGrant`
(exported from the top-level `kalshi` package) describing the caller's active
usage-level grants across exchange lanes. Each grant has:

- `exchange_instance` — the exchange lane: `"event_contract"` or `"margined"`.
- `level` — the API usage level the grant confers (e.g. `"premier"`,
`"paragon"`, `"prime"`).
- `source` — how it was created: `"volume"` (earned from trading volume) or
`"manual"` (assigned by Kalshi).
- `expires_ts` — Unix-seconds expiry, or `None` for a permanent grant.

```python
limits = client.account.limits()
for grant in limits.grants:
print(grant.exchange_instance, grant.level, grant.source, grant.expires_ts)
```

`upgrade()` requests a **permanent Advanced** API usage-level grant
(`POST /account/api_usage_level/upgrade`). It requires that at least one of your
last 100 Predictions orders was API-created; otherwise the server returns 403
(mapped to `KalshiAuthError`). It returns `None` on success (HTTP 201) — re-read
`limits()` to see the resulting grant:

```python
client.account.upgrade()
print(client.account.limits().grants)
```

## Reference

::: kalshi.resources.account.AccountResource
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/subaccounts.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Subaccounts

Logical wallet partitions under your main account. Subaccount `0` is your
primary; `1`–`32` are numbered extras. Auth required throughout.
primary; `1`–`63` are numbered extras. Auth required throughout.

## Quick reference

Expand Down
Loading