kalshi-client
v1.0.0
Published
Production-grade, fully-typed TypeScript SDK for the Kalshi Predictions API (REST + WebSocket) — RSA-PSS request signing, typed resources, automatic retries, rate-limit awareness, and a reconnecting WebSocket client with sequence-gap recovery.
Maintainers
Readme
kalshi-client
Production-grade, fully-typed TypeScript SDK for the Kalshi Predictions API — REST + WebSocket, RSA-PSS request signing, typed resources for every endpoint group, automatic retries with jittered backoff, client-side rate limiting, and a self-healing WebSocket client with sequence-gap recovery.
Not officially affiliated with or endorsed by Kalshi Inc.
Features
- Full REST coverage — Exchange, Markets, Events, Orders (V2 + legacy), Order Groups, Portfolio, Communications (RFQ), API Keys, Search
- Typed WebSocket client — all public/private channels, auto-reconnect with jittered exponential backoff, automatic re-subscription, and sequence-gap detection/recovery for
orderbook_deltaandorder_group_updates - RSA-PSS request signing built in — no need to hand-roll the crypto
- Automatic retries on 429/5xx with jittered exponential backoff and
Retry-Afterawareness - Optional client-side rate limiting (token bucket) to proactively stay under your tier's limits
- Cursor pagination helpers —
client.markets.listAll()style async generators for every list endpoint - Dual ESM + CJS build with full type declarations, zero runtime dependencies besides
ws - Strict TypeScript throughout,
noUncheckedIndexedAccess, noanyin the public surface - Zero-cost tree-shaking —
sideEffects: false, per-resource classes
Install
npm install kalshi-clientRequires Node.js >= 18.
Quick start
import { KalshiClient } from 'kalshi-client';
// Public market data needs no credentials.
const client = new KalshiClient({ environment: 'demo' });
const { markets } = await client.markets.list({ status: 'open', limit: 10 });
console.log(markets.map((m) => m.ticker));Authenticated usage
Generate an API key under Account & security → API Keys in the Kalshi UI (production or demo). You'll get an API Key ID and a downloadable .key file (PEM-encoded RSA private key).
import { KalshiClient } from 'kalshi-client';
const client = new KalshiClient({
environment: 'demo', // or 'production'
apiKeyId: process.env.KALSHI_API_KEY_ID!,
privateKeyPath: process.env.KALSHI_PRIVATE_KEY_PATH!, // or privateKeyPem
});
const balance = await client.portfolio.getBalance();
console.log(`Balance: $${(balance.balance / 100).toFixed(2)}`);Or build straight from environment variables (KALSHI_ENV, KALSHI_API_KEY_ID, KALSHI_PRIVATE_KEY / KALSHI_PRIVATE_KEY_PATH):
const client = KalshiClient.fromEnv();Placing an order (V2)
import { randomUUID } from 'node:crypto';
const { order } = await client.orders.create({
ticker: 'HIGHNY-24JAN01-T60',
client_order_id: randomUUID(), // required idempotency key
side: 'bid', // 'bid' | 'ask'
count: '10.00',
price: '0.5600',
time_in_force: 'good_till_canceled',
self_trade_prevention_type: 'taker_at_cross',
});
await client.orders.cancel(order.order_id);Pagination
Every list endpoint has a raw cursor method (.list()) and a lazy-iterating convenience method (.listAll() / .listPositions() / etc.) that walks every page for you:
for await (const market of client.markets.listAll({ status: 'open' })) {
console.log(market.ticker);
}WebSocket streaming
const ws = client.createWebSocket();
ws.on('orderbook_snapshot', (msg) => console.log('snapshot', msg.msg));
ws.on('orderbook_delta', (msg) => console.log('delta', msg.msg));
ws.on('sequence_gap', (channel, expected, received) => {
console.warn(`gap on ${channel}: expected ${expected}, got ${received}`);
// The client automatically requests a fresh snapshot to resync.
});
ws.on('reconnecting', (attempt, delayMs) => console.log(`reconnecting #${attempt} in ${delayMs}ms`));
await ws.connect();
await ws.subscribe({
channels: ['orderbook_delta', 'ticker'],
market_tickers: ['HIGHNY-24JAN01-T60'],
});The socket handles reconnection and re-subscription automatically. All order mutation (create/cancel/amend) stays on REST — the WebSocket is read-only, matching Kalshi's API design.
Available channels
| Channel | Access | Notes |
|---|---|---|
| orderbook_delta | private | sequenced; snapshot + deltas |
| ticker | public | price/volume/OI updates |
| trade | public | public trade prints |
| fill | private | your own fills |
| market_positions | private | position updates |
| market_lifecycle_v2 | public | market open/close/settle events |
| multivariate_market_lifecycle | public | multivariate event lifecycle |
| multivariate | public | multivariate market updates |
| communications | private | RFQ/quote activity |
| order_group_updates | private | sequenced; order group state |
| user_orders | private | your order lifecycle |
Error handling
import { KalshiApiError, KalshiTimeoutError } from 'kalshi-client';
try {
await client.orders.create({ /* ... */ });
} catch (err) {
if (err instanceof KalshiApiError) {
console.error(err.status, err.code, err.message);
if (err.isRateLimited) {
// handled automatically by retry, but you can inspect it
}
} else if (err instanceof KalshiTimeoutError) {
console.error('request timed out');
}
}All SDK errors extend KalshiError, so catch (err) { if (err instanceof KalshiError) ... } catches everything the SDK throws.
Configuration reference
new KalshiClient({
environment: 'production' | 'demo', // default 'production'
apiKeyId: string,
privateKeyPem: string, // PEM contents
privateKeyPath: string, // path to a .key file (mutually exclusive with privateKeyPem)
baseUrl: string, // override REST base URL
wsUrl: string, // override WebSocket base URL
timeoutMs: number, // default 10000
maxRetries: number, // default 3
rateLimitPerSecond: number, // default 0 (disabled)
fetchImpl: typeof fetch, // inject a custom fetch (testing)
onRequest: (info) => void, // observability hook
});Resource map
| Resource | Methods |
|---|---|
| client.exchange | getStatus, getAnnouncements, getSchedule, getSeriesFeeChanges, getUserDataTimestamp, getApiLimits |
| client.markets | list, listAll, get, getOrderbook, getOrderbooks, getTrades, getCandlesticks, getSeries, listSeries |
| client.events | list, listAll, get, getMetadata, getForecastHistory, listMultivariateCollections |
| client.orders | list, listAll, get, create, batchCreate, cancel, batchCancel, amend, decrease, getQueuePosition, getQueuePositions, legacy.create, legacy.batchCreate |
| client.orderGroups | list, get, create, delete, reset, trigger, updateLimit |
| client.portfolio | getBalance, getPositions, listPositions, getFills, listFills, getSettlements, getDeposits, getWithdrawals, getRestingOrderTotalValue, createSubaccount, getSubaccountBalances, transferBetweenSubaccounts, getSubaccountTransfers |
| client.communications | listRfqs, createRfq, getRfq, deleteRfq, listQuotes, createQuote, getQuote, deleteQuote, acceptQuote, confirmQuote, getCommunicationsId |
| client.apiKeys | list, create, generate, delete |
| client.search | query |
See examples/ for runnable scripts, and inline TSDoc on every method (which REST path it hits) in your editor.
Development
npm install
npm run typecheck
npm run lint
npm test
npm run buildLicense
MIT — see LICENSE. This is an independent, community-built SDK and is not officially affiliated with, endorsed by, or maintained by Kalshi Inc. Always verify behavior against the official Kalshi API docs before trading with real funds.
