Kalshi.Sdk.WebSocket
2.2.5
dotnet add package Kalshi.Sdk.WebSocket --version 2.2.5
NuGet\Install-Package Kalshi.Sdk.WebSocket -Version 2.2.5
<PackageReference Include="Kalshi.Sdk.WebSocket" Version="2.2.5" />
<PackageVersion Include="Kalshi.Sdk.WebSocket" Version="2.2.5" />
<PackageReference Include="Kalshi.Sdk.WebSocket" />
paket add Kalshi.Sdk.WebSocket --version 2.2.5
#r "nuget: Kalshi.Sdk.WebSocket, 2.2.5"
#:package Kalshi.Sdk.WebSocket@2.2.5
#addin nuget:?package=Kalshi.Sdk.WebSocket&version=2.2.5
#tool nuget:?package=Kalshi.Sdk.WebSocket&version=2.2.5
Kalshi SDK for F#
F# SDKs for Kalshi
Version: 2.2.5 License: BSD-3-Clause Target Framework: .NET 9.0
Installation
<PackageReference Include="Kalshi.Sdk.RestApi" Version="2.2.5" />
<PackageReference Include="Kalshi.Sdk.WebSocket" Version="2.2.5" />
Setup
REST API
open Kalshi.Sdk.RestApi
open Kalshi.Sdk.RestApi.Client
// Create authenticated client
use client =
Client.createWithPemFile
"your-api-key-id"
"/secure/path/private_key.pem"
None // Uses production URL
// Get balance
let! balanceResponse = client.Portfolio.GetBalanceAsync()
match balanceResponse with
| Success balance -> printfn $"Balance: ${balance.Balance.Dollars}"
| Error err -> printfn $"Error: {err.Message}"
// Create order
let orderRequest = {
Ticker = "FED-23DEC-T300"
Action = Buy
Side = Yes
Count = 10
Type = Limit
YesPrice = Some 55
NoPrice = None
ExpirationTs = None
SellPositionFloor = None
BuyMaxCost = None
}
let! orderResponse = client.Portfolio.CreateOrderAsync(orderRequest)
WebSocket
open Kalshi.Sdk.WebSocket
open Kalshi.Sdk.WebSocket.Types
open Kalshi.Sdk.WebSocket.Client
let config = configWithPemFile
"wss://api.elections.kalshi.com/trade-api/ws/v2"
"your-api-key-id"
"/secure/path/private_key.pem"
let handlers = {
defaultHandlers with
OnTicker = fun ticker -> task {
printfn $"{ticker.Msg.Ticker.Value}: {ticker.Msg.Price}¢"
}
}
use client = new KalshiWebSocketClient(config, handlers)
do! client.ConnectAndListenAsync(cancellationToken)
do! client.SubscribeAsync([Ticker], marketTicker = Some (MarketTicker "FED-23DEC-T300"))
Authentication
Both SDKs require RSA-PSS authentication. Generate credentials at https://kalshi.com/account/profile
REST API Credential Sources
// PEM file
let client = Client.createWithPemFile "key-id" "/path/to/key.pem" None
// PEM string
let client = Client.createWithPemString "key-id" pemContent None
// Environment variables (KALSHI_API_KEY_ID, KALSHI_PEM_FILE)
let client = Client.createWithEnvironment None None None
// Custom provider
type MyProvider() =
interface ICredentialProvider with
member _.GetCredentials() = // your logic
let client = Client.createWithProvider myProvider None
WebSocket Credential Sources
// PEM file
let config = configWithPemFile "wss://..." "key-id" "/path/to/key.pem"
// PEM string
let config = configWithPemString "wss://..." "key-id" pemContent
// Environment variables (reads KALSHI_API_KEY_ID, KALSHI_PEM_FILE)
let config = defaultConfig "wss://..."
REST API Reference
Client Structure
type KalshiClient = {
Portfolio: PortfolioClient
Markets: MarketsClient
Events: EventsClient
Series: SeriesClient
Exchange: ExchangeClient
Communications: CommunicationsClient
ApiKeys: ApiKeysClient
Search: SearchClient
Collections: CollectionsClient
Milestones: MilestonesClient
LiveData: LiveDataClient
Fcm: FcmClient
StructuredTargets: StructuredTargetsClient
IncentivePrograms: IncentiveProgramsClient
}
Portfolio Operations (20 endpoints)
// Balance & Positions
GetBalanceAsync() : Task<ApiResponse<GetBalanceResponse>>
GetPositionsAsync(?pagination) : Task<ApiResponse<GetPositionsResponse>>
GetEventPositionsAsync(?pagination) : Task<ApiResponse<GetEventPositionsResponse>>
GetPortfolioRestingOrderTotalValueAsync() : Task<ApiResponse<GetPortfolioRestingOrderTotalValueResponse>>
// Orders
CreateOrderAsync(request: CreateOrderRequest) : Task<ApiResponse<CreateOrderResponse>>
GetOrderAsync(orderId: Guid) : Task<ApiResponse<GetOrderResponse>>
GetOrdersAsync(?pagination) : Task<ApiResponse<GetOrdersResponse>>
CancelOrderAsync(orderId: Guid) : Task<ApiResponse<CancelOrderResponse>>
DecreaseOrderAsync(orderId: Guid, request: DecreaseOrderRequest) : Task<ApiResponse<DecreaseOrderResponse>>
// Batch Operations
BatchCreateOrdersAsync(requests: CreateOrderRequest list) : Task<ApiResponse<BatchCreateOrdersResponse>>
BatchCancelOrdersAsync(orderIds: Guid list) : Task<ApiResponse<BatchCancelOrdersResponse>>
// Order Groups
CreateOrderGroupAsync(request: CreateOrderGroupRequest) : Task<ApiResponse<CreateOrderGroupResponse>>
GetOrderGroupsAsync(?pagination) : Task<ApiResponse<GetOrderGroupsResponse>>
GetOrderGroupAsync(orderGroupId: Guid) : Task<ApiResponse<GetOrderGroupResponse>>
ResetOrderGroupAsync(orderGroupId: Guid) : Task<ApiResponse<EmptyResponse>>
// Queue & Fills
GetOrderQueuePositionAsync(orderId: Guid) : Task<ApiResponse<GetOrderQueuePositionResponse>>
GetOrderQueuePositionsAsync(orderIds: string) : Task<ApiResponse<GetOrderQueuePositionsResponse>>
GetFillsAsync(?pagination) : Task<ApiResponse<GetFillsResponse>>
// Settlements & Transfers
GetPortfolioSettlementsAsync() : Task<ApiResponse<GetPortfolioSettlementsResponse>>
CreateDepositAsync(amount: int64) : Task<ApiResponse<CreateDepositResponse>>
Markets (6 endpoints)
GetMarketAsync(ticker: string) : Task<ApiResponse<GetMarketResponse>>
GetMarketsAsync(?pagination) : Task<ApiResponse<GetMarketsResponse>>
GetMarketOrderbookAsync(ticker: string, ?depth: int) : Task<ApiResponse<GetMarketOrderbookResponse>>
GetTradesAsync(ticker: string, ?pagination) : Task<ApiResponse<GetTradesResponse>>
GetMarketCandlesticksAsync(seriesTicker: string, ticker: string, ?period: string) : Task<ApiResponse<GetMarketCandlesticksResponse>>
GetMarketHistoryAsync(ticker: string) : Task<ApiResponse<obj>> // No schema available
Events (3 endpoints)
GetEventAsync(eventTicker: string) : Task<ApiResponse<GetEventResponse>>
GetEventsAsync(?pagination) : Task<ApiResponse<GetEventsResponse>>
GetEventMetadataAsync(eventTicker: string) : Task<ApiResponse<GetEventMetadataResponse>>
GetEventCandlesticksAsync(seriesTicker: string, ticker: string, ?period: string) : Task<ApiResponse<GetEventCandlesticksResponse>>
GetEventForecastPercentilesHistoryAsync(seriesTicker: string, ticker: string) : Task<ApiResponse<GetEventForecastPercentilesHistoryResponse>>
Series (2 endpoints)
GetSeriesAsync(?pagination) : Task<ApiResponse<GetSeriesResponse>>
GetSeriesByIdAsync(seriesTicker: string) : Task<ApiResponse<GetSeriesResponse>>
Exchange (4 endpoints)
GetExchangeStatusAsync() : Task<ApiResponse<GetExchangeStatusResponse>>
GetExchangeAnnouncementsAsync() : Task<ApiResponse<GetExchangeAnnouncementsResponse>>
GetSeriesFeeChangesAsync(?seriesTicker: string, ?showHistorical: bool) : Task<ApiResponse<GetSeriesFeeChangesResponse>>
GetExchangeScheduleAsync() : Task<ApiResponse<GetExchangeScheduleResponse>>
GetUserDataTimestampAsync() : Task<ApiResponse<GetUserDataTimestampResponse>>
Communications (8 endpoints)
// RFQs
CreateRfqAsync(request: CreateRfqRequest) : Task<ApiResponse<CreateRfqResponse>>
BatchCreateRfqsAsync(requests: CreateRfqRequest list) : Task<ApiResponse<BatchCreateRfqsResponse>>
GetRfqAsync(rfqId: Guid) : Task<ApiResponse<GetRfqResponse>>
GetRfqsAsync(?pagination) : Task<ApiResponse<GetRfqsResponse>>
CancelRfqAsync(rfqId: Guid) : Task<ApiResponse<CancelRfqResponse>>
// Quotes
GetQuoteAsync(quoteId: Guid) : Task<ApiResponse<GetQuoteResponse>>
GetQuotesAsync(?pagination) : Task<ApiResponse<GetQuotesResponse>>
AcceptQuoteAsync(quoteId: Guid) : Task<ApiResponse<AcceptQuoteResponse>>
API Keys (4 endpoints)
GetApiKeysAsync() : Task<ApiResponse<GetApiKeysResponse>>
CreateApiKeyAsync(request: CreateApiKeyRequest) : Task<ApiResponse<CreateApiKeyResponse>>
GenerateApiKeyAsync() : Task<ApiResponse<GenerateApiKeyResponse>>
DeleteApiKeyAsync(keyId: string) : Task<ApiResponse<EmptyResponse>>
Search (2 endpoints)
GetTagsForSeriesCategoriesAsync() : Task<ApiResponse<GetTagsForSeriesCategoriesResponse>>
GetFiltersForSportsAsync() : Task<ApiResponse<GetFiltersBySportsResponse>>
Collections (5 endpoints)
GetMultivariateEventCollectionsAsync(?pagination) : Task<ApiResponse<GetMultivariateEventCollectionsResponse>>
GetMultivariateEventCollectionAsync(collectionTicker: string) : Task<ApiResponse<GetMultivariateEventCollectionResponse>>
GetTickersForMveLegsAsync(request: GetTickersForMveLegsRequest) : Task<ApiResponse<GetTickersForMveLegsResponse>>
GetOpenOrdersForMveLegsAsync(request: GetOpenOrdersForMveLegsRequest) : Task<ApiResponse<GetOpenOrdersForMveLegsResponse>>
GetPositionsForMveLegsAsync(request: GetPositionsForMveLegsRequest) : Task<ApiResponse<GetPositionsForMveLegsResponse>>
Milestones (2 endpoints)
GetMilestonesAsync(?pagination) : Task<ApiResponse<GetMilestonesResponse>>
GetMilestoneAsync(milestoneId: string) : Task<ApiResponse<GetMilestoneResponse>>
Live Data (2 endpoints)
GetLiveDataAsync(dataType: string, milestoneId: string) : Task<ApiResponse<GetLiveDataResponse>>
GetLiveDatasAsync() : Task<ApiResponse<GetLiveDatasResponse>>
Structured Targets (2 endpoints)
GetStructuredTargetsAsync(?pagination) : Task<ApiResponse<GetStructuredTargetsResponse>>
GetStructuredTargetAsync(structuredTargetId: string) : Task<ApiResponse<GetStructuredTargetResponse>>
Incentive Programs (1 endpoint)
GetIncentiveProgramsAsync() : Task<ApiResponse<GetIncentiveProgramsResponse>>
FCM (2 endpoints)
GetFcmOrdersAsync() : Task<ApiResponse<obj>> // No public schema
GetFcmPositionsAsync() : Task<ApiResponse<obj>> // No public schema
Response Type
type ApiResponse<'T> =
| Success of 'T
| Error of Error
type Error = {
Code: string
Message: string
}
Pagination
type PaginationParams = {
Limit: int option
Cursor: string option
}
// Async sequence support
let allMarkets = client.Markets.GetMarketsAsyncSeq(?limit = Some 100)
for market in allMarkets do
printfn $"{market.Ticker}"
WebSocket Reference
Channels
| Channel | Description | Message Type |
|---|---|---|
OrderbookDelta |
Real-time orderbook updates | OrderbookDeltaMsg |
Ticker |
Market price, volume, open interest | TickerMsg |
Trade |
Public trade executions | TradeMsg |
Fill |
Your order fills | FillMsg |
MarketPositions |
Your position updates | MarketPositionMsg |
MarketLifecycleV2 |
Market lifecycle events | MarketLifecycleV2Msg |
Multivariate |
Multivariate event data | MultivariateMsg |
Configuration
type WebSocketClientConfig = {
Uri: Uri
CredentialProvider: ICredentialProvider
ReconnectDelay: TimeSpan // Default: 5 seconds
MaxReconnectAttempts: int option // Default: 10
PingInterval: TimeSpan option // Default: 30 seconds
ReceiveBufferSize: int // Default: 8192 bytes
}
// Default config (uses environment variables for credentials)
let config = defaultConfig "wss://..."
// Custom config
let config = {
Uri = Uri("wss://...")
CredentialProvider = PemFileCredentialProvider("key-id", "key.pem")
ReconnectDelay = TimeSpan.FromSeconds(10.0)
MaxReconnectAttempts = Some 5
PingInterval = Some(TimeSpan.FromSeconds(60.0))
ReceiveBufferSize = 16384
}
Handlers
type ClientHandlers = {
OnConnected: unit -> Task<unit>
OnDisconnected: unit -> Task<unit>
OnError: exn -> Task<unit>
OnOrderbookDelta: OrderbookDeltaMsg -> Task<unit>
OnOrderbookSnapshot: OrderbookSnapshotMsg -> Task<unit>
OnOrderbookError: MarketTicker * string -> Task<unit>
OnTicker: TickerMsg -> Task<unit>
OnTrade: TradeMsg -> Task<unit>
OnFill: FillMsg -> Task<unit>
OnMarketPosition: MarketPositionMsg -> Task<unit>
OnMarketLifecycle: MarketLifecycleV2Msg -> Task<unit>
OnMultivariate: MultivariateMsg -> Task<unit>
}
// Use defaultHandlers and override specific handlers
let handlers = {
defaultHandlers with
OnTicker = fun ticker -> task { (* your logic *) }
OnError = fun ex -> task { eprintfn $"Error: {ex.Message}" }
}
Client Methods
type KalshiWebSocketClient =
new (config: WebSocketClientConfig, handlers: ClientHandlers)
member ConnectAndListenAsync(cancellationToken: CancellationToken) : Task<unit>
member SubscribeAsync(channels: ChannelName list, ?marketTicker: MarketTicker, ?seriesTicker: SeriesTicker) : Task<unit>
member UnsubscribeAsync(channels: ChannelName list, ?marketTicker: MarketTicker, ?seriesTicker: SeriesTicker) : Task<unit>
member DisconnectAsync() : Task<unit>
member GetState() : ClientState
interface IDisposable
Client State
type ConnectionState =
| Disconnected
| Connecting
| Connected
| Reconnecting
| Failed of string
type ClientState = {
State: ConnectionState
ConnectedAt: DateTimeOffset option
ReconnectAttempts: int
}
Orderbook Management
open Kalshi.Sdk.WebSocket.OrderbookManager
// Create manager with error callback
let manager = OrderbookManager(fun (ticker, error) -> task {
eprintfn $"Orderbook error for {ticker.Value}: {error}"
// Re-subscribe or handle error
})
// Subscribe to orderbook
do! manager.SubscribeAsync(client, MarketTicker "FED-23DEC-T300")
// Get current orderbook state
let orderbook = manager.GetOrderbook(MarketTicker "FED-23DEC-T300")
match orderbook with
| Some book ->
printfn $"Bids: {book.Bids.Length}, Asks: {book.Asks.Length}"
| None ->
printfn "No orderbook data yet"
// Unsubscribe
do! manager.UnsubscribeAsync(client, MarketTicker "FED-23DEC-T300")
Type System
Core Discriminated Unions
// Order & Market Types
type OrderAction = Buy | Sell
type MarketSide = Yes | No
type OrderType = Limit | Market
type OrderStatus = Resting | Canceled | Executed | Pending
type MarketType = Binary | Scalar
type MarketStatus = Initialized | Active | Closed | Settled | Determined
type SettlementStatus = All | Unsettled | Settled
// Fee & Strike Types
type FeeType = Quadratic | QuadraticWithMakerFees | Flat
type StrikeType = Greater | GreaterOrEqual | Less | LessOrEqual | Between | Functional | Custom | Structured
// RFQ/Quote Types
type RfqStatus = Open | Closed
type QuoteStatus = QuoteOpen | Accepted | Confirmed | Executed | Cancelled
Price Representation
// Standard prices (cents)
type Price = {
Cents: int // Price in cents (e.g., 50 = $0.50)
Dollars: string // Price as string (e.g., "0.5000")
}
// Large monetary values (centi-cents = 1/10,000th dollar)
type Money = {
CentiCents: int64 // 10000 = $1.00
Dollars: string // "1.0000"
}
Core Request Types
// Create order
type CreateOrderRequest = {
Ticker: string
ClientOrderId: string option
Side: MarketSide
Action: OrderAction
Type: OrderType
YesPrice: int option
NoPrice: int option
Count: int
ExpirationTs: int64 option
SellPositionFloor: int option
BuyMaxCost: int64 option
CancelOrderOnPause: bool option
}
// Decrease order
type DecreaseOrderRequest = {
ReduceBy: int option
ReduceTo: int option
}
// Create RFQ
type CreateRfqRequest = {
MarketTicker: string option
Contracts: int option
TargetCostCentiCents: int64 option
RestRemainder: bool option
MveCollectionTicker: string option
MveSelectedLegs: TickerPair list option
}
// Pagination
type PaginationParams = {
Limit: int option
Cursor: string option
}
Core Response Types
// Order
type Order = {
OrderId: Guid
UserId: string
ClientOrderId: string option
Ticker: string
Side: MarketSide
Action: OrderAction
Type: OrderType
Status: OrderStatus
YesPrice: int
YesPriceDollars: string option
NoPrice: int
NoPriceDollars: string option
FillCount: int
RemainingCount: int
InitialCount: int
TakerFees: int64
TakerFeesDollars: string option
MakerFees: int64
MakerFeesDollars: string option
TakerFillCost: int64
TakerFillCostDollars: string option
MakerFillCost: int64
MakerFillCostDollars: string option
QueuePosition: int option
ExpirationTime: DateTimeOffset option
CreatedTime: DateTimeOffset
LastUpdateTime: DateTimeOffset option
OrderGroupId: Guid option
CancelOrderOnPause: bool option
}
// Market
type Market = {
Ticker: string
EventTicker: string
MarketType: MarketType
Title: string
Subtitle: string option
YesSubTitle: string option
NoSubTitle: string option
OpenTime: DateTimeOffset
CloseTime: DateTimeOffset
ExpirationTime: DateTimeOffset option
LatestExpirationTime: DateTimeOffset option
Status: MarketStatus
Result: string option
YesBid: int option
YesBidDollars: string option
YesAsk: int option
YesAskDollars: string option
NoBid: int option
NoBidDollars: string option
NoAsk: int option
NoAskDollars: string option
LastPrice: int option
LastPriceDollars: string option
PreviousYesBid: int option
PreviousYesBidDollars: string option
PreviousYesAsk: int option
PreviousYesAskDollars: string option
PreviousPrice: int option
PreviousPriceDollars: string option
Volume: int64
Volume24h: int64
OpenInterest: int64
NotionalValue: int64 option
NotionalValueDollars: string option
Liquidity: int64 option
LiquidityDollars: string option
TickSize: int
StrikeType: StrikeType option
CustomStrike: CustomStrike option
FloorStrike: string option
CapStrike: string option
ExpirationValue: string option
SettlementValue: string option
CanCloseEarly: bool
PriceLevelStructure: string option
PriceRanges: PriceRange list option
}
// Market position
type MarketPosition = {
Ticker: string
Position: int
TotalTraded: int64
TotalTradedDollars: string option
MarketExposure: int64
MarketExposureDollars: string option
RealizedPnl: int64
RealizedPnlDollars: string option
FeesPaid: int64
FeesPaidDollars: string option
RestingOrdersCount: int
LastUpdatedTs: int64 option
}
// Fill
type Fill = {
TradeId: Guid
OrderId: Guid
Ticker: string
Side: MarketSide
Action: OrderAction
YesPrice: int
YesPriceDollars: string option
NoPrice: int
NoPriceDollars: string option
Count: int
TakerFees: int64
TakerFeesDollars: string option
CreatedTime: DateTimeOffset
IsTaker: bool
}
// Public trade
type Trade = {
TradeId: Guid
Ticker: string
YesPrice: int
YesPriceDollars: string option
NoPrice: int
NoPriceDollars: string option
Count: int
TakerSide: MarketSide
CreatedTime: DateTimeOffset
}
// RFQ
type Rfq = {
Id: string
CreatorId: string
MarketTicker: string
EventTicker: string option
Status: RfqStatus
Contracts: int option
TargetCostCentiCents: int64 option
TargetCostDollars: string option
RestRemainder: bool option
MveCollectionTicker: string option
MveSelectedLegs: MveSelectedLeg list option
CreatedTs: DateTimeOffset
CancellationReason: string option
CancelledTs: DateTimeOffset option
UpdatedTs: DateTimeOffset option
}
// Quote
type Quote = {
Id: string
RfqId: string
CreatorId: string
RfqCreatorId: string
MarketTicker: string
EventTicker: string option
Contracts: int
YesBid: int
NoBid: int
YesBidDollars: string
NoBidDollars: string
Status: QuoteStatus
YesContractsOffered: int option
NoContractsOffered: int option
RfqTargetCost: int64 option
RfqTargetCostDollars: string option
CreatedTs: DateTimeOffset
AcceptedSide: MarketSide option
AcceptedTs: DateTimeOffset option
ConfirmedTs: DateTimeOffset option
ExecutedTs: DateTimeOffset option
CancelledTs: DateTimeOffset option
UpdatedTs: DateTimeOffset option
}
WebSocket Message Types
// Ticker
type TickerMsg = {
MarketTicker: MarketTicker
Price: int
YesBid: int
YesAsk: int
PriceDollars: string option
YesBidDollars: string option
NoBidDollars: string option
Volume: int
OpenInterest: int
DollarVolume: int
DollarOpenInterest: int
Ts: int64
}
// Orderbook delta
type OrderbookDeltaMsg = {
MarketTicker: MarketTicker
Price: int
PriceDollars: string option
Delta: int
Side: MarketSide
ClientOrderId: string option
}
// Orderbook snapshot
type OrderbookSnapshotMsg = {
MarketTicker: MarketTicker
Yes: (int * int) list option // [(price, quantity)]
YesDollars: (string * int) list option
No: (int * int) list option
NoDollars: (string * int) list option
}
// Fill
type FillMsg = {
TradeId: Guid
OrderId: Guid
MarketTicker: MarketTicker
IsTaker: bool
Side: MarketSide
YesPrice: int
YesPriceDollars: string option
NoPrice: int
NoPriceDollars: string option
Count: int
Action: OrderAction
Ts: int64
ClientOrderId: string option
PostPosition: int option
PurchasedSide: MarketSide option
}
// Trade
type TradeMsg = {
MarketTicker: MarketTicker
YesPrice: int
YesPriceDollars: string option
NoPrice: int
NoPriceDollars: string option
Count: int
TakerSide: MarketSide
Ts: int64
}
// Market position (values in centi-cents)
type MarketPositionMsg = {
UserId: string
MarketTicker: MarketTicker
Position: int
PositionCost: int64 // centi-cents
RealizedPnl: int64 // centi-cents
FeesPaid: int64 // centi-cents
Volume: int
}
with
member PositionCostDollars: decimal
member RealizedPnlDollars: decimal
member FeesPaidDollars: decimal
// Market lifecycle
type MarketLifecycleV2Msg = {
EventType: MarketLifecycleEventType
MarketTicker: MarketTicker
OpenTs: int64 option
CloseTs: int64 option
Result: string option
DeterminationTs: int64 option
SettledTs: int64 option
IsDeactivated: bool option
AdditionalMetadata: MarketAdditionalMetadata option
}
type MarketLifecycleEventType =
| Created
| Activated
| Deactivated
| CloseDateUpdated
| Determined
| Settled
Error Handling
REST API Errors
match response with
| Success data ->
// Handle success
| Error err ->
match err.Code with
| "UNAUTHORIZED" -> // Invalid credentials
| "RATE_LIMIT_EXCEEDED" -> // Slow down
| "INSUFFICIENT_BALANCE" -> // Not enough funds
| "ORDER_NOT_FOUND" -> // Invalid order ID
| _ -> // Other errors
WebSocket Errors
let handlers = {
defaultHandlers with
OnError = fun ex -> task {
match ex with
| :? InvalidOperationException ->
// Authentication failure
| :? WebSocketException ->
// Connection issue
| _ ->
// Other errors
}
OnOrderbookError = fun (ticker, error) -> task {
eprintfn $"Orderbook {ticker.Value}: {error}"
// Re-subscribe to recover
do! client.SubscribeAsync([OrderbookDelta], marketTicker = Some ticker)
}
}
Common Error Codes
| Code | Description | Solution |
|---|---|---|
UNAUTHORIZED |
Invalid credentials | Check API key/PEM file |
AUTHENTICATION_ERROR |
Signature generation failed | Verify PEM format, check clock sync |
RATE_LIMIT_EXCEEDED |
Too many requests | Implement backoff |
INSUFFICIENT_BALANCE |
Not enough funds | Deposit more funds |
ORDER_NOT_FOUND |
Invalid order ID | Verify order exists |
MARKET_NOT_FOUND |
Invalid ticker | Check ticker spelling |
INVALID_REQUEST |
Bad request parameters | Validate input |
Building & Testing
# Build
dotnet build
# Test
dotnet test
# Package REST API
dotnet pack src/Kalshi.Sdk.RestApi/Kalshi.Sdk.RestApi.fsproj -c Release -o ./nupkg
# Package WebSocket
dotnet pack src/Kalshi.Sdk.WebSocket/Kalshi.Sdk.WebSocket.fsproj -c Release -o ./nupkg
Local Development Setup
Create nuget.config in your project:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="KalshiSDK" value="../KalshiSdk/nupkg" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
Dependencies
Both SDKs:
- FSharp.Core >= 8.0.400
- System.Text.Json >= 9.0.0
- FSharp.SystemTextJson >= 1.4.36
WebSocket SDK:
- System.Net.WebSockets.Client >= 4.3.2
REST API SDK:
- System.Net.Http (included in .NET 9.0)
Remaining untyped endpoints (no OpenAPI schema):
GetMarketHistoryAsync()GetFcmOrdersAsync()GetFcmPositionsAsync()
See CHANGELOG.md for complete details.
Project Structure
KalshiSdk/
├── src/
│ ├── Kalshi.Sdk.RestApi/
│ │ ├── Types.fs # Domain types
│ │ ├── Serialization.fs # JSON converters
│ │ ├── Auth.fs # RSA-PSS authentication
│ │ ├── HttpClient.fs # HTTP client with auth
│ │ ├── Pagination.fs # Cursor-based pagination
│ │ ├── Portfolio.fs # Orders, positions, fills
│ │ ├── Markets.fs # Market data, orderbooks
│ │ ├── Events.fs # Event data
│ │ ├── Series.fs # Series data
│ │ ├── Exchange.fs # Exchange status
│ │ ├── Communications.fs # RFQs, quotes
│ │ ├── ApiClients.fs # API keys, search, etc.
│ │ └── Client.fs # Main client facade
│ └── Kalshi.Sdk.WebSocket/
│ ├── Types.fs # Type definitions
│ ├── Serialization.fs # JSON converters
│ ├── Client.fs # WebSocket client
│ ├── OrderbookManager.fs # Orderbook state
│ └── Examples.fs # Usage examples
└── tests/
└── Kalshi.Sdk.WebSocket.Tests/
License
BSD-3-Clause
This SDK is generated from the official Kalshi OpenAPI and AsyncAPI specifications.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- FSharp.Core (>= 9.0.100)
- FSharp.SystemTextJson (>= 1.4.36)
- System.Text.Json (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v2.2.5 - Bug Fixes:
- REST API endpoint fixes and improvements
- Serialization and type system enhancements
- Improved code deduplication
- Auth code now embedded (no separate dependency)
See CHANGELOG.md for complete details.