Epic: part of #387 · Depends on: #389, #390, #391, #392, #393, #394, #395, #396, #398, #400
Summary
Cross-cutting closeout for the PERPS (margin) API work: user-facing documentation (README section + docs/ page + mkdocs nav), runnable examples/, a CHANGELOG entry + version bump, the perps repo label, the spec-version banner update, and demo-environment integration tests gated exactly like the existing live tests but pointed at external-api.demo.kalshi.co. This issue ships last — it documents and exercises the PerpsClient / AsyncPerpsClient surface that the REST and WS feature issues build. No new SDK models or resource methods are introduced here; this is documentation, examples, tests, and release plumbing only.
This is the release-closeout issue: it depends on all perps REST resources, the WebSocket channels, and the SCM/Klear endpoints landing first. Docs, examples, and the CHANGELOG must cover the full shipped surface — REST, WS, and SCM/Klear — with no silent omissions.
Endpoints / scope
N/A — no new endpoints. This issue documents and integration-tests the already-implemented perps surface. For reference, the surface under documentation/test is the perps REST resource families and the perps WS channels:
| Area |
Surface being documented / tested |
| REST exchange |
GetMarginExchangeStatus, GetMarginRiskParameters |
| REST orders |
GetMarginOrders, CreateMarginOrder, GetMarginOrder, CancelMarginOrder, DecreaseMarginOrder, AmendMarginOrder, GetMarginFCMOrders |
| REST markets |
GetMarginMarkets, GetMarginMarket, GetMarginMarketOrderbook, GetMarginMarketCandlesticks |
| REST portfolio/risk |
GetMarginFills, GetMarginPositions, GetMarginTrades, GetMarginBalance, GetMarginRisk, GetMarginEnabled, GetMarginNotionalRiskLimit, GetMarginFeeTiers |
| REST funding |
GetMarginFundingHistory, GetMarginHistoricalFundingRates, GetMarginFundingRateEstimate |
| REST subaccounts/transfer |
IntraExchangeInstanceTransfer, CreateMarginSubaccount, ApplyMarginSubaccountTransfer |
| REST order groups |
GetMarginOrderGroups, CreateMarginOrderGroup, GetMarginOrderGroup, DeleteMarginOrderGroup, ResetMarginOrderGroup, TriggerMarginOrderGroup, UpdateMarginOrderGroupLimit |
| WS channels |
orderbook_delta (snapshot + delta), ticker (carries funding_rate / next_funding_time_ms), trade, fill, user_orders, order_group_updates |
Each implemented family must have at least one runnable example and at least one demo integration test (see Tests). Whichever families have NOT shipped at merge time are explicitly scoped out and noted as "pending" in the docs.
Models
N/A — no new Pydantic models. See the Deliverables checklist below in place of Models/Resource methods.
Deliverables checklist
1. README perps section (README.md)
- Add a top-of-file feature bullet noting perps/margin support (mirror the existing terse bullet style, e.g. the
V2 event-market orders bullet).
- Add a
## Perps (margin) trading section after the WebSocket quickstart with:
- a one-paragraph explainer (standalone
PerpsClient / AsyncPerpsClient, own base URL https://external-api.kalshi.com/trade-api/v2 prod / https://external-api.demo.kalshi.co/trade-api/v2 demo, reuses the same RSA-PSS auth as KalshiClient).
- a sync quickstart and an async quickstart, mirroring the existing
## Quickstart — sync / ## Quickstart — async blocks but constructing PerpsClient(key_id=..., private_key_path=...) / PerpsClient.from_env().
- a short note that prices are
DollarDecimal (FixedPointDollars, up to 6 decimals), counts are FixedPointCount (2 decimals, wire field suffix _fp), REST timestamps are RFC3339, and WS timestamps are Unix-epoch milliseconds (*_ms fields).
2. Docs page (docs/perps.md, NEW)
- New page covering: client construction, auth (links to existing
authentication.md), environment variables (from_env() + demo routing), the resource families, funding mechanics (funding rate, next_funding_time_ms), risk/balance introspection, and the perps WS channels (link to websockets.md patterns; note the WS host external-api-margin-ws.kalshi.com prod / external-api-margin-ws.demo.kalshi.co demo, path /trade-api/ws/v2/margin).
- Register in
mkdocs.yml nav: add a top-level - Perps: perps.md entry (place it after WebSocket: websockets.md). If per-resource pages are desired, add them under a Perps section mirroring the Resources: block — but a single perps.md is sufficient for this issue.
- If the perps resources expose mkdocstrings-rendered API reference, add the module path to
reference.md (mirror how existing resources are referenced).
3. Examples (examples/, NEW directory — none exists today)
examples/perps_create_order.py — construct PerpsClient.from_env(), place a resting (non-marketable) margin order via the perps orders create method, print the returned order, then cancel it. Mirror the safety pattern of tests/integration/conftest.py::non_marketable_price (far-from-market limit so it rests, not fills).
examples/perps_stream_ticker.py — async example: connect to the perps margin WS, subscribe to the ticker channel for a market, and print funding_rate + next_funding_time_ms as messages arrive (demonstrates funding mechanics).
examples/perps_balance_risk.py — call GetMarginBalance (balance()) and GetMarginRisk (risk()); print cash balance, position value, total balance, maintenance-margin requirement, account leverage, and per-position liquidation prices.
- Each example must be runnable as
python examples/perps_*.py, read credentials via from_env(), default to demo (KALSHI_DEMO=true or the perps demo base URL), and carry a top-of-file docstring stating the required env vars. Keep examples minimal — no argparse scaffolding unless a ticker arg is genuinely needed.
4. CHANGELOG + version bump
CHANGELOG.md: add a new top entry following the existing format (heading ## <new-version> — <date>, prose summary, ### Added bullets for PerpsClient / AsyncPerpsClient and the perps resource families + WS channels, ### Internal bullets for the vendored specs/perps_openapi.yaml (+ specs/perps_asyncapi.yaml) sha256 snapshots and the README/docs/index.md banner bump). Cross-link docs/perps.md.
- Bump
version in pyproject.toml:3 and __version__ in kalshi/__init__.py:356 to the same value. This is an additive release (new standalone client, no breaking changes to KalshiClient) → minor bump from 3.1.0 (e.g. 3.2.0). Do not use the breaking label.
5. Spec-version banner update
README.md and docs/index.md currently advertise "99 operations across 19 resources (OpenAPI v3.20.0)" and "13 channels"/"11 typed channels". Add a perps line to both banners stating the perps surface (operation count from /tmp/perps_openapi.yaml, WS channel count from /tmp/perps_asyncapi.yaml) and the perps spec version. Do not overwrite the existing core-spec banner — add a sibling line for perps.
6. perps repo label
- Create the
perps GitHub label on TexasCoding/kalshi-python-sdk (gh label create perps --description "Perps (margin) API" --color <hex>). All perps issues should carry it. (This issue itself is labeled documentation, testing per the planned labels; the foundation/REST/WS issues carry perps.)
Resource methods
N/A — no new resource methods. See Deliverables checklist above.
Contract-test wiring
N/A for this issue — METHOD_ENDPOINT_MAP / BODY_MODEL_MAP / EXCLUSIONS entries are owned by the REST and WS feature issues, not here.
This issue only consumes the contract-test harness parameterization established by the foundation issue (loading the vendored specs/perps_openapi.yaml). Acceptance for this issue includes confirming the full suite — including the perps drift tests added upstream — is green after the docs/version changes. If the version bump or banner edits touch anything the drift tests read, fix forward; do not add EXCLUSIONS to paper over a real drift.
Tests
New file(s) under tests/integration/ (one per family is fine; e.g. tests/integration/test_perps_orders.py, test_perps_markets.py, test_perps_balance_risk.py, test_perps_funding.py, test_perps_ws.py). These are live demo tests, not respx unit tests — mirror the existing tests/integration/ style exactly:
- Reuse
tests/integration/conftest.py fixture conventions. Add perps client fixtures (perps_sync_client, perps_async_client) that build PerpsClient.from_env() / AsyncPerpsClient.from_env(), auto-skip when KALSHI_KEY_ID is unset (_credentials_available()), and hard-fail unless pointed at demo — extend _assert_demo_url (or add a perps analog) to assert external-api.demo.kalshi.co is the resolved base URL and external-api-margin-ws.demo.kalshi.co for WS. Reuse the conftest RSA / from_env env-bridging plumbing; do not duplicate it.
- Gate exactly like existing live tests:
@pytest.mark.integration, auto-skip without credentials. Use @pytest.mark.integration_real_api_only (the existing marker, skipped unless KALSHI_ENABLE_REAL_API_ONLY=1) for any perps endpoint the demo server cannot service (e.g. SCM/Klear, or margin endpoints requiring a margin-enabled demo account).
- Add
skip_if_* guards mirroring skip_if_low_balance for: account not margin-enabled (GetMarginEnabled returns false → skip order/balance tests), and demo having no margin markets (GetMarginMarkets empty → skip).
- Register tested methods in
tests/integration/coverage_harness.py. Since perps resources live in kalshi/perps/resources/*, add the perps resource module paths to RESOURCE_MODULES (or introduce a parallel registry) so test_coverage.py introspects them; otherwise perps methods are invisible to the coverage check. Call register("PerpsOrdersResource", [...]) etc. from each test file.
Per-family coverage (each at least one happy-path live call; mark order-mutating tests with cleanup):
- orders: create a resting margin order (non-marketable price), assert it returns, then cancel (cleanup in an autouse session sweep mirroring
cleanup_orders); list orders; get a single order.
- markets: list margin markets, get one, fetch its orderbook and candlesticks.
- balance/risk:
balance() returns cash/position/total/maintenance-margin fields; risk() returns leverage + liquidation prices; enabled() boolean.
- funding:
funding_history, historical_funding_rates, funding_rate_estimate return without error and parse funding_rate as Decimal.
- ws (async): connect to demo margin WS, subscribe
ticker, receive at least one message, assert funding_rate parses and next_funding_time_ms is an int epoch-ms; mirror the ws_session fixture pattern.
- Add a session-end autouse cleanup sweep for any resting perps orders tagged with the run id (mirror
cleanup_orders + TEST_RUN_ID), so live order tests never leak.
Examples themselves are not part of CI unit tests, but add a lightweight import/syntax check (e.g. a tests/test_examples_importable.py that imports each examples/perps_*.py module without executing main()) so a broken example reds CI.
Acceptance criteria
Dependencies
- perps: foundation — PerpsClient/AsyncPerpsClient, PerpsConfig, spec vendoring & contract-test parameterization (provides the clients, vendored
specs/perps_openapi.yaml / specs/perps_asyncapi.yaml, and the contract-test harness parameterization this issue's tests rely on).
- Implicitly depends on the perps REST resource issues and the perps WebSocket issue landing first — this closeout documents and integration-tests their surface. Whichever families have not merged at the time this lands are scoped out of docs/examples/tests and noted as "pending".
Summary
Cross-cutting closeout for the PERPS (margin) API work: user-facing documentation (README section +
docs/page + mkdocs nav), runnableexamples/, aCHANGELOGentry + version bump, theperpsrepo label, the spec-version banner update, and demo-environment integration tests gated exactly like the existing live tests but pointed atexternal-api.demo.kalshi.co. This issue ships last — it documents and exercises thePerpsClient/AsyncPerpsClientsurface that the REST and WS feature issues build. No new SDK models or resource methods are introduced here; this is documentation, examples, tests, and release plumbing only.This is the release-closeout issue: it depends on all perps REST resources, the WebSocket channels, and the SCM/Klear endpoints landing first. Docs, examples, and the CHANGELOG must cover the full shipped surface — REST, WS, and SCM/Klear — with no silent omissions.
Endpoints / scope
N/A — no new endpoints. This issue documents and integration-tests the already-implemented perps surface. For reference, the surface under documentation/test is the perps REST resource families and the perps WS channels:
GetMarginExchangeStatus,GetMarginRiskParametersGetMarginOrders,CreateMarginOrder,GetMarginOrder,CancelMarginOrder,DecreaseMarginOrder,AmendMarginOrder,GetMarginFCMOrdersGetMarginMarkets,GetMarginMarket,GetMarginMarketOrderbook,GetMarginMarketCandlesticksGetMarginFills,GetMarginPositions,GetMarginTrades,GetMarginBalance,GetMarginRisk,GetMarginEnabled,GetMarginNotionalRiskLimit,GetMarginFeeTiersGetMarginFundingHistory,GetMarginHistoricalFundingRates,GetMarginFundingRateEstimateIntraExchangeInstanceTransfer,CreateMarginSubaccount,ApplyMarginSubaccountTransferGetMarginOrderGroups,CreateMarginOrderGroup,GetMarginOrderGroup,DeleteMarginOrderGroup,ResetMarginOrderGroup,TriggerMarginOrderGroup,UpdateMarginOrderGroupLimitorderbook_delta(snapshot + delta),ticker(carriesfunding_rate/next_funding_time_ms),trade,fill,user_orders,order_group_updatesEach implemented family must have at least one runnable example and at least one demo integration test (see Tests). Whichever families have NOT shipped at merge time are explicitly scoped out and noted as "pending" in the docs.
Models
N/A — no new Pydantic models. See the Deliverables checklist below in place of Models/Resource methods.
Deliverables checklist
1. README perps section (
README.md)V2 event-market ordersbullet).## Perps (margin) tradingsection after the WebSocket quickstart with:PerpsClient/AsyncPerpsClient, own base URLhttps://external-api.kalshi.com/trade-api/v2prod /https://external-api.demo.kalshi.co/trade-api/v2demo, reuses the same RSA-PSS auth asKalshiClient).## Quickstart — sync/## Quickstart — asyncblocks but constructingPerpsClient(key_id=..., private_key_path=...)/PerpsClient.from_env().DollarDecimal(FixedPointDollars, up to 6 decimals), counts areFixedPointCount(2 decimals, wire field suffix_fp), REST timestamps are RFC3339, and WS timestamps are Unix-epoch milliseconds (*_msfields).2. Docs page (
docs/perps.md, NEW)authentication.md), environment variables (from_env()+ demo routing), the resource families, funding mechanics (funding rate,next_funding_time_ms), risk/balance introspection, and the perps WS channels (link towebsockets.mdpatterns; note the WS hostexternal-api-margin-ws.kalshi.comprod /external-api-margin-ws.demo.kalshi.codemo, path/trade-api/ws/v2/margin).mkdocs.ymlnav: add a top-level- Perps: perps.mdentry (place it afterWebSocket: websockets.md). If per-resource pages are desired, add them under aPerpssection mirroring theResources:block — but a singleperps.mdis sufficient for this issue.reference.md(mirror how existing resources are referenced).3. Examples (
examples/, NEW directory — none exists today)examples/perps_create_order.py— constructPerpsClient.from_env(), place a resting (non-marketable) margin order via the perps orders create method, print the returned order, then cancel it. Mirror the safety pattern oftests/integration/conftest.py::non_marketable_price(far-from-market limit so it rests, not fills).examples/perps_stream_ticker.py— async example: connect to the perps margin WS, subscribe to thetickerchannel for a market, and printfunding_rate+next_funding_time_msas messages arrive (demonstrates funding mechanics).examples/perps_balance_risk.py— callGetMarginBalance(balance()) andGetMarginRisk(risk()); print cash balance, position value, total balance, maintenance-margin requirement, account leverage, and per-position liquidation prices.python examples/perps_*.py, read credentials viafrom_env(), default to demo (KALSHI_DEMO=trueor the perps demo base URL), and carry a top-of-file docstring stating the required env vars. Keep examples minimal — no argparse scaffolding unless a ticker arg is genuinely needed.4. CHANGELOG + version bump
CHANGELOG.md: add a new top entry following the existing format (heading## <new-version> — <date>, prose summary,### Addedbullets forPerpsClient/AsyncPerpsClientand the perps resource families + WS channels,### Internalbullets for the vendoredspecs/perps_openapi.yaml(+specs/perps_asyncapi.yaml) sha256 snapshots and the README/docs/index.mdbanner bump). Cross-linkdocs/perps.md.versioninpyproject.toml:3and__version__inkalshi/__init__.py:356to the same value. This is an additive release (new standalone client, no breaking changes toKalshiClient) → minor bump from3.1.0(e.g.3.2.0). Do not use thebreakinglabel.5. Spec-version banner update
README.mdanddocs/index.mdcurrently advertise "99 operations across 19 resources (OpenAPI v3.20.0)" and "13 channels"/"11 typed channels". Add a perps line to both banners stating the perps surface (operation count from/tmp/perps_openapi.yaml, WS channel count from/tmp/perps_asyncapi.yaml) and the perps spec version. Do not overwrite the existing core-spec banner — add a sibling line for perps.6.
perpsrepo labelperpsGitHub label onTexasCoding/kalshi-python-sdk(gh label create perps --description "Perps (margin) API" --color <hex>). All perps issues should carry it. (This issue itself is labeleddocumentation,testingper the planned labels; the foundation/REST/WS issues carryperps.)Resource methods
N/A — no new resource methods. See Deliverables checklist above.
Contract-test wiring
N/A for this issue —
METHOD_ENDPOINT_MAP/BODY_MODEL_MAP/EXCLUSIONSentries are owned by the REST and WS feature issues, not here.This issue only consumes the contract-test harness parameterization established by the foundation issue (loading the vendored
specs/perps_openapi.yaml). Acceptance for this issue includes confirming the full suite — including the perps drift tests added upstream — is green after the docs/version changes. If the version bump or banner edits touch anything the drift tests read, fix forward; do not addEXCLUSIONSto paper over a real drift.Tests
New file(s) under
tests/integration/(one per family is fine; e.g.tests/integration/test_perps_orders.py,test_perps_markets.py,test_perps_balance_risk.py,test_perps_funding.py,test_perps_ws.py). These are live demo tests, not respx unit tests — mirror the existingtests/integration/style exactly:tests/integration/conftest.pyfixture conventions. Add perps client fixtures (perps_sync_client,perps_async_client) that buildPerpsClient.from_env()/AsyncPerpsClient.from_env(), auto-skip whenKALSHI_KEY_IDis unset (_credentials_available()), and hard-fail unless pointed at demo — extend_assert_demo_url(or add a perps analog) to assertexternal-api.demo.kalshi.cois the resolved base URL andexternal-api-margin-ws.demo.kalshi.cofor WS. Reuse the conftest RSA /from_envenv-bridging plumbing; do not duplicate it.@pytest.mark.integration, auto-skip without credentials. Use@pytest.mark.integration_real_api_only(the existing marker, skipped unlessKALSHI_ENABLE_REAL_API_ONLY=1) for any perps endpoint the demo server cannot service (e.g. SCM/Klear, or margin endpoints requiring a margin-enabled demo account).skip_if_*guards mirroringskip_if_low_balancefor: account not margin-enabled (GetMarginEnabledreturns false → skip order/balance tests), and demo having no margin markets (GetMarginMarketsempty → skip).tests/integration/coverage_harness.py. Since perps resources live inkalshi/perps/resources/*, add the perps resource module paths toRESOURCE_MODULES(or introduce a parallel registry) sotest_coverage.pyintrospects them; otherwise perps methods are invisible to the coverage check. Callregister("PerpsOrdersResource", [...])etc. from each test file.Per-family coverage (each at least one happy-path live call; mark order-mutating tests with cleanup):
cleanup_orders); list orders; get a single order.balance()returns cash/position/total/maintenance-margin fields;risk()returns leverage + liquidation prices;enabled()boolean.funding_history,historical_funding_rates,funding_rate_estimatereturn without error and parsefunding_rateasDecimal.ticker, receive at least one message, assertfunding_rateparses andnext_funding_time_msis an int epoch-ms; mirror thews_sessionfixture pattern.cleanup_orders+TEST_RUN_ID), so live order tests never leak.Examples themselves are not part of CI unit tests, but add a lightweight import/syntax check (e.g. a
tests/test_examples_importable.pythat imports eachexamples/perps_*.pymodule without executingmain()) so a broken example reds CI.Acceptance criteria
## Perps (margin) tradingsection with sync and async quickstarts usingPerpsClient/AsyncPerpsClient.docs/perps.mdcreated and added tomkdocs.ymlnav;mkdocs build --strictpasses (no broken links / orphan pages).examples/directory created withperps_create_order.py,perps_stream_ticker.py(funding),perps_balance_risk.py— each runnable, demo-defaulted, credential-via-from_env().CHANGELOG.mdhas a new top entry;versioninpyproject.tomland__version__inkalshi/__init__.pybumped to the same new minor version.docs/index.mdbanners updated with a perps line (operation/channel counts from the perps specs + perps spec version); existing core-spec banner left intact.perpsGitHub label created on the repo.tests/integration/for every shipped perps family, gated by@pytest.mark.integration, auto-skipping withoutKALSHI_KEY_ID, hard-failing unless resolved toexternal-api.demo.kalshi.co(and the margin WS demo host). Order-mutating tests have a cleanup sweep.coverage_harness.RESOURCE_MODULES(or a parallel registry) andregister(...)called from each perps integration test file;test_coverage.pyis green for perps resources.tests/test_examples_importable.py).uv run ruff check .clean (incl. newexamples/and tests).uv run mypy kalshi/strict clean (no perps-source changes here, but version-file edits must not regress); examples annotated enough to not break if mypy scope includes them, usingbuiltins.list[T]if any example touches a resource.list()return inside a shadowed scope.uv run pytest tests/ -vgreen including the perps contract/drift tests added by upstream issues (the docs/version edits must not red them).Dependencies
specs/perps_openapi.yaml/specs/perps_asyncapi.yaml, and the contract-test harness parameterization this issue's tests rely on).