Skip to content

Commit 29e5305

Browse files
TexasCodingclaude
andauthored
docs: bring all docs up to date with v2.1.0 surface (#156)
Closes documentation gaps surfaced by a four-agent audit after the v2.1.0 spec sync merged via #155. Findings reorganized into per-file edits below. README.md - Feature list: V2 orders callout, funding/cost introspection, OpenAPI v3.18.0 + 85 endpoints, 13 WebSocket channels (was 11). - New "V2 event-market orders" section under Placing orders with a working `create_v2()` example. docs/index.md - Feature list parity with README. docs/migration.md - New `## v2.0 → v2.1` section covering: Balance.balance_dollars construction-time soft break, V2 orders family + client_order_id idempotency semantics, spec-driven subaccount/exchange_index asymmetry on amend_v2/decrease_v2, new optional kwargs, additive endpoints, new public types. docs/resources/orders.md - Quick reference table split into V1 (legacy) + V2 sections. - New "V2 event-market orders" section covering V1→V2 differences, create/cancel/amend/decrease/batch_create/batch_cancel examples, and the spec-driven subaccount-vs-exchange_index asymmetry table. - Legacy deprecation callout (no earlier than May 6, 2026). docs/resources/portfolio.md - Balance section now covers balance_dollars (required), balance_breakdown (optional), the cents-vs-DollarDecimal type collision, and the construction-site migration note. - New "Deposits and withdrawals" section. docs/resources/account.md - New `endpoint_costs()` entry + section. docs/resources/communications.md - list_quotes filter warning enumerates all four valid satisfiers. - New "Filtering shortcuts (v2.1.0)" with user_filter / rfq_user_filter examples and the UserFilterLiteral note. - New "Post-only quotes" section for the CreateQuoteRequest.post_only field. docs/resources/incentive-programs.md - list() example adds incentive_description. docs/resources/order-groups.md - Quick-reference signatures gain exchange_index on create/delete. - New "Exchange index (v2.1.0)" section. docs/types.md - Integer-cents table now lists Deposit/Withdrawal cents fields. - New warning callout for Balance.balance vs IndexedBalance.balance type collision (same name, cents vs dollars). - Literal aliases table adds BookSideLiteral, UserFilterLiteral, PaymentStatusLiteral, PaymentTypeLiteral. docs/request-models.md - Inventory table adds 5 V2 request models. - New "V2 surface: model-only" subsection explaining why V2 dropped the kwarg overload and what client_order_id idempotency means. - Cross-field invariants mentions DecreaseOrderV2Request. docs/concepts.md - Order section now distinguishes V1 vs V2 families. ROADMAP.md - v2.1.0 entry in Shipped section. - Next-milestone candidate: response-side spec drift detection (the Balance.balance_dollars miss is the motivating case). CLAUDE.md - Spec version bumped 3.13.0 → 3.18.0 in two places; endpoint count 85; channel count 13. Test count refreshed to ~1920. Validation: - `uv run mkdocs build` clean (no anchor warnings). - `uv run mypy kalshi/` clean. - `uv run ruff check .` clean. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 75727b0 commit 29e5305

14 files changed

Lines changed: 521 additions & 25 deletions

CLAUDE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ tests/
8888
with `_dollars` suffix field names. SDK models use short Python names (`yes_bid`)
8989
with `validation_alias=AliasChoices("yes_bid_dollars", "yes_bid")` to accept both.
9090
`CreateOrderRequest` serializes with `_dollars` suffix via `serialization_alias`.
91-
Verified against OpenAPI spec v3.13.0 on 2026-04-12.
91+
Verified against OpenAPI spec v3.18.0 on 2026-05-18.
9292
- All prices use `Decimal` via the `DollarDecimal` custom Pydantic type.
9393
- Auth signing payload: `str(timestamp_ms) + METHOD + path_only` (path from urlparse,
9494
no query params, no trailing slash).
@@ -116,14 +116,14 @@ tests/
116116

117117
## Testing
118118

119-
- pytest + pytest-asyncio + respx (httpx mock); 1455 tests collected (1407 passing, 48 skipped without live credentials).
119+
- pytest + pytest-asyncio + respx (httpx mock); ~1920 tests across unit + contract drift suites.
120120
- Use `respx.mock` for HTTP mocking. Generate test RSA keys via conftest.py fixtures.
121121
- New function → write a test. Bug fix → write a regression test. New error path → write a test that triggers it.
122122

123123
## API Reference
124124

125-
- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.13.0, 90+ endpoints)
126-
- AsyncAPI spec: https://docs.kalshi.com/asyncapi.yaml (11 WebSocket channels)
125+
- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.18.0, 85 endpoints)
126+
- AsyncAPI spec: https://docs.kalshi.com/asyncapi.yaml (13 WebSocket channels)
127127
- Base URL: https://api.elections.kalshi.com/trade-api/v2
128128
- Demo URL: https://demo-api.kalshi.co/trade-api/v2
129129
- Auth: RSA-PSS / SHA256 / MGF1(SHA256) / salt_length=DIGEST_LENGTH / base64

README.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) predi
1111
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
1212
[![Type checked: mypy strict](https://img.shields.io/badge/mypy-strict-blue.svg)](https://mypy.readthedocs.io/)
1313

14-
- **Full coverage** of the Kalshi REST API (89 endpoints across 19 resources) and WebSocket API (11 channels).
14+
- **Full coverage** of the Kalshi REST API (85 endpoints across 19 resources, OpenAPI v3.18.0) and WebSocket API (13 channels).
15+
- **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.
16+
- **Funding & cost introspection**: `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`.
1517
- **Sync and async** clients sharing one transport — no thread-pool wrapping.
1618
- **Typed end-to-end**: Pydantic v2 models, `mypy --strict` clean, ships `py.typed`. `Literal` types on fixed-enum kwargs.
1719
- **Spec-aligned with drift guards**: hard-fail contract tests catch query, body, and WebSocket payload drift on every commit.
@@ -141,6 +143,34 @@ client.orders.create(request=CreateOrderRequest(
141143
))
142144
```
143145

146+
### V2 event-market orders
147+
148+
Spec v3.18.0 introduced the V2 family on `/portfolio/events/orders/*`
149+
event-scoped semantics with single-book `bid`/`ask` sides and fixed-point
150+
dollar prices. Legacy `/portfolio/orders` keeps working and will be
151+
deprecated no earlier than May 6, 2026.
152+
153+
```python
154+
import uuid
155+
from decimal import Decimal
156+
from kalshi import KalshiClient, CreateOrderV2Request
157+
158+
with KalshiClient.from_env() as client:
159+
resp = client.orders.create_v2(request=CreateOrderV2Request(
160+
ticker="EVENT-MKT",
161+
client_order_id=str(uuid.uuid4()), # required + server idempotency key
162+
side="bid", # BookSideLiteral: "bid" | "ask"
163+
count=Decimal("10"),
164+
price=Decimal("0.50"),
165+
time_in_force="good_till_canceled",
166+
self_trade_prevention_type="taker_at_cross",
167+
))
168+
print(resp.order_id, resp.remaining_count, resp.fill_count)
169+
```
170+
171+
The V2 surface is model-only (no kwarg overload); pass a fully-constructed
172+
request model. See [V2 orders docs](https://texascoding.github.io/kalshi-python-sdk/resources/orders/#v2-event-market-orders) for amend/decrease/batch variants.
173+
144174
## WebSocket streaming
145175

146176
```python
@@ -161,9 +191,10 @@ async def main() -> None:
161191
asyncio.run(main())
162192
```
163193

164-
Available channels (11): `ticker`, `trade`, `orderbook_delta`, `fill`,
165-
`market_positions`, `user_orders`, `order_group`, `market_lifecycle`,
166-
`multivariate`, `multivariate_lifecycle`, `communications`.
194+
Available channels (13): `ticker`, `trade`, `orderbook_delta`, `fill`,
195+
`market_positions`, `user_orders`, `order_group_updates`,
196+
`market_lifecycle_v2`, `multivariate`, `multivariate_market_lifecycle`,
197+
`communications`, `control_frames`, `root`.
167198

168199
## Error handling
169200

ROADMAP.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44

55
See `CHANGELOG.md` for full release history.
66

7+
- **v2.1.0 (2026-05-18)** — OpenAPI sync v3.13.0 → v3.18.0 (`#155`). V2
8+
event-market orders family (`create_v2` / `amend_v2` / `decrease_v2` /
9+
`cancel_v2` + batched variants on `/portfolio/events/orders/*`),
10+
deposit/withdrawal history, account endpoint-cost introspection,
11+
`Balance.balance_dollars` (soft-breaking at construction sites only),
12+
optional `exchange_index` / `user_filter` / `rfq_user_filter` /
13+
`incentive_description` / `post_only` kwargs on existing endpoints.
14+
Also fixes a recurring false-alarm in the weekly spec-drift workflow
15+
by committing `specs/asyncapi.yaml` as a pinned snapshot.
716
- **v2.0.0 (2026-05-17)** — audit-driven hardening. 30 audit findings closed
817
across five parallel waves (`#77``#106`) plus follow-ups: WebSocket
918
recv-loop overhaul (5 reconnect races + narrowed exceptions),
@@ -36,7 +45,13 @@ See `CHANGELOG.md` for full release history.
3645

3746
## Next milestone
3847

39-
Not scoped. Candidates from the v2.0 audit backlog:
48+
Not scoped. Carry-overs from v2.1 and v2.0 audit backlog:
49+
50+
- **Response-side spec drift detection.** v2.1 contract tests cover request
51+
bodies (`TestRequestBodyDrift`) but not response models, which is how
52+
`Balance.balance_dollars` slipped through 5 rounds of review. Add a
53+
`RESPONSE_MODEL_MAP` + walker that asserts spec-required fields exist on
54+
the SDK model.
4055

4156
- Apply `extra="allow"` policy to WS envelope models (`kalshi/ws/models/`)
4257
to mirror the response-model uniformity from `#114`.

docs/concepts.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ anything with `_dollars` or `yes_price` / `no_price` is `Decimal` dollars.
6666
- **Position** — your aggregate exposure on a market (signed by side).
6767
- **Settlement** — what the exchange paid out when the market resolved.
6868

69+
Orders come in two families:
70+
71+
- **V1**`/portfolio/orders/*`. Yes/no sides, paired `yes_price` /
72+
`no_price`. Stable surface; deprecation no earlier than May 6, 2026.
73+
- **V2**`/portfolio/events/orders/*`. Event-scoped, single-book
74+
`bid` / `ask` sides, single `price` field, required `client_order_id`
75+
acting as an idempotency key. Use this for new event-market integrations.
76+
6977
See [Orders](resources/orders.md), [Portfolio](resources/portfolio.md).
7078

7179
## RFQ and Quote

docs/index.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) prediction
44
markets API.
55

6-
- **Full REST coverage** — 89 endpoints across 19 resources, every kwarg drift-tested
7-
against the OpenAPI spec.
8-
- **Full WebSocket coverage** — 11 channels with sequence-gap detection, automatic
6+
- **Full REST coverage** — 85 endpoints across 19 resources (OpenAPI v3.18.0),
7+
every kwarg drift-tested against the spec.
8+
- **V2 event-market orders** — new `create_v2` / `amend_v2` / `decrease_v2` /
9+
`cancel_v2` family on `/portfolio/events/orders/*`. Legacy `/portfolio/orders`
10+
keeps working; deprecation no earlier than May 6, 2026.
11+
- **Funding + cost introspection**`portfolio.deposits()`,
12+
`portfolio.withdrawals()`, `account.endpoint_costs()`.
13+
- **Full WebSocket coverage** — 13 channels with sequence-gap detection, automatic
914
reconnection, backpressure strategies, and an in-memory orderbook builder.
1015
- **Sync and async parity**`KalshiClient` and `AsyncKalshiClient` share one
1116
transport implementation; method names, kwargs, return types, and error behavior

docs/migration.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,112 @@
11
# Migration
22

3+
## v2.0 → v2.1
4+
5+
v2.1 syncs the SDK to OpenAPI spec v3.18.0. It's **additive at the resource
6+
surface** — eight new endpoints, several new optional kwargs on existing
7+
methods, and one soft-breaking model-construction change called out below.
8+
Code that only consumes the SDK's responses needs no edits; code that
9+
constructs models directly in tests/mocks needs one small update.
10+
11+
### `Balance.balance_dollars` — required, soft-breaking at construction
12+
13+
Spec v3.18.0 adds `balance_dollars: FixedPointDollars` to
14+
`GetBalanceResponse` as a required field. The server now guarantees it, so
15+
callers parsing API responses (`client.portfolio.balance()`) are unaffected.
16+
**But** any code that builds `Balance(...)` directly — typically test mocks
17+
— will hit `ValidationError` until it adds the field.
18+
19+
```python
20+
# v2.0 — broken in v2.1
21+
Balance(balance=50000, portfolio_value=75000, updated_ts=ts)
22+
23+
# v2.1
24+
Balance(
25+
balance=50000,
26+
balance_dollars=Decimal("500.00"), # new required field
27+
portfolio_value=75000,
28+
updated_ts=ts,
29+
)
30+
```
31+
32+
The accompanying optional `balance_breakdown: list[IndexedBalance] | None`
33+
splits the total across exchange shards when present. `IndexedBalance.balance`
34+
is `DollarDecimal` (matching `balance_dollars` units), not cents — same
35+
field name as `Balance.balance` but a different type. Be deliberate when
36+
reading from `balance.balance_breakdown[i].balance`.
37+
38+
### V2 event-market orders
39+
40+
Six new methods on `OrdersResource` / `AsyncOrdersResource` hit the new
41+
`/portfolio/events/orders/*` paths:
42+
43+
- `create_v2(*, request: CreateOrderV2Request)`
44+
- `cancel_v2(order_id, *, subaccount, exchange_index)`
45+
- `amend_v2(order_id, *, request: AmendOrderV2Request, subaccount)`
46+
- `decrease_v2(order_id, *, request: DecreaseOrderV2Request, subaccount)`
47+
- `batch_create_v2(*, request: BatchCreateOrdersV2Request)`
48+
- `batch_cancel_v2(*, request: BatchCancelOrdersV2Request)`
49+
50+
Legacy `/portfolio/orders` keeps working and will be deprecated no earlier
51+
than May 6, 2026. No migration is required to stay on v1 paths. New
52+
event-market trading should target the V2 family for the cleaner shape
53+
(single `bid`/`ask` side, single `price` field, explicit idempotency).
54+
55+
Two important differences from V1:
56+
57+
1. **`client_order_id` is required** on `CreateOrderV2Request` and acts as
58+
the server-side idempotency key. Reusing a value returns the original
59+
order rather than placing a new one. Use a fresh UUID4 per call.
60+
2. **`side` uses `BookSideLiteral` (`"bid"` / `"ask"`)**, not V1's
61+
`SideLiteral` (`"yes"` / `"no"`).
62+
63+
### Spec-driven asymmetry on V2 amend/decrease
64+
65+
`amend_v2` and `decrease_v2` accept `subaccount` as a **resource-method
66+
kwarg** (query param on the wire) but read `exchange_index` from the
67+
**request body**. `cancel_v2` differs again — both are query params there,
68+
because that endpoint has no body. This mirrors the spec exactly:
69+
70+
```python
71+
client.orders.amend_v2(
72+
"ord-1",
73+
subaccount=3, # query param
74+
request=AmendOrderV2Request(
75+
ticker="EVENT-MKT", side="bid",
76+
price=Decimal("0.55"),
77+
count=Decimal("10"),
78+
exchange_index=0, # body field
79+
),
80+
)
81+
```
82+
83+
### New optional kwargs on existing endpoints
84+
85+
All additive — existing call sites keep working:
86+
87+
- `orders.cancel(*, exchange_index)`, `order_groups.delete(*, exchange_index)`
88+
- `communications.list_rfqs(*, user_filter)`, `communications.list_all_rfqs(*, user_filter)`
89+
- `communications.list_quotes(*, user_filter, rfq_user_filter)`, `communications.list_all_quotes(*, user_filter, rfq_user_filter)`. `user_filter="self"` and `rfq_user_filter="self"` are now standalone satisfiers for the server-side filter requirement (previously only `quote_creator_user_id` / `rfq_creator_user_id` worked).
90+
- `incentive_programs.list(*, incentive_description)` and the `list_all` variant.
91+
- `CreateQuoteRequest.post_only` — pass `post_only=True` to `create_quote()`.
92+
- `exchange_index` on order/amend/decrease/batch-cancel request models.
93+
94+
### New endpoints (additive)
95+
96+
- `portfolio.deposits()` / `portfolio.deposits_all()` — deposit history.
97+
- `portfolio.withdrawals()` / `portfolio.withdrawals_all()` — withdrawal history.
98+
- `account.endpoint_costs()` — token costs for endpoints whose cost differs
99+
from the default.
100+
101+
### New public types
102+
103+
Added to `kalshi.*` and `kalshi.models.*`:
104+
105+
- Literals: `BookSideLiteral`, `UserFilterLiteral`, `PaymentStatusLiteral`, `PaymentTypeLiteral`.
106+
- Models: `Deposit`, `Withdrawal`, `IndexedBalance`, `AccountEndpointCosts`, `EndpointTokenCost`, and the V2 request/response family.
107+
108+
---
109+
3110
## v1.x → v2.0
4111

5112
v2.0 is mostly additive (new `max_pages` kwarg, `KalshiConfig.http2`/`limits`,

docs/request-models.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ OpenAPI spec.
6464
| `client.orders.batch_cancel` | `BatchCancelOrdersRequest` (wraps `list[BatchCancelOrdersRequestOrder]`) |
6565
| `client.orders.amend` | `AmendOrderRequest` |
6666
| `client.orders.decrease` | `DecreaseOrderRequest` |
67+
| `client.orders.create_v2` | `CreateOrderV2Request` (V2 event-market — model-only, no kwarg form) |
68+
| `client.orders.amend_v2` | `AmendOrderV2Request` (V2 — model-only) |
69+
| `client.orders.decrease_v2` | `DecreaseOrderV2Request` (V2 — model-only, XOR `reduce_by`/`reduce_to`) |
70+
| `client.orders.batch_create_v2` | `BatchCreateOrdersV2Request` (wraps `list[CreateOrderV2Request]`) |
71+
| `client.orders.batch_cancel_v2` | `BatchCancelOrdersV2Request` (wraps `list[BatchCancelOrdersV2RequestOrder]`) |
6772
| `client.api_keys.create` | `CreateApiKeyRequest` |
6873
| `client.api_keys.generate` | `GenerateApiKeyRequest` |
6974
| `client.communications.create_rfq` | `CreateRFQRequest` |
@@ -94,12 +99,42 @@ an awkward wire name:
9499

95100
You never type these long names — they're an internal implementation detail.
96101

102+
## V2 surface: model-only
103+
104+
The V2 event-market order endpoints (`create_v2` / `amend_v2` / `decrease_v2`
105+
/ `batch_*_v2`) don't accept individual kwargs — pass a fully-constructed
106+
request model. This is intentional: V2 took the opportunity to drop the
107+
V1 kwarg-overload surface entirely, so the model is the single source of
108+
truth for the payload shape.
109+
110+
```python
111+
import uuid
112+
from decimal import Decimal
113+
from kalshi import CreateOrderV2Request
114+
115+
req = CreateOrderV2Request(
116+
ticker="EVENT-MKT",
117+
client_order_id=str(uuid.uuid4()), # required — idempotency key
118+
side="bid",
119+
count=Decimal("10"),
120+
price=Decimal("0.50"),
121+
time_in_force="good_till_canceled",
122+
self_trade_prevention_type="taker_at_cross",
123+
)
124+
client.orders.create_v2(request=req)
125+
```
126+
127+
`CreateOrderV2Request.client_order_id` is required (V1's was optional) and
128+
the server treats it as an idempotency key — reusing one returns the
129+
original order rather than placing a new one. Generate a fresh UUID4 per
130+
call.
131+
97132
## Cross-field invariants
98133

99134
Some request models enforce relationships **before** the HTTP call:
100135

101-
- `DecreaseOrderRequest` — exactly one of `reduce_by` / `reduce_to` is
102-
required (XOR enforced via a model validator).
136+
- `DecreaseOrderRequest` / `DecreaseOrderV2Request` — exactly one of
137+
`reduce_by` / `reduce_to` is required (XOR enforced via a model validator).
103138
- `AmendOrderRequest` — at least one of `yes_price` / `no_price` / `count`
104139
must be present (enforced in the resource method, before the body is built).
105140

docs/resources/account.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Auth required.
99
| Method | Endpoint |
1010
|---|---|
1111
| `limits()` | `GET /account/limits` |
12+
| `endpoint_costs()` | `GET /account/endpoint_costs` |
1213

1314
## Read tier limits
1415

@@ -29,6 +30,23 @@ calls.
2930
the production server returns the nested `RateLimit` objects shown above.
3031
The SDK normalizes to the live shape.
3132

33+
## Endpoint costs
34+
35+
New in v2.1.0. Lists API v2 endpoints whose configured token cost differs
36+
from the default — useful for budgeting against your write tier.
37+
38+
```python
39+
costs = client.account.endpoint_costs()
40+
print(costs.default_cost) # int, the baseline (typically 10)
41+
for entry in costs.endpoint_costs:
42+
# EndpointTokenCost: method (str), path (str), cost (int)
43+
print(entry.method, entry.path, entry.cost)
44+
```
45+
46+
Endpoints not present in `endpoint_costs` use `default_cost`. Batch
47+
endpoints typically appear here with a per-item multiplier (e.g.
48+
`POST /portfolio/orders/batched` costs ~10 tokens per order in the batch).
49+
3250
## Reference
3351

3452
::: kalshi.resources.account.AccountResource

0 commit comments

Comments
 (0)