KalshiSharp 1.3.0

dotnet add package KalshiSharp --version 1.3.0
                    
NuGet\Install-Package KalshiSharp -Version 1.3.0
                    
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="KalshiSharp" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="KalshiSharp" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="KalshiSharp" />
                    
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 KalshiSharp --version 1.3.0
                    
#r "nuget: KalshiSharp, 1.3.0"
                    
#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 KalshiSharp@1.3.0
                    
#: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=KalshiSharp&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=KalshiSharp&version=1.3.0
                    
Install as a Cake Tool

KalshiSharp

NuGet NuGet downloads CI License .NET 8 GitHub stars

A production-grade .NET 8 SDK for the Kalshi prediction market API.

KalshiSharp gives C# and .NET applications typed access to Kalshi REST endpoints, WebSocket streams, RSA-PSS authentication, rate limiting, dependency injection, and current fixed-point market contracts without hand-rolling HTTP signing or JSON models.

KalshiSharp is the first reference target for KalshiCore, the shared contract and conformance framework for the Kalshi SDK family. See KalshiCore conformance for local validation details.

Features

  • Core API Coverage: REST clients for exchange, markets, events, orders, portfolio, users, and historical data
  • Real-time WebSocket: Order book, ticker, trade, fill, position, and user-order subscriptions with auto-reconnect
  • Async-First: All operations are async/await with proper cancellation support
  • Thread-Safe: Safe for concurrent use from multiple threads
  • Strongly Typed: Complete type coverage with nullable reference types enabled
  • Automatic Signing: RSA-PSS request signing handled transparently
  • Resilience: Built-in retry with exponential backoff, rate limiting, and circuit breaker
  • Observability: OpenTelemetry tracing and metrics integration
  • Dependency Injection: First-class support for IServiceCollection

Why KalshiSharp?

  • Built for Kalshi's current API contracts: fixed-point prices, shard-aware routing, V2 orders, historical data, and fast-moving market metadata.
  • Production-oriented defaults: RSA-PSS signing, retry, rate limiting, cancellation support, typed exceptions, and DI-friendly client construction.
  • Real-time ready: WebSocket subscriptions for order books, ticker, trades, fills, positions, user orders, lifecycle, and market updates.
  • Package-first workflow: published on NuGet with CI, coverage, vulnerability audit, pack, and package-smoke verification.

API Coverage

Area Coverage
Exchange and account metadata Status, schedule, rate-limit discovery
Markets and events Listing, details, order books, candlesticks, dynamic price grids, localized text
Orders Legacy order reads plus current V2 create, amend, decrease, cancel, batch, and order groups
Portfolio Balance, positions, fills, settlements, subaccounts, transfers, resting value, target allocation
Historical data Cutoff, markets, candlesticks, trades, fills, orders, positions
Reference data Series, structured targets, milestones, multivariate collections, live weather data
Communications and admin Communications identity, RFQs, quotes, and API-key administration
WebSocket Public market streams, private account streams, reconnect, replay, and typed messages

See API coverage for a fuller map and visibility tracking for repository discovery metrics.

Installation

dotnet add package KalshiSharp

Quick Start

Basic Usage

using KalshiSharp.Configuration;
using KalshiSharp.Rest;

// Create a client with your API credentials
using var client = new KalshiClient(new KalshiClientOptions
{
    ApiKey = "your-api-key",
    ApiSecret = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", // RSA private key in PEM format
    Environment = KalshiEnvironment.Demo // or Production
});

var status = await client.Exchange.GetStatusAsync();
Console.WriteLine($"Trading Active: {status.TradingActive}");

With Dependency Injection (ASP.NET Core)

For applications using dependency injection:

using KalshiSharp.Configuration;
using KalshiSharp.DependencyInjection;

// In Program.cs or Startup.cs
services.AddKalshiClient(options =>
{
    options.ApiKey = configuration["Kalshi:ApiKey"]!;
    options.ApiSecret = configuration["Kalshi:ApiSecret"]!;
    options.Environment = KalshiEnvironment.Production;
});

// Inject IKalshiClient directly
public class MyService(IKalshiClient client)
{
    public async Task DoSomethingAsync()
    {
        var markets = await client.Markets.ListMarketsAsync();
    }
}

Localized Market Responses

Kalshi can return localized market text and rules when a preferred language is configured. Use one BCP 47 language tag. Spanish and Portuguese, including regional variants such as es-MX and pt-BR, are currently documented. Valid but unsupported tags, or an omitted value, fall back to English.

using var client = new KalshiClient(new KalshiClientOptions
{
    ApiKey = "your-api-key",
    ApiSecret = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
    Environment = KalshiEnvironment.Production,
    PreferredLanguage = "es-MX"
});

Get Exchange Schedule

var schedule = await client.Exchange.GetScheduleAsync();
Console.WriteLine($"Standard hours entries: {schedule.Schedule.StandardHours.Count}");
Console.WriteLine($"Maintenance windows: {schedule.Schedule.MaintenanceWindows.Count}");

List Markets with Pagination

using KalshiSharp.Models.Enums;
using KalshiSharp.Models.Requests;

var query = new MarketQuery
{
    Limit = 10,
    Status = MarketStatus.Active
};

var page1 = await client.Markets.ListMarketsAsync(query);

foreach (var market in page1.Items)
{
    Console.WriteLine($"{market.Ticker}: {market.Title}");
    Console.WriteLine($"  Yes: ${market.YesBidDollars} / ${market.YesAskDollars}");
}

// Fetch next page
if (page1.HasMore)
{
    var page2 = await client.Markets.ListMarketsAsync(query with { Cursor = page1.Cursor });
}

Get Order Book

var orderBook = await client.Markets.GetOrderBookAsync("TICKER-ABC");

Console.WriteLine($"Yes levels: {orderBook.OrderbookFp.YesDollars.Count}");
Console.WriteLine($"No levels: {orderBook.OrderbookFp.NoDollars.Count}");

// Each level is [fixed-point dollar price, fixed-point quantity]
foreach (var level in orderBook.OrderbookFp.YesDollars)
{
    Console.WriteLine($"  ${level[0]}: {level[1]} contracts");
}

Discover Series

Series access is an optional client capability for discovering recurring contracts.

using KalshiSharp.Models.Requests;

var seriesClient = client.Series
    ?? throw new NotSupportedException("This client does not provide series discovery.");

var series = await seriesClient.ListSeriesAsync(new SeriesQuery
{
    Category = "Climate and Weather",
    Tags = ["Daily temperature"],
    IncludeVolume = true
});

foreach (var item in series.Items)
{
    var discoveryCategories = item.Categories is { Count: > 0 }
        ? string.Join(", ", item.Categories)
        : item.Category;
    Console.WriteLine($"{item.Ticker}: {item.Title} ({discoveryCategories})");
}

Discover Structured Targets and Milestones

Structured targets identify participants, teams, or other subjects, while milestones connect those targets to scheduled events and live data.

using KalshiSharp.Models.Requests;

var targets = client.StructuredTargets
    ?? throw new NotSupportedException("This client does not provide structured-target access.");
var milestones = client.Milestones
    ?? throw new NotSupportedException("This client does not provide milestone access.");

var playerTargets = await targets.ListStructuredTargetsAsync(new StructuredTargetQuery
{
    Type = "basketball_player",
    Competition = "NBA",
    PageSize = 100
});

foreach (var target in playerTargets.Items)
{
    Console.WriteLine($"{target.Name}: {target.Details?.ImageUrl}");
}

var upcoming = await milestones.ListMilestonesAsync(new MilestoneQuery
{
    Limit = 100,
    MinimumStartDate = DateTimeOffset.UtcNow,
    Competition = "Pro Basketball (M)"
});

foreach (var milestone in upcoming.Items)
{
    Console.WriteLine($"{milestone.Title}: {milestone.StartDate:u}");
}

Multivariate Event Collections

Multivariate collections describe combo-market creation rules. Use the collection price grid for newly created markets, then use each market's own PriceRanges after creation.

using KalshiSharp.Models.Requests;

var multivariate = client.Multivariate
    ?? throw new NotSupportedException("This client does not provide multivariate collection access.");

var collections = await multivariate.ListEventCollectionsAsync(new MultivariateEventCollectionQuery
{
    Status = "open",
    SeriesTicker = "KXEXAMPLE",
    Limit = 100
});

foreach (var collection in collections.Items)
{
    Console.WriteLine($"{collection.CollectionTicker}: {collection.PriceLevelStructure}");
    foreach (var range in collection.PriceRanges)
    {
        Console.WriteLine($"  ${range.Start} - ${range.End}: step ${range.Step}");
    }
}

Weather Live Data

Weather live-data access is an optional client capability for city temperature indexes and their published calibration timeline.

using KalshiSharp.Models.Requests;

var liveData = client.LiveData
    ?? throw new NotSupportedException("This client does not provide live-data access.");

var weather = await liveData.GetWeatherIndexAsync("miami", new WeatherIndexQuery
{
    LastSec = 3600,
    Detailed = true
});

foreach (var point in weather.Timeseries)
{
    Console.WriteLine($"{point.T}: {point.V?.ToString() ?? "incomplete"}");
}

var calibrations = await liveData.GetWeatherIndexCalibrationsAsync("miami");
Console.WriteLine($"Calibration records: {calibrations.Calibrations.Count}");

var milestoneData = await liveData.GetLiveDataAsync("milestone-id", new LiveDataQuery
{
    IncludePlayerStats = true
});
Console.WriteLine($"{milestoneData.LiveData.Type}: {milestoneData.LiveData.MilestoneId}");

var eventData = await liveData.GetEventLiveDataAsync("EVENT-TICKER");
Console.WriteLine($"{eventData.LiveData.Type}: {eventData.LiveData.DefaultRange}");

Place and Cancel Orders

using KalshiSharp.Models.Requests;

// Create an order through the current V2 event-order API
var request = new CreateOrderRequestV2
{
    Ticker = "TICKER-ABC",
    Side = OrderBookSide.Bid,
    Count = "10.00",
    Price = "0.4500",
    TimeInForce = EventOrderTimeInForce.GoodTillCanceled,
    SelfTradePreventionType = SelfTradePreventionType.TakerAtCross,
    ClientOrderId = Guid.NewGuid().ToString("N"),
    ExchangeIndex = -1 // auto-route by ticker
};

var ordersV2 = client.OrdersV2
    ?? throw new NotSupportedException("This client does not provide the V2 order capability.");

var order = await ordersV2.CreateOrderAsync(request);
Console.WriteLine($"Order ID: {order.OrderId}, remaining: {order.RemainingCount}");

// Cancel the order
var cancelled = await ordersV2.CancelOrderAsync(order.OrderId, new CancelOrderQueryV2
{
    ExchangeIndex = -1,
    MarketTicker = request.Ticker
});

// Preserve queue priority while changing only the expiration.
var amended = await ordersV2.AmendOrderAsync(order.OrderId, new AmendOrderRequestV2
{
    Ticker = request.Ticker,
    Side = request.Side,
    Price = request.Price,
    Count = order.RemainingCount,
    ExpirationTime = DateTimeOffset.UtcNow.AddDays(1).ToUnixTimeSeconds(),
    ExchangeIndex = request.ExchangeIndex
});

// Explicitly cancel up to 10,000 resting event orders across every shard and subaccount.
// If more than 10,000 match, Kalshi selects the cancelled orders arbitrarily.
// Newly placed matching orders may also be cancelled during the minute after this request.
await ordersV2.CancelAllOrdersAsync();

// Restrict the same operation to one subaccount.
await ordersV2.CancelAllOrdersAsync(subaccount: 2);

Order Groups

var orderGroups = client.OrderGroups
    ?? throw new NotSupportedException("This client does not provide order-group access.");

var group = await orderGroups.CreateOrderGroupAsync(new CreateOrderGroupRequest
{
    ExchangeIndex = 1,
    Subaccount = 0,
    ContractsLimitFp = "25.00"
});

await orderGroups.TriggerOrderGroupAsync(group.OrderGroupId!, new TriggerOrderGroupRequest
{
    ExchangeIndex = 1,
    Subaccount = 0
});

Portfolio Information

// Get balance
var balance = await client.Portfolio.GetBalanceAsync();
Console.WriteLine($"Balance: ${balance.BalanceDollars}");

// List positions
var positions = await client.Portfolio.ListPositionsAsync(new PositionQuery
{
    SettlementStatus = PositionSettlementStatus.Unsettled
});
foreach (var position in positions.Items)
{
    Console.WriteLine($"{position.Ticker}: {position.PositionFp} contracts");
}

// List fills
var fills = await client.Portfolio.ListFillsAsync();

// Filter fills to one or more market tickers.
var selectedFills = await client.Portfolio.ListFillsAsync(new FillQuery
{
    Tickers = ["MARKET-A", "MARKET-B"]
});

// List settlements across all subaccounts, or scope to one subaccount.
var settlements = await client.Portfolio.ListSettlementsAsync(new SettlementQuery
{
    Subaccount = 0,
    Limit = 100
});

// Transfer funds between subaccounts on a specific exchange shard.
await client.Portfolio.TransferBetweenSubaccountsAsync(new CreateSubaccountTransferRequest
{
    ClientTransferId = Guid.NewGuid(),
    FromSubaccount = 0,
    ToSubaccount = 2,
    AmountCents = 5_000,
    ExchangeIndex = 0
});

var transferHistory = await client.Portfolio.ListSubaccountTransfersAsync(new SubaccountTransferQuery
{
    Limit = 100
});

// FCM-only resting order summary
var resting = await client.Portfolio.GetTotalRestingOrderValueAsync();
Console.WriteLine($"Resting order value: {resting.TotalRestingOrderValue} cents");

Historical Data

Historical access is explicit. Existing live methods are never rerouted automatically.

var historical = client.Historical
    ?? throw new NotSupportedException("This client does not provide historical data access.");

var cutoff = await historical.GetCutoffAsync();
Console.WriteLine($"Markets before {cutoff.MarketSettledTs:u} are historical");

var archived = await historical.ListMarketsAsync(new HistoricalMarketQuery
{
    SeriesTicker = "KXHIGHNY",
    Limit = 100
});

Incentive Programs

using KalshiSharp.Models.Enums;
using KalshiSharp.Models.Requests;

var incentives = client.Incentives
    ?? throw new NotSupportedException("This client does not provide incentive-program access.");

var programs = await incentives.ListIncentiveProgramsAsync(new IncentiveProgramQuery
{
    Status = IncentiveProgramStatus.Active,
    Type = IncentiveProgramType.Liquidity,
    Limit = 100
});

foreach (var program in programs.Items)
{
    Console.WriteLine($"{program.MarketTicker}: {program.TargetSizeFp} contracts");
}

The shared endpoint also supports IncentiveProgramType.MarginMakerVolume and IncentiveProgramType.MarginTakerVolume. Margin programs may omit MarketId, MarketTicker, and other event-only fields; MaxRewardPerAccount exposes the optional account reward cap in centi-cents.

Target Balance Allocation

using KalshiSharp.Models.Enums;
using KalshiSharp.Models.Requests;

var currentAllocation = await client.Portfolio.GetTargetBalanceAllocationAsync();

await client.Portfolio.SetTargetBalanceAllocationAsync(new SetTargetBalanceAllocationRequest
{
    Allocations =
    [
        new TargetBalanceAllocationRequest { ExchangeIndex = 0, Percent = 70 },
        new TargetBalanceAllocationRequest { ExchangeIndex = 2, Percent = 30 }
    ],
    RestingMarginReservation = RestingMarginReservation.None
});

Percentages must total 100 unless the allocation list is empty, which disables automatic rebalancing. Omit RestingMarginReservation to keep Kalshi's default sum policy.

API Key Administration

using KalshiSharp.Models.Common;
using KalshiSharp.Models.Requests;

var apiKeys = client.ApiKeys
    ?? throw new NotSupportedException("This client does not provide API-key administration.");

var keys = await apiKeys.ListApiKeysAsync();
foreach (var key in keys.ApiKeys)
{
    Console.WriteLine($"{key.ApiKeyId}: {key.Name}");
}

var generated = await apiKeys.GenerateApiKeyAsync(new GenerateApiKeyRequest
{
    Name = "readonly-reporting",
    Scopes = [ApiKeyScopes.Read]
});

// Store generated.PrivateKey immediately. Kalshi returns it once and the SDK redacts it from ToString().
Console.WriteLine($"Generated API key: {generated.ApiKeyId}");

Communications

using KalshiSharp.Models.Requests;

var communications = client.Communications
    ?? throw new NotSupportedException("This client does not provide communications access.");

var identity = await communications.GetCommunicationsIdAsync();
Console.WriteLine($"Communications ID: {identity.CommunicationsId}");

var rfqs = await communications.ListRfqsAsync(new RfqQuery
{
    MarketTicker = "MARKET-ABC",
    UserFilter = CommunicationsUserFilter.Self
});

var createdRfq = await communications.CreateRfqAsync(new CreateRfqRequest
{
    MarketTicker = "MARKET-ABC",
    ContractsFp = "10.00",
    TargetCostDollars = "5.6000",
    RestRemainder = true
});

var quote = await communications.CreateQuoteAsync(new CreateQuoteRequest
{
    RfqId = createdRfq.Id,
    YesBid = "0.5600",
    NoBid = "0.4400",
    RestRemainder = false,
    PostOnly = true
});

WebSocket Real-Time Updates

using KalshiSharp.Configuration;
using KalshiSharp.WebSockets;
using KalshiSharp.WebSockets.Subscriptions;
using KalshiSharp.Models.WebSocket;

// Create WebSocket client
await using var wsClient = new KalshiWebSocketClient(new KalshiClientOptions
{
    ApiKey = "your-api-key",
    ApiSecret = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
    Environment = KalshiEnvironment.Production
});

// Connect and subscribe
await wsClient.ConnectAsync();
await wsClient.SubscribeAsync(OrderBookSubscription.ForMarkets("TICKER-ABC"));
await wsClient.SubscribeAsync(TradeSubscription.ForMarkets("TICKER-ABC"));

// Process messages
await foreach (var message in wsClient.Messages)
{
    switch (message)
    {
        case OrderBookSnapshot snapshot:
            Console.WriteLine($"Snapshot: {snapshot.Message.MarketTicker}");
            break;
        case OrderBookUpdate update:
            Console.WriteLine($"Delta: ${update.Message.PriceDollars}, {update.Message.DeltaFp}");
            break;
        case TradeUpdate trade:
            Console.WriteLine($"Trade: {trade.Message.CountFp} @ ${trade.Message.YesPriceDollars}");
            break;
    }
}

Error Handling

The SDK provides strongly-typed exceptions for different error scenarios:

using KalshiSharp.Errors;

try
{
    var market = await client.Markets.GetMarketAsync("INVALID");
}
catch (KalshiNotFoundException)
{
    Console.WriteLine("Market not found");
}
catch (KalshiAuthException)
{
    Console.WriteLine("Invalid credentials");
}
catch (KalshiRateLimitException ex)
{
    Console.WriteLine($"Rate limited, retry after: {ex.RetryAfter}");
}
catch (KalshiValidationException ex)
{
    foreach (var error in ex.ValidationErrors ?? new())
    {
        Console.WriteLine($"{error.Key}: {string.Join(", ", error.Value)}");
    }
}
catch (KalshiException ex)
{
    Console.WriteLine($"API error: {ex.StatusCode} - {ex.Message}");
}

Configuration Options

Option Default Description
ApiKey Required Your Kalshi API key ID
ApiSecret Required Your RSA private key in PEM format
Environment Production Production or Demo
BaseUri Auto Override base URI
Timeout 30s HTTP request timeout
ClockSkewTolerance 30s Tolerance for timestamp validation
EnableRateLimiting true Enable client-side rate limiting
RateLimits Basic tier: read burst 200, write burst 100 Separate read, unscoped-write, and shard-write token budgets; override after checking account limits

Project Structure

KalshiSharp/
├── KalshiSharp/                   # Main SDK library
│   ├── Auth/                      # RSA-PSS request signing
│   ├── Configuration/             # Client options
│   ├── DependencyInjection/       # IServiceCollection extensions
│   ├── Errors/                    # Exception types
│   ├── Http/                      # HTTP client pipeline
│   ├── Models/                    # DTOs and enums
│   │   ├── Enums/
│   │   ├── Requests/
│   │   ├── Responses/
│   │   └── WebSocket/
│   ├── RateLimiting/              # Token bucket limiter
│   ├── Rest/                      # REST API clients
│   │   ├── Exchange/
│   │   ├── Historical/
│   │   ├── Markets/
│   │   ├── Events/
│   │   ├── Orders/
│   │   ├── Portfolio/
│   │   └── Users/
│   ├── Serialization/             # JSON converters
│   └── WebSockets/                # WebSocket client
│       ├── Connections/
│       ├── ReconnectPolicy/
│       └── Subscriptions/
├── KalshiSharp.Tests/             # Unit and integration tests
└── KalshiSharp.Examples/          # Example console app

Running Examples

# Set credentials via user-secrets (recommended)
cd KalshiSharp.Examples
dotnet user-secrets set "Kalshi:ApiKey" "your-api-key-id"
dotnet user-secrets set "Kalshi:ApiSecret" "-----BEGIN PRIVATE KEY-----
...your PEM key...
-----END PRIVATE KEY-----"

# Or via environment variables (double underscore for nesting)
export KALSHI__APIKEY="your-api-key-id"
export KALSHI__APISECRET="-----BEGIN PRIVATE KEY-----..."

# Run specific example
dotnet run --project KalshiSharp.Examples -- exchange
dotnet run --project KalshiSharp.Examples -- markets
dotnet run --project KalshiSharp.Examples -- order
dotnet run --project KalshiSharp.Examples -- websocket

# Run all examples
dotnet run --project KalshiSharp.Examples -- all

Example scenarios include exchange status, market discovery, V2 order placement, portfolio fills, and WebSocket market streams. These examples are intentionally small so they can be copied into new .NET console apps or services.

Requirements

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  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
1.3.0 292 10/11/2026
1.2.0 202 8/19/2026
1.1.0 225 8/19/2026
1.0.1 596 1/15/2026
1.0.0 159 1/11/2026