Summary
The Kalshi OpenAPI spec consistently caps limit per endpoint (e.g. MarketLimitQuery 1–1000, LimitQuery 1–100/1–200, MilestoneLimitQuery 1–500). Every resource method types limit: int | None = None and forwards it untouched to the server.
Callers passing limit=0, limit=-5, or limit=10000 discover the bound only after a wasted round trip and a 400 error.
milestones.list is the worst offender — its docstring (milestones.py:69-70) literally says "limit REQUIRED, range 1-500" but only types it as int with no validation. Same gap on structured_targets.list(page_size=...) (spec 1–2000, default 100).
For a high-reliability trading SDK, fail-fast with spec-correct bounds is the documented contract; deferring to the server burns a round trip and produces a less-actionable error.
Location
kalshi/resources/orders.py:407, 502, 505 — limit: int | None = None, no validation
kalshi/resources/milestones.py:69-70, 82, 104 — docstring promises bounds, code doesn't enforce
kalshi/resources/structured_targets.py — page_size similarly unvalidated
- Many other
*.list methods
Recommended fix
Add a _validate_limit helper in kalshi/resources/_base.py:
def _validate_limit(value: int | None, *, lo: int = 1, hi: int, name: str = "limit") -> int | None:
if value is None:
return None
if not (lo <= value <= hi):
raise ValueError(
f"{name} must be in [{lo}, {hi}], got {value}. "
f"Kalshi enforces this server-side; validating client-side avoids a wasted round trip."
)
return value
Call it from each resource's param builder using the spec-correct bounds (sources of truth live in specs/openapi.yaml under each *LimitQuery schema):
def list(self, *, limit: int | None = None, ...):
limit = _validate_limit(limit, hi=1000) # MarketLimitQuery
...
For required limits (e.g. milestones.list), make limit: int (no default) and validate.
Add a per-resource parametrized test: test_limit_below_lo_raises, test_limit_above_hi_raises, test_limit_at_boundaries_accepted.
Severity & category
medium / consistency, spec-drift
Summary
The Kalshi OpenAPI spec consistently caps
limitper endpoint (e.g.MarketLimitQuery1–1000,LimitQuery1–100/1–200,MilestoneLimitQuery1–500). Every resource method typeslimit: int | None = Noneand forwards it untouched to the server.Callers passing
limit=0,limit=-5, orlimit=10000discover the bound only after a wasted round trip and a 400 error.milestones.listis the worst offender — its docstring (milestones.py:69-70) literally says "limit REQUIRED, range 1-500" but only types it asintwith no validation. Same gap onstructured_targets.list(page_size=...)(spec 1–2000, default 100).For a high-reliability trading SDK, fail-fast with spec-correct bounds is the documented contract; deferring to the server burns a round trip and produces a less-actionable error.
Location
kalshi/resources/orders.py:407, 502, 505—limit: int | None = None, no validationkalshi/resources/milestones.py:69-70, 82, 104— docstring promises bounds, code doesn't enforcekalshi/resources/structured_targets.py—page_sizesimilarly unvalidated*.listmethodsRecommended fix
Add a
_validate_limithelper inkalshi/resources/_base.py:Call it from each resource's param builder using the spec-correct bounds (sources of truth live in
specs/openapi.yamlunder each*LimitQueryschema):For required limits (e.g.
milestones.list), makelimit: int(no default) and validate.Add a per-resource parametrized test:
test_limit_below_lo_raises,test_limit_above_hi_raises,test_limit_at_boundaries_accepted.Severity & category
medium / consistency, spec-drift