Perps (margin) trading¶
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.
Client construction¶
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).
Environment variables (from_env)¶
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 |
Resource families¶
| 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.
Value types & timestamps¶
- 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.
Funding mechanics¶
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.
Risk & balance introspection¶
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).
WebSocket streaming¶
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.
Self-Clearing-Member "Klear" API¶
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).
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).
Perps over FIX¶
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.