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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Kalshi.Sdk.WebSocket" Version="2.2.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kalshi.Sdk.WebSocket" Version="2.2.5" />
                    
Directory.Packages.props
<PackageReference Include="Kalshi.Sdk.WebSocket" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Kalshi.Sdk.WebSocket --version 2.2.5
                    
#r "nuget: Kalshi.Sdk.WebSocket, 2.2.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Kalshi.Sdk.WebSocket@2.2.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Kalshi.Sdk.WebSocket&version=2.2.5
                    
Install as a Cake Addin
#tool nuget:?package=Kalshi.Sdk.WebSocket&version=2.2.5
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.5 262 10/29/2025
2.2.4 230 10/29/2025
2.2.3 237 10/29/2025
2.2.2 222 10/28/2025
2.2.0 247 10/28/2025
2.1.2 215 10/28/2025
2.1.1 225 10/28/2025
1.1.0 213 10/26/2025
1.0.0 207 10/26/2025

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.