Apply these before and during every task. They override default speed/verbosity bias. For trivial tasks, use judgment — but bias toward caution over speed.
Don't assume. Don't hide confusion. Surface tradeoffs.
- State assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them — don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If 200 lines could be 50, rewrite it.
Test: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
Touch only what you must. Clean up only your own mess.
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken. Match existing style.
- Remove imports/vars/functions only if YOUR changes orphaned them.
- Don't delete pre-existing dead code — mention it instead.
Test: every changed line should trace directly to the user's request.
Define success criteria. Loop until verified.
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan with verify: checks per step.
uv sync # install dependencies
uv run pytest tests/ -v # run all tests
uv run ruff check . # lint
uv run ruff check . --fix # lint + auto-fix
uv run mypy kalshi/ # type check (MUST pass before every commit)Always run mypy before committing. CI runs mypy strict and will reject the PR if it fails.
The list builtin is shadowed by resource .list() methods. Use builtins.list[T] in type
annotations inside resource classes (not bare list[T]).
Spec-First Hybrid SDK: hand-crafted client facade + Pydantic models. OpenAPI-generated
models live in kalshi/_generated/ and feed contract tests via _contract_map.py.
kalshi/
__init__.py # Public API exports + __version__
client.py / async_client.py # Sync and async facades
_base_client.py # SyncTransport + AsyncTransport (httpx, retry, error mapping)
_contract_map.py # Maps SDK models ↔ generated OpenAPI models for contract tests
_generated/ # OpenAPI-generated models (do not hand-edit)
auth.py # RSA-PSS signer
config.py # KalshiConfig (base URL, timeouts, retry policy)
errors.py # Exception hierarchy
types.py # DollarDecimal custom Pydantic type
models/ # common, markets, orders, events, exchange, historical,
# multivariate, portfolio, series
resources/ # Sync + async resources matching models/
ws/ # WebSocket: client, connection, channels, dispatch,
# backpressure, sequence, orderbook, models/
tests/
conftest.py # Shared fixtures (RSA keys, auth, config)
_contract_support.py # Contract test helpers
integration/ # Live API integration tests
ws/ # WebSocket tests
test_*.py # Per-resource + per-feature tests
- Price format: Kalshi API returns prices as FixedPointDollars strings (e.g.
"0.5600") with_dollarssuffix field names. SDK models use short Python names (yes_bid) withvalidation_alias=AliasChoices("yes_bid_dollars", "yes_bid")to accept both.CreateOrderRequestserializes with_dollarssuffix viaserialization_alias. Verified against OpenAPI spec v3.18.0 on 2026-05-18. - All prices use
Decimalvia theDollarDecimalcustom Pydantic type. - Auth signing payload:
str(timestamp_ms) + METHOD + path_only(path from urlparse, no query params, no trailing slash). - POST and DELETE are NEVER retried (duplicate order/cancel risk). Only GET/HEAD/OPTIONS retry.
- Retry on 429/502/503/504 with exponential backoff + jitter, capped at
retry_max_delay. Retry-Afterheader is capped atretry_max_delay(prevents server-controlled sleep).- Sync and async share logic via dual transport abstraction (not sync-wrapping-async).
- Async
list_all()returnsAsyncIteratordirectly —async for item in client.markets.list_all():works. - Request bodies are Pydantic models with
extra="forbid"(v0.8.0+). Every POST/PUT/DELETE-with-body method builds a request model internally and serializes viamodel.model_dump(exclude_none=True, by_alias=True, mode="json"). Don't build inline dict bodies in resource methods. Phantom keys fail at call time via the model's forbid. - Drift tests hard-fail.
TestRequestParamDrift(query+path) andTestRequestBodyDrift(body) parametrize overMETHOD_ENDPOINT_MAP. Adding a new kwarg the spec doesn't have, or missing one the spec has, reds CI. Intentional deviations go inEXCLUSIONS(tests/_contract_support.py) with a requiredreasonstring.
- Create
kalshi/models/new_resource.pywith Pydantic models. UseDollarDecimalfor prices,validation_alias=AliasChoices("api_name_dollars", "short_name")for API field mapping. - For POST/PUT/DELETE endpoints with a request body: create a request model (e.g.
CreateThingRequest) withmodel_config = {"extra": "forbid"}. Setserialization_alias="foo_dollars"/"count_fp"for wire-format mismatches. - Create
kalshi/resources/new_resource.pywith bothNewResource(SyncResource)andAsyncNewResource(AsyncResource). POST/PUT/DELETE methods build their request model internally, then serialize viamodel.model_dump(exclude_none=True, by_alias=True, mode="json"). - Wire into
KalshiClient.__init__andAsyncKalshiClient.__init__. - Export from
kalshi/models/__init__.pyandkalshi/__init__.py. - Add tests in
tests/test_new_resource.pyusingrespx.mock. - Every public method needs at least: happy path, error path, edge case.
- Register endpoints in
METHOD_ENDPOINT_MAP(tests/_contract_support.py). POST/PUT/DELETE entries must setrequest_body_schemato the spec ref (e.g.,"#/components/schemas/CreateThingRequest"). Add the spec-ref → model-FQN mapping toBODY_MODEL_MAPintests/test_contracts.pyso the body drift test can diff it. - If the resource has generated OpenAPI counterparts, register them in
_contract_map.py.
- pytest + pytest-asyncio + respx (httpx mock); ~1920 tests across unit + contract drift suites.
- Use
respx.mockfor HTTP mocking. Generate test RSA keys via conftest.py fixtures. - New function → write a test. Bug fix → write a regression test. New error path → write a test that triggers it.
- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.23.0, 101 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
- Auth: RSA-PSS / SHA256 / MGF1(SHA256) / salt_length=DIGEST_LENGTH / base64
All work — bugs, enhancements, polish, spec drift — is tracked in GitHub Issues
on TexasCoding/kalshi-python-sdk. Use the gh CLI or the GitHub UI; do not add
markdown trackers (TODOS/BACKLOG) back to the repo.
- Active milestone: post-v2.2 (after the response-side spec drift hardening stack that shipped v2.2.0)
- Labels in use:
bug,enhancement,documentation,polish,breaking,spec-drift,testing,infra,ws,cli ROADMAP.md— short pointer to the active milestoneCHANGELOG.md— release-facing history; updated per release
Reference issues from PRs via Closes #N so the issue closes on merge.
This project is indexed by GitNexus as kalshi-python-sdk (13553 symbols, 29700 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 analyzein terminal first.
- MUST run impact analysis before editing any symbol. Before modifying a function, class, or method, run
gitnexus_impact({target: "symbolName", direction: "upstream"})and report the blast radius (direct callers, affected processes, risk level) to the user. - MUST run
gitnexus_detect_changes()before committing to verify your changes only affect expected symbols and execution flows. - MUST warn the user if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use
gitnexus_query({query: "concept"})to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use
gitnexus_context({name: "symbolName"}).
- NEVER edit a function, class, or method without first running
gitnexus_impacton it. - NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use
gitnexus_renamewhich understands the call graph. - NEVER commit changes without running
gitnexus_detect_changes()to check affected scope.
| Resource | Use for |
|---|---|
gitnexus://repo/kalshi-python-sdk/context |
Codebase overview, check index freshness |
gitnexus://repo/kalshi-python-sdk/clusters |
All functional areas |
gitnexus://repo/kalshi-python-sdk/processes |
All execution flows |
gitnexus://repo/kalshi-python-sdk/process/{name} |
Step-by-step execution trace |
| Task | Read this skill file |
|---|---|
| Understand architecture / "How does X work?" | .claude/skills/gitnexus/gitnexus-exploring/SKILL.md |
| Blast radius / "What breaks if I change X?" | .claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md |
| Trace bugs / "Why is X failing?" | .claude/skills/gitnexus/gitnexus-debugging/SKILL.md |
| Rename / extract / split / refactor | .claude/skills/gitnexus/gitnexus-refactoring/SKILL.md |
| Tools, resources, schema reference | .claude/skills/gitnexus/gitnexus-guide/SKILL.md |
| Index, status, clean, wiki CLI commands | .claude/skills/gitnexus/gitnexus-cli/SKILL.md |