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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@

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

## 3.0.1 — 2026-05-26

OpenAPI spec sync from v3.18.0 → v3.19.0 (`#383`). Single additive
request-side constraint on `GET /structured_targets`: the `ids` query
param gained `maxItems: 2000`. Mirrored at the SDK boundary so an
oversize filter fails fast with a clear `ValueError` instead of paying
a network round trip for a 400. No endpoint, channel, or response-model
changes; regenerated `kalshi/_generated/models.py` is byte-identical to
the v3.18.0 output.

### Changed

- `StructuredTargetsResource.list` / `list_all` and their async
counterparts now raise `ValueError("ids accepts at most 2000 entries
per spec ...")` when `ids` exceeds 2000 entries. The 2000 boundary
is inclusive (matches spec `maxItems`). Mirrors the existing
`live_data.batch` (`milestone_ids`, max 100) and
`markets.bulk_*` (`tickers`, max 100) precedent.

### Internal

- `specs/openapi.yaml` snapshot bumped (sha256
`5eaeca6bb64b2ff0aa4f63f9e13381da5a8f6d8f9b34328408499a0503a3085d`).
- README + `docs/index.md` banners bumped to "OpenAPI v3.19.0".

## 3.0.0 — 2026-05-22

Public-API rename release. Three breaking-rename issues (`#348`, `#349`, `#351`)
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 (98 operations across 19 resources, OpenAPI v3.18.0) and WebSocket API (11 typed `subscribe_*` channels + 2 escape-hatch).
- **Full coverage** of the Kalshi REST API (98 operations across 19 resources, OpenAPI v3.19.0) and WebSocket API (11 typed `subscribe_*` channels + 2 escape-hatch).
- **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()`.
- **Sync and async** clients sharing one transport — no thread-pool wrapping.
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** — 98 operations across 19 resources (OpenAPI v3.18.0),
- **Full REST coverage** — 98 operations across 19 resources (OpenAPI v3.19.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
2 changes: 1 addition & 1 deletion kalshi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,4 @@
"Withdrawal",
]

__version__ = "2.6.0"
__version__ = "3.0.1"
16 changes: 16 additions & 0 deletions kalshi/resources/structured_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@
_validate_max_pages,
)

# Spec v3.19.0 added ``maxItems: 2000`` to the ``ids`` query param on
# ``GET /structured_targets``. Mirror it at the SDK boundary so an oversize
# filter fails fast with a clear ``ValueError`` instead of a 400 round trip.
_MAX_IDS = 2000


def _validate_ids(ids: builtins.list[str] | None) -> None:
if ids is not None and len(ids) > _MAX_IDS:
raise ValueError(
f"ids accepts at most {_MAX_IDS} entries per spec (got {len(ids)})"
)


class StructuredTargetsResource(SyncResource):
"""Sync structured targets API.
Expand All @@ -39,6 +51,7 @@ def list(
cursor: str | None = None,
extra_headers: dict[str, str] | None = None,
) -> Page[StructuredTarget]:
_validate_ids(ids)
_validate_limit(page_size, hi=2000, name="page_size")
params = _params(
ids=ids,
Expand Down Expand Up @@ -66,6 +79,7 @@ def list_all(
extra_headers: dict[str, str] | None = None,
) -> Iterator[StructuredTarget]:
_validate_max_pages(max_pages)
_validate_ids(ids)
_validate_limit(page_size, hi=2000, name="page_size")
params = _params(
ids=ids,
Expand Down Expand Up @@ -105,6 +119,7 @@ async def list(
cursor: str | None = None,
extra_headers: dict[str, str] | None = None,
) -> Page[StructuredTarget]:
_validate_ids(ids)
_validate_limit(page_size, hi=2000, name="page_size")
params = _params(
ids=ids,
Expand Down Expand Up @@ -133,6 +148,7 @@ def list_all(
) -> AsyncIterator[StructuredTarget]:
"""Returns an async iterator — use ``async for``."""
_validate_max_pages(max_pages)
_validate_ids(ids)
_validate_limit(page_size, hi=2000, name="page_size")
params = _params(
ids=ids,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "kalshi-sdk"
version = "3.0.0"
version = "3.0.1"
description = "A professional Python SDK for the Kalshi prediction markets API"
readme = "README.md"
license = { text = "MIT" }
Expand Down
3 changes: 2 additions & 1 deletion specs/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: Kalshi Trade API Manual Endpoints
version: 3.18.0
version: 3.19.0
description: >-
Manually defined OpenAPI spec for endpoints being migrated to spec-first
approach
Expand Down Expand Up @@ -2909,6 +2909,7 @@ paths:
required: false
schema:
type: array
maxItems: 2000
items:
type: string
style: form
Expand Down
43 changes: 43 additions & 0 deletions tests/test_structured_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,46 @@ def test_type_alias_round_trip(self) -> None:
{"target_type": "basketball_player"},
)
assert from_sdk.target_type == "basketball_player"

class TestIdsCap:
"""Spec v3.19.0 added ``maxItems: 2000`` on the ``ids`` query param.

The SDK mirrors this client-side so callers see a clear ``ValueError``
instead of paying a network round trip for a 400.
"""

def test_list_rejects_oversize_ids(
self, resource: StructuredTargetsResource,
) -> None:
with pytest.raises(ValueError, match=r"at most 2000.*got 2001"):
resource.list(ids=[f"uuid-{i}" for i in range(2001)])

def test_list_all_rejects_oversize_ids(
self, resource: StructuredTargetsResource,
) -> None:
with pytest.raises(ValueError, match=r"at most 2000.*got 2001"):
resource.list_all(ids=[f"uuid-{i}" for i in range(2001)])

@respx.mock
def test_list_allows_boundary(self, resource: StructuredTargetsResource) -> None:
respx.get("https://test.kalshi.com/trade-api/v2/structured_targets").mock(
return_value=httpx.Response(200, json={"structured_targets": []})
)
# 2000 is the spec cap — boundary must succeed.
page = resource.list(ids=[f"uuid-{i}" for i in range(2000)])
assert page.items == []

@pytest.mark.asyncio
async def test_async_list_rejects_oversize_ids(
self, async_resource: AsyncStructuredTargetsResource,
) -> None:
with pytest.raises(ValueError, match=r"at most 2000.*got 2001"):
await async_resource.list(ids=[f"uuid-{i}" for i in range(2001)])

def test_async_list_all_rejects_oversize_ids(
self, async_resource: AsyncStructuredTargetsResource,
) -> None:
# ``list_all`` is a plain ``def`` returning an AsyncIterator so the
# validator fires synchronously at call time, not on first iteration.
with pytest.raises(ValueError, match=r"at most 2000.*got 2001"):
async_resource.list_all(ids=[f"uuid-{i}" for i in range(2001)])
Loading