feat: sync spec to v3.18.0 + fix asyncapi drift false alarm#155
Conversation
…alarm
OpenAPI bumped 3.13.0 → 3.18.0 (77 → 85 paths). Adds the V2 event-market
orders family, deposits/withdrawals history, and endpoint_costs, plus
exchange_index/user_filter/rfq_user_filter/incentive_description params
and a post_only field on CreateQuoteRequest.
Also un-gitignores specs/asyncapi.yaml so the weekly spec-sync workflow
stops reporting bogus "AsyncAPI 0 → 13 channels" drift every Monday — the
file was never committed, so each cron run diffed the fresh download
against a non-existent old file and rendered a "0 → N" delta. Now both
specs are pinned snapshots, matching the openapi.yaml model.
New endpoints:
- POST /portfolio/events/orders (create_v2)
- DELETE /portfolio/events/orders/{id} (cancel_v2)
- POST /portfolio/events/orders/{id}/amend (amend_v2)
- POST /portfolio/events/orders/{id}/decrease (decrease_v2)
- POST /portfolio/events/orders/batched (batch_create_v2)
- DELETE /portfolio/events/orders/batched (batch_cancel_v2)
- GET /portfolio/deposits (deposits/deposits_all)
- GET /portfolio/withdrawals (withdrawals/withdrawals_all)
- GET /account/endpoint_costs
Closes #154
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — feat: sync spec to v3.18.0 + fix asyncapi drift false alarmOverall this is a solid, well-structured spec bump. The architecture is consistent, contract tests are thorough, and the infra fix (committing Bug —
|
Addresses claude[bot]'s review on PR #155: - AccountResource.endpoint_costs (sync + async) now calls _require_auth(), matching the limits() pattern. Without it callers got a 401 from the API instead of the SDK's AuthRequiredError. - CreateOrderV2Request.side and AmendOrderV2Request.side now use BookSideLiteral instead of str. v1 narrows at the resource-method kwarg boundary; v2 is model-only so the Literal must live on the model field. Pydantic validates "bid"/"ask" at construction time. - 37 new unit tests across test_account.py, test_portfolio.py, test_orders.py, test_async_orders.py covering all 11 new methods. Notable coverage: * cancel_v2 / batch_cancel_v2 — the data is None → KalshiError branch for 204 No Content * DecreaseOrderV2Request — both XOR error paths * deposits_all / withdrawals_all — pagination + max_pages cap * endpoint_costs — happy path, empty list, auth required Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the review — all three actionable issues addressed in c9abff3. Bug fix — Type-safety — V2 Unit tests — 37 new tests across 4 files
Full suite: 1903 passed (was 1866), mypy clean, ruff clean. On the |
Code Review — PR #155: sync spec to v3.18.0 + fix asyncapi driftOverviewThis is a solid, well-structured spec-sync PR. The V2 order model family follows the existing request/response pattern cleanly, test coverage is good, and the asyncapi gitignore fix directly solves the recurring false-alarm problem. A few things worth discussing before merging: Potential Bugs1. Missing
|
…enums Addresses claude[bot]'s second review on PR #155: - UserFilterLiteral moved from resources/communications.py to models/communications.py to match the other *Literal aliases' location, and exported from kalshi.models + kalshi top-level. - DepositStatusLiteral → PaymentStatusLiteral: the spec defines two structurally-identical inline enums for Deposit.status and Withdrawal.status. A neutral name reads less surprisingly when used on Withdrawal. - CreateOrderV2Request.time_in_force and self_trade_prevention_type now use TimeInForceLiteral and SelfTradePreventionTypeLiteral — catches typos at construction time. - amend_v2 / decrease_v2 (sync + async) gained docstrings explaining the spec-driven asymmetry: subaccount is a query param but exchange_index lives in the request body. This is faithful to the spec, not a bug — cancel_v2 carries both as query params because that endpoint declares ExchangeIndexQuery in its parameters list. - New tests: * TestAmendOrderV2.test_passes_subaccount_query and the decrease_v2 equivalent — regression guard so the params kwarg never silently drops off the _post call. * TestAsyncAmendDecreaseV2QueryParams mirror for the async surface. * TestV2RequiresAuth + TestAsyncV2RequiresAuth — auth-guard coverage for create_v2 / amend_v2 / decrease_v2 / batch_create_v2 / batch_cancel_v2. The reviewer's _dollars-suffix concern was investigated: the V2 spec uses plain field names (price, average_fill_price, average_fee_paid) without the _dollars suffix that V1 carries. V1 has the suffix because its fields have both int-cent and decimal-dollar forms; V2 ships FixedPointDollars-only and dropped the duplication. No alias work needed — the existing models match the spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the second pass. Pushed Verified against spec (no change needed)#1 #2 Fixed#3 #4 Minor — V2 enum types — New tests#6 query-param forwarding —
#7 auth guards — Validation1920 tests pass (was 1903), mypy clean, ruff clean. DeferredMinor — |
Code Review — PR #155: Sync spec to v3.18.0 + AsyncAPI drift fixOverall this is a solid, well-structured spec-sync. The scope is appropriate, contract drift tests are all green, and the new V2 surface has good test coverage. A few items worth addressing before merge: Potential Bug —
|
Addresses claude[bot]'s third review on PR #155: - Version bumped 2.0.0 → 2.1.0 in __init__ and pyproject.toml. 9 new public methods across sync + async = minor bump per semver. CHANGELOG.md updated with the v2.1.0 entry. - Added Field(ge=0, le=32) on subaccount fields in CreateOrderV2Request and BatchCancelOrdersV2RequestOrder to match the V1 SDK convention. The spec only enforces minimum=0 but the descriptions document the 1-32 range and V1 has carried the upper bound since release. (AmendOrderV2Request and DecreaseOrderV2Request have no subaccount field — that param routes via the resource method's query kwarg per spec.) - New tests: * test_sends_body — regression guard that the serialized orders array actually reaches the DELETE body for batch_cancel_v2. * test_error_entry_parses — documents the spec contract that an errored cancel still carries order_id + reduced_by=0 alongside the error block, so we catch upstream divergence early. The reviewer flagged BatchCancelOrdersV2ResponseEntry.reduced_by as potentially needing nullability for error entries. The spec lines 7510-7512 mark order_id + reduced_by as required, with reduced_by explicitly "Zero if the cancel errored." Kept the model spec-faithful and added the error-entry test to document the contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the third pass. Pushed Primary ask —
|
Code Review — PR #155: Spec sync v3.13.0 → v3.18.0 + AsyncAPI drift fixOverall this is a well-executed spec sync. The V2 order family, portfolio history endpoints, and account endpoint-cost introspection are implemented consistently with the rest of the SDK. The Bugs / Correctness1. class BatchCancelOrdersV2ResponseEntry(BaseModel):
order_id: str
reduced_by: FixedPointCount # required
...
error: dict[str, object] | None = NoneThe test comment says "Per spec, an errored cancel still carries order_id + reduced_by=0 alongside the error block" — but this is a spec-level assertion that's worth making visible. If the live API ever omits reduced_by: FixedPointCount | None = NoneIf the spec truly guarantees it even for errors, a 2. Both have the same 7 fields with the same types and the same Test Coverage Gaps3.
The same gap exists for 4. The happy-path test checks the response only. Given that Minor / Style5. The asymmetry (subaccount=query, exchange_index=body) is non-obvious enough that users are likely to read the async version first. The async docstring on lines 1690–1692 should carry the full explanation rather than deferring to the sync sibling—at minimum include the one-sentence reason: "Per OpenAPI spec v3.18.0, subaccount is a query parameter while exchange_index lives in the request body." 6.
7. V1 Positive Highlights
Summary: Items 1 and 3 are the most actionable before merge. Item 1 is a mild correctness risk; item 3 is a missing regression guard for a non-obvious spec invariant. Everything else is polish. 🤖 Reviewed with Claude Code |
…olish Addresses claude[bot]'s fourth review on PR #155: - BatchCancelOrdersV2ResponseEntry: reduced_by relaxed to Optional with a model_validator that raises a descriptive error if it ever lands as None. Spec v3.18.0 marks it required (zero on error) — the validator makes that invariant explicit so a non-conforming upstream surfaces a clear message rather than a generic Pydantic ValidationError. - New TestCreateOrderV2.test_serializes_body — guards DollarDecimal / FixedPointCount mode="json" serialization on the create_v2 wire body. Mirrors the assertion style of the amend_v2 body test. - Async amend_v2 / decrease_v2 docstrings expanded with the full spec-asymmetry rationale rather than deferring to the sync sibling. Users who read the async version first now get the context inline. - CreateOrderV2Request docstring now enumerates V1→V2 differences: BookSideLiteral side, required client_order_id (vs. optional in V1), single price field (vs. paired yes_price/no_price). Note on review item #3 (async amend_v2/decrease_v2 query-param tests): both already exist in TestAsyncAmendDecreaseV2QueryParams added in the round-2 commit (f7fecf8). The class lives at the bottom of test_async_orders.py rather than alongside the per-method test classes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the fourth pass. Pushed #1 —
|
Code Review — PR #155: sync spec to v3.18.0 + fix asyncapi drift false alarmOverall this is a solid, well-structured spec-sync PR. The new surface (V2 orders, deposits/withdrawals, endpoint costs) follows established conventions closely. Below are my findings, ordered by severity. Observations & Suggestions1.
|
Addresses claude[bot]'s fifth review on PR #155: - BatchCancelOrdersV2ResponseEntry.reduced_by reverted to required (FixedPointCount, no default). The previous round's Optional+validator pattern was a misleading type signature — the validator only existed to provide a better error message, but Pydantic's "field required" error is descriptive enough. Spec invariant moved into the model docstring instead. - New _require_quote_filter regression tests in test_communications.py: * test_user_filter_alone_is_sufficient — list_quotes(user_filter="self") must not raise even when both creator-id filters are None. * test_rfq_user_filter_alone_is_sufficient — same for rfq_user_filter. * test_raises_lists_all_four_satisfiers — the updated error message must enumerate all four valid filters so callers learn about the new shortcuts. * Mirror coverage on TestAsyncCommunications for the async surface. - CHANGELOG migration note: V2 client_order_id is required and acts as the server-side idempotency key, so callers migrating from V1's optional client_order_id must generate a unique value (UUID4) per call; reusing one returns the original order instead of placing a new one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the fifth pass. Pushed #1 —
|
Code Review — PR #155: Sync spec to v3.18.0 + fix asyncapi drift false alarmOverall this is a well-structured, large spec sync. The V2 order family, deposits/withdrawals, and Bug / Spec Omission
The v3.18.0 required:
- balance
- balance_dollars # <-- NEW, now required
- portfolio_value
- updated_ts
properties:
balance_dollars:
$ref: '#/components/schemas/FixedPointDollars'
balance_breakdown: # <-- NEW, optional array of IndexedBalance
type: array
items:
$ref: '#/components/schemas/IndexedBalance'Neither was added to Suggested fix: class Balance(BaseModel):
balance: int
balance_dollars: DollarDecimal # new in v3.18.0
portfolio_value: int
updated_ts: int
balance_breakdown: list[IndexedBalance] | None = None # new in v3.18.0
model_config = {"extra": "allow"}
class IndexedBalance(BaseModel):
exchange_index: int
balance: DollarDecimal
model_config = {"extra": "allow"}This is the only spec item in the 3.13→3.18 delta that appears to have been missed. Minor Observations
The
This is intentional and consistent with the established SDK pattern (CLAUDE.md: "Async
The method already exists in
The docstring documents the spec invariant ("when error is set, reduced_by is still present and is 0"). The Test Coverage SummaryCoverage is strong: 1866 tests, 0 failures, all 23 drift failures resolved without new
The only meaningful gap is the async SummaryOne real issue to fix before merge: |
….0 miss) Addresses claude[bot]'s sixth review on PR #155 — a real spec drift miss. The contract drift tests only cover request bodies, so a required field added to a response model slipped through. - New IndexedBalance model (exchange_index + balance). Exported from kalshi and kalshi.models. - Balance gains: * balance_dollars: DollarDecimal — required in spec v3.18.0; the same value as `balance` (cents) rendered as a fixed-point dollar string. * balance_breakdown: list[IndexedBalance] | None — optional per-shard split when present. - All existing balance mocks updated to include balance_dollars (now required by the model — the prior shape would fail validation). - New tests: * test_balance_breakdown — happy path parsing the new array. * test_balance_breakdown_omitted — confirms None default when absent. - Integration test_balance now asserts balance_dollars is non-null. - TestAsyncCancelOrderV2.test_passes_query_params — async parity with the existing sync regression guard for cancel_v2 routing both subaccount AND exchange_index to query params (unlike amend_v2 / decrease_v2 where exchange_index lives in the body). - CHANGELOG v2.1.0 entry enumerates the new Balance fields and IndexedBalance model. Note on coverage gap: the contract drift tests check request models (TestRequestBodyDrift) but no equivalent walks response schemas. Worth a follow-up to add response-side drift detection so future required- field additions don't slip through. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Good catch — this was a real spec-drift miss. Pushed #1 —
|
Code Review — PR #155: Sync spec to v3.18.0 + fix AsyncAPI drift false alarmOverall this is a well-structured spec-sync PR. The new resources follow established patterns, mypy/ruff/pytest all pass, and the contract drift tests are clean. A few items to address before merge. FindingsMedium — Type asymmetry:
|
Addresses claude[bot]'s seventh review on PR #155. All items were non-blocking polish; bundled together rather than spread across commits. - BatchCreateOrdersV2ResponseEntry docstring now explains the ts_ms asymmetry vs. CreateOrderV2Response: batch entries may be per-order errors with no timestamp, while a single create either succeeds with a timestamp or raises HTTP error. - IndexedBalance.balance docstring flags that the field is DollarDecimal (per spec), unlike Balance.balance which is integer cents. Same field name, different semantics — readers iterating balance_breakdown need to be deliberate. Pointer to balance_dollars (same units) for cross- reference. - AsyncPortfolioResource.deposits_all and withdrawals_all gained the "Returns an async iterator — use ``async for``." docstring, matching AsyncIncentiveProgramsResource.list_all's documentation pattern. - New tests filling small gaps the reviewer flagged: * TestPortfolioWithdrawals.test_auth_failure and the async mirror — every other resource has both the 401-from-server and the AuthRequiredError-before-request paths covered; withdrawals were missing one of them. * TestAsyncAmendDecreaseV2QueryParams.test_decrease_v2 now also asserts exchange_index lands in the body (matching the sync version's assertions). * TestAsyncAccountEndpointCosts.test_returns_empty_costs — parity with the sync empty-list test. mypy clean, ruff clean. 667 tests pass across touched files (was 653). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the seventh pass. Pushed Docstring clarifications
Async iterator docstrings — Test gaps
Acknowledged, no action
Validationmypy clean, ruff clean. 667 tests pass across touched files (was 653 in round 6). |
Code Review — PR #155: sync spec to v3.18.0 + fix asyncapi driftOverall this is a solid, well-structured spec-sync PR. The code follows project conventions closely and the test coverage is thorough. A few things worth addressing before merge. Medium:
|
Addresses claude[bot]'s eighth review on PR #155. - CHANGELOG v2.1.0 now has a "Changed" subsection calling out the soft-breaking change: callers building Balance(...) directly in their own tests/mocks must now supply balance_dollars. The field stays required because the spec marks it required (the prior drift-detection miss is what added it). Includes a before/after example showing the construction-site impact. - New unauth_portfolio + unauth_async_portfolio fixtures. - New tests for the AuthRequiredError pre-check path on every new portfolio method (sync + async): test_deposits_requires_auth, test_deposits_all_requires_auth, test_withdrawals_requires_auth, test_withdrawals_all_requires_auth. The existing test_auth_failure exercises the 401-from-server path; these complete the parity by hitting the local _require_auth() pre-check. - New TestAsyncCreateOrderV2.test_serializes_body — async mirror of the sync wire-shape assertion (price as "0.50", count as "10", no phantom keys). Guards DollarDecimal / FixedPointCount with mode="json" on the async dispatch path. mypy clean, ruff clean, 503 tests pass across touched files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the eighth pass. Pushed #1 (blocking) —
|
Code Review — PR #155: sync spec to v3.18.0 + fix asyncapi drift false alarmOverall: Well-executed spec sync. The code is consistent with project conventions, contract drift tests are clean (372 passed), and the full test suite is green (1866 passed). A few observations below — nothing blocking. Strengths
Minor Issues / Questions1. The model comment is correct ("spec marks them 2. Both have exactly the same six fields. 3. The comment in 4. V1 uses 5. The V1 6. Not a bug (tests pass), but confirming Test CoverageCoverage is solid:
One gap noted above: no test for SummaryNo blocking issues. The two actionable suggestions:
Everything else is informational. Ready to merge after those are addressed (or consciously deferred to a follow-up). |
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>
Summary
specs/asyncapi.yamlso the weekly spec-sync workflow stops opening false-alarm drift issues every Monday. The file was never committed, so each cron run diffed the freshly downloaded asyncapi against a non-existent old file and rendered a bogus "0 → 13 channels" delta.exchange_index/user_filter/rfq_user_filter/incentive_descriptionquery params andpost_onlyonCreateQuoteRequestto keep contract drift tests green.What's new
V2 event-market orders (legacy
/portfolio/ordersdeprecated no earlier than May 6, 2026):orders.create_v2(request=CreateOrderV2Request(...))orders.cancel_v2(order_id, subaccount=..., exchange_index=...)orders.amend_v2(order_id, request=AmendOrderV2Request(...))orders.decrease_v2(order_id, request=DecreaseOrderV2Request(...))orders.batch_create_v2(request=BatchCreateOrdersV2Request(...))orders.batch_cancel_v2(request=BatchCancelOrdersV2Request(...))Portfolio history:
portfolio.deposits()+portfolio.deposits_all()portfolio.withdrawals()+portfolio.withdrawals_all()Account:
account.endpoint_costs()— lists endpoints whose token cost differs from the default.Field additions (all optional,
exclude_nonesemantics preserved):CreateOrderRequest,AmendOrderRequest,DecreaseOrderRequest,BatchCancelOrdersRequestOrder,CreateOrderGroupRequest—exchange_index: int | None.CreateQuoteRequest—post_only: bool | None.OrdersResource.cancel,OrderGroupsResource.delete—exchange_indexquery param.CommunicationsResource.list_rfqs/list_all_rfqs—user_filter.CommunicationsResource.list_quotes/list_all_quotes—user_filter,rfq_user_filter._require_quote_filternow accepts these as satisfying the server-side filter requirement.IncentiveProgramsResource.list/list_all—incentive_description.Infra fix:
.gitignoreline 34 (specs/asyncapi.yaml) removed. Both pinned spec files are now committed, matching the comment intent already on the openapi line. Resolves the recurring drift false-alarm noted in the comment of Spec drift 2026-05-18: openapi 3.13.0 → 3.18.0 #154 itself.Test plan
uv run mypy kalshi/— clean (76 files).uv run ruff check .— clean.uv run pytest tests/ --ignore=tests/integration— 1866 passed, 0 failed.uv run pytest tests/test_contracts.py— 372 passed (all 23 drift failures resolved without EXCLUSIONS placeholders).7e31fc45…,6ff8a7f3…)./account/endpoint_costs,/portfolio/deposits,/portfolio/withdrawalsagainst demo.Closes #154
🤖 Generated with Claude Code