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
112 changes: 112 additions & 0 deletions .github/workflows/spec-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ on:
- cron: "0 6 * * *"
workflow_dispatch:

# Least privilege: the test job only needs read. The failure-reporting job
# below re-grants issues:write at job scope (it opens a tracking issue).
permissions:
contents: read

jobs:
drift-check:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -39,3 +44,110 @@ jobs:
- name: Run contract tests (PR — warnings only)
if: github.event_name != 'schedule'
run: uv run pytest tests/test_contracts.py -v

# When the scheduled strict run fails, open (and dedup) a single tracking
# issue. `needs` + `if: failure()` scopes this to a failed drift-check; the
# extra `github.event_name == 'schedule'` guard keeps it off PR runs (a PR
# failure already blocks the merge — no issue needed). Least privilege:
# issues:write lives only on this job; drift-check stays read-only. Actions
# are pinned to commit SHAs here (not @v6/@v7 tags like drift-check) because
# this job holds issues:write AND runs sync_spec.py against untrusted
# upstream — same threat model as spec-sync.yml (see its header comment).
report-failure:
needs: drift-check
if: failure() && github.event_name == 'schedule'
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0

- name: Set up Python
run: uv python install 3.12

- name: Install dependencies
run: uv sync --dev

# Re-fetch the same fresh spec drift-check saw, then re-run the strict
# suite to capture the failing tail for the issue body. continue-on-error
# so this step's (expected) failure doesn't abort the report.
- name: Reproduce failure output
id: repro
continue-on-error: true
run: |
set -uo pipefail
uv run python scripts/sync_spec.py || true
uv run pytest tests/test_contracts.py -v -W error::UserWarning \
> /tmp/drift.log 2>&1 || true
# Keep the last 200 lines so the body stays inside GitHub's limits.
tail -n 200 /tmp/drift.log > /tmp/drift.tail

- name: Ensure spec-drift label exists
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh label create spec-drift \
--color 'e4e669' \
--description 'Upstream OpenAPI/AsyncAPI spec changed since last sync' \
--force \
--repo "${GITHUB_REPOSITORY}"

- name: Open or update nightly drift tracker issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail

# Stable marker (NOT a content fingerprint): exactly one open tracker
# at a time. Re-runs append a dated comment instead of duplicating.
readonly MARKER='nightly-spec-drift-tracker'
today=$(date -u '+%Y-%m-%d')

existing=$(gh issue list \
--repo "${GITHUB_REPOSITORY}" \
--state open \
--label spec-drift \
--limit 200 \
--json number,body \
--jq '[.[] | select(.body | contains("'"${MARKER}"'"))] | first | .number // empty')

# Body / comment share the captured failing tail. printf (no unquoted
# heredoc) so upstream-sourced log text is never shell-expanded.
{
printf '## Nightly strict contract tests failed\n\n'
printf 'The scheduled `Spec Drift Detection` run promoted additive drift to an error.\n'
printf 'This means upstream OpenAPI/AsyncAPI changed and the SDK contract suite no longer matches.\n\n'
printf '**To resolve:** locally run `uv run python scripts/sync_spec.py` + `uv run python scripts/generate.py`, reconcile models/maps, and open a PR with `Closes #<this issue>`.\n\n'
printf -- '- Failing run: %s\n\n' "${RUN_URL}"
printf '### Failing test output (last 200 lines)\n\n```\n'
cat /tmp/drift.tail
printf '\n```\n'
} > /tmp/body.md

if [ -n "${existing}" ]; then
{
printf 'Still failing as of **%s** ([run](%s)).\n\n' "${today}" "${RUN_URL}"
printf '### Latest failing output (last 200 lines)\n\n```\n'
cat /tmp/drift.tail
printf '\n```\n'
} > /tmp/comment.md
echo "Tracker already open as #${existing}; appending dated comment."
gh issue comment "${existing}" \
--repo "${GITHUB_REPOSITORY}" \
--body-file /tmp/comment.md
exit 0
fi

# Hidden marker so the dedup query above matches future runs.
printf '\n<!-- spec-drift-bot: do not edit\n%s\n-->\n' "${MARKER}" >> /tmp/body.md

gh issue create \
--repo "${GITHUB_REPOSITORY}" \
--label spec-drift \
--title "Nightly spec-drift: strict contract tests failing (since ${today})" \
--body-file /tmp/body.md
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence

This project is indexed by GitNexus as **kalshi-python-sdk** (13573 symbols, 29721 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **kalshi-python-sdk** (13637 symbols, 29888 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

Expand Down
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,58 @@

All notable changes to kalshi-sdk will be documented in this file.

## 4.1.0 — 2026-06-14

Spec sync from upstream OpenAPI v3.20.0 → v3.21.0 (plus AsyncAPI/perps). All
changes are additive — new query params, response fields, and four new
endpoints. Also closes the nightly spec-drift CI gap (failures now open a
tracking issue).

### Added

- **`client.communications.block_trade_proposals`** — new sub-resource for the
block-trade-proposals API (openapi 3.21.0): `list()` / `list_all()`
(`GET /communications/block-trade-proposals`), `create()`
(`POST /communications/block-trade-proposals`), and `accept()`
(`POST /communications/block-trade-proposals/{id}/accept`). New models
`BlockTradeProposal` / `GetBlockTradeProposalsResponse` /
`ProposeBlockTradeRequest` / `ProposeBlockTradeResponse` /
`AcceptBlockTradeProposalRequest` (exported from `kalshi`).
- **`AccountResource.volume_progress()`** — `GET /account/api_usage_level/volume_progress`
returns trailing-30-day trading-volume progress toward volume-based API usage
tiers. New models `AccountVolumeProgress` / `AccountApiUsageLevelVolumeProgress`
/ `AccountApiUsageLevelVolumeGoal`.
- **`events.list` / `list_all` gain a `tickers` filter** — comma-separated event
tickers (`GET /events?tickers=...`).
- **`communications` quotes endpoints gain `min_ts` / `max_ts`** — filter quotes
by last-updated Unix timestamp on `quotes.list` / `list_all` (and the
deprecated `list_quotes` / `list_all_quotes` forwarders).
- **Perps `MarginMarket` mark-price fields** — `settlement_mark_price`,
`liquidation_mark_price`, and `reference_price` (each a nested `TickerPrice`
of `{price, ts_ms}`); **`MarginPosition.subaccount`** (the holding subaccount
number); and **WS `ErrorPayload.market_tickers`** (multi-market error frames).
Note: `MarginPosition.subaccount` is spec-**required**, so code that constructs
`MarginPosition` directly (e.g. test mocks) must now include it.

### Changed

- Re-vendored `specs/openapi.yaml` (3.20.0 → 3.21.0), `specs/asyncapi.yaml`,
`specs/perps_openapi.yaml`, and `specs/perps_scm_openapi.yaml`.
- **Upstream narrowed the `Settlement.market_result` enum** — `void` was removed
(now `yes` / `no` / `scalar`). No SDK change: `Settlement.market_result` is a
plain `str` with `extra="allow"`, so any value still parses; noted here for
accuracy.
- **Nightly spec-drift CI now files a tracking issue on failure.** The scheduled
`Spec Drift Detection` run previously failed silently between the weekly
`Weekly Spec Sync` runs; it now opens (and dedups) a single `spec-drift` issue
so upstream drift is tracked the day it appears.

### Fixed

- Hardened the `TestWsSpecDrift` contract-test fixture (the class-scoped fixture
is now a `@staticmethod`) so the strict nightly run reports real WebSocket
drift instead of erroring the whole class under `-W error::UserWarning`.

## 4.0.0 — 2026-06-09

Spec-drift reconciliation against the latest upstream OpenAPI (3.20.0) and AsyncAPI
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ tests/

## API Reference

- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.20.0, 99 operations)
- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.21.0, 104 operations)
- AsyncAPI spec: https://docs.kalshi.com/asyncapi.yaml (13 WebSocket channels)
- Base URL: https://api.elections.kalshi.com/trade-api/v2
- Demo URL: https://demo-api.kalshi.co/trade-api/v2
Expand All @@ -144,7 +144,7 @@ Reference issues from PRs via `Closes #N` so the issue closes on merge.
<!-- gitnexus:start -->
# GitNexus — Code Intelligence

This project is indexed by GitNexus as **kalshi-python-sdk** (13573 symbols, 29721 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **kalshi-python-sdk** (13637 symbols, 29888 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ 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 (12 typed `subscribe_*` channels + 2 escape-hatch).
- **Full coverage** of the Kalshi REST API (104 operations across 19 resources, OpenAPI v3.21.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.
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) prediction
markets API.

- **Full REST coverage** — 99 operations across 19 resources (OpenAPI v3.20.0),
- **Full REST coverage** — 104 operations across 19 resources (OpenAPI v3.21.0),
every kwarg drift-tested against the spec.
- **V2 event-market orders** — new `create_v2` / `amend_v2` / `decrease_v2` /
`cancel_v2` family on `/portfolio/events/orders/*`. Legacy `/portfolio/orders`
Expand Down
22 changes: 21 additions & 1 deletion kalshi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
from kalshi.fix import FixClient, FixConfig, FixEnvironment, FixSessionType
from kalshi.models import (
RFQ,
AcceptBlockTradeProposalRequest,
AcceptQuoteRequest,
AccountApiLimits,
AccountApiUsageLevelVolumeGoal,
AccountApiUsageLevelVolumeProgress,
AccountEndpointCosts,
AccountVolumeProgress,
ActionLiteral,
AmendOrderRequest,
AmendOrderResponse,
Expand All @@ -55,6 +59,7 @@
BatchCreateOrdersV2Response,
BatchCreateOrdersV2ResponseEntry,
BidAskDistribution,
BlockTradeProposal,
BookSideLiteral,
CancelOrderV2Response,
Candlestick,
Expand Down Expand Up @@ -90,6 +95,7 @@
GenerateApiKeyRequest,
GenerateApiKeyResponse,
GetApiKeysResponse,
GetBlockTradeProposalsResponse,
GetCommunicationsIDResponse,
GetFiltersBySportsResponse,
GetGameStatsResponse,
Expand Down Expand Up @@ -143,6 +149,8 @@
PlayByPlayPeriod,
PositionsResponse,
PriceDistribution,
ProposeBlockTradeRequest,
ProposeBlockTradeResponse,
Quote,
QuoteStatusLiteral,
RateLimit,
Expand Down Expand Up @@ -181,18 +189,24 @@
KlearConfig,
)
from kalshi.resources.communications import (
AsyncBlockTradeProposalsResource,
AsyncQuotesResource,
AsyncRFQsResource,
BlockTradeProposalsResource,
QuotesResource,
RFQsResource,
)
from kalshi.types import NullableList, StrictInt

__all__ = [
"RFQ",
"AcceptBlockTradeProposalRequest",
"AcceptQuoteRequest",
"AccountApiLimits",
"AccountApiUsageLevelVolumeGoal",
"AccountApiUsageLevelVolumeProgress",
"AccountEndpointCosts",
"AccountVolumeProgress",
"ActionLiteral",
"AmendOrderRequest",
"AmendOrderResponse",
Expand All @@ -203,6 +217,7 @@
"ApiUsageLevelGrant",
"ApplySubaccountTransferRequest",
"AssociatedEvent",
"AsyncBlockTradeProposalsResource",
"AsyncKalshiClient",
"AsyncKlearClient",
"AsyncPerpsClient",
Expand All @@ -225,6 +240,8 @@
"BatchCreateOrdersV2Response",
"BatchCreateOrdersV2ResponseEntry",
"BidAskDistribution",
"BlockTradeProposal",
"BlockTradeProposalsResource",
"BookSideLiteral",
"CancelOrderV2Response",
"Candlestick",
Expand Down Expand Up @@ -264,6 +281,7 @@
"GenerateApiKeyRequest",
"GenerateApiKeyResponse",
"GetApiKeysResponse",
"GetBlockTradeProposalsResponse",
"GetCommunicationsIDResponse",
"GetFiltersBySportsResponse",
"GetGameStatsResponse",
Expand Down Expand Up @@ -344,6 +362,8 @@
"PlayByPlayPeriod",
"PositionsResponse",
"PriceDistribution",
"ProposeBlockTradeRequest",
"ProposeBlockTradeResponse",
"Quote",
"QuoteStatusLiteral",
"QuotesResource",
Expand Down Expand Up @@ -377,4 +397,4 @@
"Withdrawal",
]

__version__ = "4.0.0"
__version__ = "4.1.0"
41 changes: 41 additions & 0 deletions kalshi/_contract_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,25 @@ class ContractEntry:
sdk_model="kalshi.models.communications.AcceptQuoteRequest",
spec_schema="AcceptQuoteRequest",
),
# Block trade proposals (openapi 3.21.0). The response item + the two
# request bodies get response-side drift coverage here, matching Quote/RFQ
# and CreateQuoteRequest/AcceptQuoteRequest. The list wrapper
# (GetBlockTradeProposalsResponse) and create response
# (ProposeBlockTradeResponse) are intentionally omitted, consistent with
# GetQuotesResponse / CreateQuoteResponse not being mapped.
ContractEntry(
sdk_model="kalshi.models.communications.BlockTradeProposal",
spec_schema="BlockTradeProposal",
notes="price_centi_cents/centicount are plain int (centi-cents/centicounts), not _fp.",
),
ContractEntry(
sdk_model="kalshi.models.communications.ProposeBlockTradeRequest",
spec_schema="ProposeBlockTradeRequest",
),
ContractEntry(
sdk_model="kalshi.models.communications.AcceptBlockTradeProposalRequest",
spec_schema="AcceptBlockTradeProposalRequest",
),
ContractEntry(
sdk_model="kalshi.models.subaccounts.SubaccountBalance",
spec_schema="SubaccountBalance",
Expand Down Expand Up @@ -226,6 +245,28 @@ class ContractEntry:
spec_schema="ApiUsageLevelGrant",
notes="exchange_instance/level/source required; expires_ts nullable (None = permanent).",
),
ContractEntry(
sdk_model="kalshi.models.account.AccountVolumeProgress",
spec_schema="GetAccountApiUsageLevelVolumeProgressResponse",
notes="Wrapper: volume_progress list of cron-computed snapshots.",
),
ContractEntry(
sdk_model="kalshi.models.account.AccountApiUsageLevelVolumeProgress",
spec_schema="AccountApiUsageLevelVolumeProgress",
notes=(
"computed_ts/trailing_30d_volume/goals all required. "
"trailing_30d_volume uses the trailing_30d_volume_fp alias "
"(FixedPointCount)."
),
),
ContractEntry(
sdk_model="kalshi.models.account.AccountApiUsageLevelVolumeGoal",
spec_schema="AccountApiUsageLevelVolumeGoal",
notes=(
"level/earn_volume_goal/keep_volume_goal all required; the two "
"goal fields use *_fp FixedPointCount aliases."
),
),
ContractEntry(
sdk_model="kalshi.models.structured_targets.StructuredTarget",
spec_schema="StructuredTarget",
Expand Down
Loading