Kalshi's Perps (perpetual futures / margin) API is a separate exchange on its
own host. The SDK exposes it through standalone clients rather than a namespace on
KalshiClient, because the perps exchange uses a different host and Kalshi
recommends separate API keys for it.
| Surface | Client | Host (prod / demo) | Auth |
|---|---|---|---|
| Perps REST | PerpsClient / AsyncPerpsClient |
external-api.kalshi.com / external-api.demo.kalshi.co (/trade-api/v2) |
RSA-PSS (same signer as KalshiClient) |
| Perps WebSocket | PerpsWebSocket |
external-api-margin-ws.kalshi.com / external-api-margin-ws.demo.kalshi.co (/trade-api/ws/v2/margin) |
RSA-PSS apiKey |
| SCM "Klear" | KlearClient / AsyncKlearClient |
api.klear.kalshi.com / demo-api.kalshi.co (/klear-api/v1) |
Bearer admin_user_id:access_token |
The RSA-PSS signer and the HTTP transport are reused unchanged from the prediction API — only the host, config, and credentials differ.
from kalshi import PerpsClient
# Explicit credentials:
with PerpsClient(
key_id="your-perps-key-id",
private_key_path="~/.kalshi/perps_key.pem",
demo=True,
) as perps:
print(perps.exchange.status())PerpsConfig.production() / PerpsConfig.demo() build the canonical environments;
pass a PerpsConfig for full control (timeouts, retry policy, custom JSON loaders).
See Authentication for how RSA-PSS keys are generated and
Configuration for the config surface (shared with KalshiConfig).
Perps credentials live under a separate KALSHI_PERPS_* namespace so they
never collide with the prediction-API KALSHI_* vars:
| Variable | Purpose |
|---|---|
KALSHI_PERPS_KEY_ID |
perps API key id (omit for an unauthenticated client) |
KALSHI_PERPS_PRIVATE_KEY / KALSHI_PERPS_PRIVATE_KEY_PATH |
RSA private key (PEM string or file) |
KALSHI_PERPS_PRIVATE_KEY_PASSPHRASE |
optional passphrase for an encrypted PEM (a password= kwarg overrides it) |
KALSHI_PERPS_API_BASE_URL |
optional REST base-URL override |
KALSHI_PERPS_WS_BASE_URL |
optional WS base-URL override (keeps the WS feed off production when overriding REST) |
KALSHI_PERPS_DEMO |
"true" routes to the demo exchange |
from kalshi import AsyncPerpsClient
async with AsyncPerpsClient.from_env(demo=True) as perps:
...| Attribute | Endpoints |
|---|---|
exchange |
status(), enabled() (per-member access gate), risk_parameters() |
markets |
list(), get(), orderbook(), candlesticks() |
orders |
create(), get(), list() / list_all(), cancel(), decrease(), amend(), list_fcm() / list_all_fcm() |
order_groups |
list(), get(), create(), delete(), reset(), trigger(), update_limit() |
portfolio |
positions(), fills() / fills_all(), trades() / trades_all() |
margin |
balance(), risk(), notional_risk_limit(), fee_tiers(), api_limits() |
funding |
rate_estimate(), historical_rates(), history() |
transfers |
transfer_instance(), create_subaccount(), transfer_subaccount() |
The margin order side is bid / ask (not the prediction API's yes / no).
Orders create/cancel/decrease/amend are POSTs/DELETEs and are never retried.
- Prices are
DollarDecimal—FixedPointDollarsstrings with up to 6 decimal places, parsed toDecimal(no float on the price path). - Counts are
FixedPointCount— 2-decimal fixed-point with an_fpwire suffix. number/doubleratios (margin thresholds, funding rate) useMultiplierDecimalto avoid binary-float drift.- REST timestamps are RFC3339 (
AwareDatetime); some query/response fields are Unix-secondsint. - WebSocket timestamps are Unix epoch milliseconds on
*_ms-suffixed fields (ts_ms,created_ts_ms,next_funding_time_ms, …) — a real parsing difference from the event-contract WS. - Notional values —
MarginMarket(REST) and the margin ticker WS payload carry optionalvolume_notional_value/volume_24h_notional_value/open_interest_notional_value(DollarDecimal | None);MarginMarketCandlestickcarries the same fields as required (inherent to a settled historical record).MarginMarket.leverage_estimatesmaps notional position sizes ("1000","10000", …) toMultiplierDecimalleverage, orNonewhen margin config / price data is unavailable. - Mark prices (since the v3.21.0 spec sync) —
MarginMarketcarries three optional reference-price objects:settlement_mark_price,liquidation_mark_price, andreference_price, each a nestedTickerPrice(TickerPrice | None).TickerPriceis a new model withprice(DollarDecimal, aFixedPointDollarsUSD string) andts_ms(the source timestamp in epoch milliseconds,int). All three are absent from the specrequiredlist, so they areNonewhen the upstream price is unavailable. - Subaccounts (since the v3.21.0 spec sync) —
MarginPosition(theportfolio.positions()rows) carries a requiredsubaccount(int) identifying which subaccount holds the position. - Portfolio hedging (since the v3.23.0 spec sync) —
MarginPositionandMarginRiskPositioncarry a requiredis_portfolio(bool),Truewhen the position is hedged within a portfolio so its margin is computed at the portfolio level.MarginOrdergained an optionalorder_reason("liquidation"/"take_profit_stop_loss") identifying system-generated orders.
Funding is the perpetual-futures tether: periodic payments between longs and shorts
keep the perp's mark price near its index. The funding resource exposes the
current in-progress estimate, historical applied rates, and the authenticated
user's per-payment history:
est = perps.funding.rate_estimate(ticker="BTC-PERP")
print(est.funding_rate, est.next_funding_time) # in-progress estimate
rows = perps.funding.history(start_date="2026-01-01", end_date="2026-02-01")subscribe_ticker on the WS also carries the live funding_rate and
next_funding_time_ms.
bal = perps.margin.balance(compute_available_balance=True)
for sub in bal.subaccount_balances:
print(sub.subaccount, sub.account_equity, sub.maintenance_margin)
risk = perps.margin.risk()
print(risk.account_leverage, risk.total_maintenance_margin)
for pos in risk.positions:
print(pos.market_ticker, pos.position_leverage, pos.estimated_liquidation_price)perps.margin.api_limits() (GET /account/limits/perps) returns the Perps API
tier limits in the same shape as the prediction API's client.account.limits()
(an AccountApiLimits with usage_tier, read/write token buckets, and the
grants list of ApiUsageLevelGrant).
from kalshi import PerpsWebSocket
from kalshi.perps import PerpsConfig
ws = PerpsWebSocket(auth=perps._auth, config=PerpsConfig.demo())
async with ws.connect() as session:
async for msg in await session.subscribe_ticker(tickers=["BTC-PERP"]):
if msg.msg.funding_rate is not None:
print(msg.msg.funding_rate.rate, msg.msg.funding_rate.next_funding_time_ms)Six data channels — orderbook_delta (snapshot + delta, sequenced), ticker,
trade, fill, user_orders, order_group_updates. The connection, sequence-gap
detection, reconnect, and backpressure machinery are reused from the event-contract
WS stack (see WebSocket); the perps orderbook is bid / ask.
The prediction-only channels (market_positions, multivariate*, communications,
market_lifecycle_v2, cfbenchmarks_value) have no perps counterpart.
The Klear API (settlement balances, obligations, margin reports, withdrawals) is a
third surface with a different auth model — a pre-generated Bearer token passed
as Authorization: Bearer <admin_user_id>:<access_token> on every request. Generate
the token and find your admin user id at https://klearing.kalshi.com (the
"Security" page). It is exposed via KlearClient / AsyncKlearClient:
from kalshi import KlearClient
with KlearClient(admin_user_id="...", access_token="...", demo=True) as klear:
reports = klear.margin.margin_reports(start_date="2026-01-01", end_date="2026-02-01")
bal = klear.margin.settlement_balance()Credentials can also come from the environment via KlearClient.from_env() (reads
KALSHI_KLEAR_ADMIN_USER_ID / KALSHI_KLEAR_ACCESS_TOKEN).
New in spec v3.24.0: klear.margin.active_obligations() returns all
currently-active settlement obligations (the plural sibling of the single-obligation
active_obligation()), and klear.margin.settlement_estimate_by_asset_class()
returns next-settlement estimates keyed by asset class.
Money fields on the Klear margin schemas are integer centicents (1 USD = 10,000 centicents); only the withdrawal amount is a fixed-point dollar string.
klear.margin.withdraw_settlement_balance(amount="500.00") validates the amount as
positive at construction (the single real-money write) before any request is sent.
The Bearer access_token is never logged and is redacted in repr() (only the
non-secret admin_user_id is shown).
The margin product is also reachable over the FIX protocol via MarginFixClient
(from kalshi import MarginFixClient), which reuses the shared FIX core with the
KALSHI_PERPS_* key and fixed-point-dollar pricing. Margin supports the
order-entry, drop-copy, and market-data sessions (no RFQ / post-trade). See
FIX protocol.