kalshi

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 17 Imported by: 0

README

kalshi-go

Production-grade, fully-typed Go SDK for the Kalshi Predictions API — REST + WebSocket, RSA-PSS request signing, typed services for every endpoint group, automatic retries with jittered backoff, client-side rate limiting, and a self-healing WebSocket client with sequence-gap recovery.

Organized as a set of DDD bounded contexts under one module — exchange, markets, events, orders, ordergroups, portfolio, communications, apikeys, search, and streaming — composed by a top-level Client facade on a single shared transport.

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
  • WebSocket client — all public/private channels, auto-reconnect with jittered exponential backoff, automatic re-subscription, and sequence-gap detection/recovery for orderbook_delta and order_group_updates, delivered as a single typed WSEvent channel (idiomatic Go type-switch, no callback registration)
  • RSA-PSS request signing built in (PKCS#1 and PKCS#8 PEM supported) — no need to hand-roll the crypto
  • Automatic retries on 429/5xx with jittered exponential backoff (context-aware, cancels cleanly)
  • Optional client-side rate limiting (token bucket) to proactively stay under your tier's limits
  • Generic cursor-pagination iteratorsclient.Markets.ListAll(ctx, params) style, one pagination.Iterator[T] implementation shared by every list endpoint
  • Functional-options Client construction — idiomatic, extensible, no giant config struct to memorize
  • context.Context on every call — cancellation and deadlines propagate correctly throughout
  • Zero-dependency core — only github.com/gorilla/websocket is required (the WebSocket client); the REST client uses only the standard library

Package layout

Each bounded context owns its own domain types and a Service; the root package only wires them together and holds cross-cutting infrastructure config/errors.

kalshi-go/
├── client.go, aliases.go, doc.go   — Client facade + Environment/APIError/... aliases
├── internal/transport/             — shared kernel: HTTP execution, retry, rate limit, RSA-PSS signing, errors
├── pagination/                     — generic Iterator[T] used by every ListAll
├── shared/                         — MarketSide (the one value object genuinely shared across contexts)
├── exchange/   apikeys/   search/
├── markets/    events/             — events.Event embeds markets.Market
├── orders/     ordergroups/
├── portfolio/  communications/
├── streaming/                      — WebSocket client (WSClient, WSEvent, Channel, ...)
└── examples/

Domain types (Market, Order, Event, ...) live in their bounded-context package and are imported directly:

import (
	kalshi "github.com/iamkanishka/kalshi-go"
	"github.com/iamkanishka/kalshi-go/markets"
)

resp, err := client.Markets.List(ctx, &markets.ListMarketsParams{Status: markets.StatusOpen})

The root package still owns the handful of types every caller touches to configure a Client or handle its errors — Environment, APIError, ConfigError, TimeoutError, RequestInfo — so those stay as kalshi.APIError etc. rather than requiring an internal/transport import you can't make anyway.

Install

go get github.com/iamkanishka/kalshi-go

Requires Go >= 1.22 (uses generics and errors.As/errors.Is; developed against 1.22).

Quick start

package main

import (
	"context"
	"fmt"
	"log"

	kalshi "github.com/iamkanishka/kalshi-go"
	"github.com/iamkanishka/kalshi-go/markets"
)

func main() {
	// Public market data needs no credentials.
	client, err := kalshi.NewClient(kalshi.WithEnvironment(kalshi.EnvironmentDemo))
	if err != nil {
		log.Fatal(err)
	}

	resp, err := client.Markets.List(context.Background(), &markets.ListMarketsParams{
		Status: markets.StatusOpen,
		Limit:  10,
	})
	if err != nil {
		log.Fatal(err)
	}
	for _, m := range resp.Markets {
		fmt.Println(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, PKCS#1 or PKCS#8).

client, err := kalshi.NewClient(
	kalshi.WithEnvironment(kalshi.EnvironmentDemo),
	kalshi.WithAPIKeyFile(os.Getenv("KALSHI_API_KEY_ID"), os.Getenv("KALSHI_PRIVATE_KEY_PATH")),
)
if err != nil {
	log.Fatal(err)
}

balance, err := client.Portfolio.GetBalance(ctx)
fmt.Printf("Balance: $%.2f\n", float64(balance.Balance)/100)

Or build straight from environment variables (KALSHI_ENV, KALSHI_API_KEY_ID, KALSHI_PRIVATE_KEY / KALSHI_PRIVATE_KEY_PATH):

client, err := kalshi.NewClientFromEnv()

Placing an order (V2)

import (
	"github.com/google/uuid"
	"github.com/iamkanishka/kalshi-go/orders"
)

order, err := client.Orders.Create(ctx, orders.CreateOrderRequest{
	Ticker:                  "HIGHNY-24JAN01-T60",
	ClientOrderID:           uuid.NewString(), // required idempotency key
	Side:                    orders.BookSideBid,
	Count:                   "10.00",
	Price:                   "0.5600",
	TimeInForce:             orders.TimeInForceGoodTillCanceled,
	SelfTradePreventionType: orders.SelfTradePreventionTakerAtCross,
})
if err != nil {
	log.Fatal(err)
}

_, err = client.Orders.Cancel(ctx, order.OrderID)

Pagination

Every list endpoint has a raw cursor method (.List()) and a lazy pagination.Iterator[T] (.ListAll() / .ListPositions() / etc.) that walks every page for you:

it := client.Markets.ListAll(ctx, &markets.ListMarketsParams{Status: markets.StatusOpen})
for it.Next() {
	market := it.Item()
	fmt.Println(market.Ticker)
}
if err := it.Err(); err != nil {
	log.Fatal(err)
}

WebSocket streaming

import "github.com/iamkanishka/kalshi-go/streaming"

ws := client.CreateWebSocket(streaming.WSClientOptions{})

go func() {
	for event := range ws.Events() {
		switch e := event.(type) {
		case streaming.OrderbookSnapshotEvent:
			fmt.Println("snapshot", e.Msg)
		case streaming.OrderbookDeltaEvent:
			fmt.Println("delta", e.Msg)
		case streaming.SequenceGapEvent:
			// The client automatically requests a fresh snapshot to resync.
			log.Printf("gap on %s: expected %d, got %d", e.Channel, e.ExpectedSeq, e.ReceivedSeq)
		case streaming.ReconnectingEvent:
			log.Printf("reconnecting #%d in %dms", e.Attempt, e.Delay)
		case streaming.ErrorEvent:
			log.Println("error:", e.Err)
		}
	}
}()

if err := ws.Connect(ctx); err != nil {
	log.Fatal(err)
}
defer ws.Close()

_, err := ws.Subscribe(ctx, streaming.SubscribeParams{
	Channels:      []streaming.Channel{streaming.ChannelOrderbookDelta, streaming.ChannelTicker},
	MarketTickers: []string{"HIGHNY-24JAN01-T60"},
})

The client 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
streaming.ChannelOrderbookDelta private sequenced; snapshot + deltas
streaming.ChannelTicker public price/volume/OI updates
streaming.ChannelTrade public public trade prints
streaming.ChannelFill private your own fills
streaming.ChannelMarketPositions private position updates
streaming.ChannelMarketLifecycleV2 public market open/close/settle events
streaming.ChannelMultivariateMarketLifecycle public multivariate event lifecycle
streaming.ChannelMultivariate public multivariate market updates
streaming.ChannelCommunications private RFQ/quote activity
streaming.ChannelOrderGroupUpdates private sequenced; order group state
streaming.ChannelUserOrders private your order lifecycle

Error handling

_, err := client.Orders.Create(ctx, req)
if err != nil {
	var apiErr *kalshi.APIError
	if errors.As(err, &apiErr) {
		log.Printf("status=%d code=%s message=%s", apiErr.StatusCode, apiErr.Code, apiErr.Message)
		if apiErr.IsRateLimited() {
			// handled automatically by retry, but you can still inspect it
		}
	}
	var timeoutErr *kalshi.TimeoutError
	if errors.As(err, &timeoutErr) {
		log.Println("request timed out")
	}
}

Configuration reference

kalshi.NewClient(
	kalshi.WithEnvironment(kalshi.EnvironmentDemo),      // default EnvironmentProduction
	kalshi.WithAPIKey(apiKeyID, pemBytes),                // or WithAPIKeyFile(apiKeyID, path)
	kalshi.WithBaseURL(url),                              // override REST base URL
	kalshi.WithWSURL(url),                                // override WebSocket base URL
	kalshi.WithHTTPClient(customClient),                  // inject *http.Client (proxy, mocking, ...)
	kalshi.WithTimeout(10*time.Second),                   // default 10s
	kalshi.WithMaxRetries(3),                             // default 3
	kalshi.WithRateLimit(ratePerSecond, burstCapacity),    // default disabled
	kalshi.WithUserAgent("my-bot/1.0"),
	kalshi.WithRequestHook(func(info kalshi.RequestInfo) { ... }),
)

Service map

Service Bounded context package Methods
client.Exchange exchange GetStatus, GetAnnouncements, GetSchedule, GetSeriesFeeChanges, GetUserDataTimestamp, GetAPILimits
client.Markets markets List, ListAll, Get, GetOrderbook, GetOrderbooks, GetTrades, GetCandlesticks, GetSeries, ListSeries
client.Events events List, ListAll, Get, GetMetadata, GetForecastHistory, ListMultivariateCollections
client.Orders orders List, ListAll, Get, Create, BatchCreate, Cancel, BatchCancel, Amend, Decrease, GetQueuePosition, GetQueuePositions, Legacy.Create, Legacy.BatchCreate
client.OrderGroups ordergroups List, Get, Create, Delete, Reset, Trigger, UpdateLimit
client.Portfolio portfolio GetBalance, GetPositions, ListPositions, GetFills, ListFills, GetSettlements, GetDeposits, GetWithdrawals, GetRestingOrderTotalValue, CreateSubaccount, GetSubaccountBalances, TransferBetweenSubaccounts, GetSubaccountTransfers
client.Communications communications ListRfqs, CreateRfq, GetRfq, DeleteRfq, ListQuotes, CreateQuote, GetQuote, DeleteQuote, AcceptQuote, ConfirmQuote, GetCommunicationsID
client.APIKeys apikeys List, Create, Generate, Delete
client.Search search Query
client.CreateWebSocket(...) streaming Connect, Subscribe, Unsubscribe, UpdateSubscription, ListSubscriptions, Events, Close

See examples/ for three runnable programs, and Go doc comments on every exported type/method (go doc github.com/iamkanishka/kalshi-go, or e.g. go doc github.com/iamkanishka/kalshi-go/markets).

Development

make build       # go build ./...
make test-race   # go test ./... -race
make cover       # coverage report
make lint        # gofmt -l + go vet
make check       # lint + test-race + build
make examples    # build the example binaries

License

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.

Documentation

Index

Constants

View Source
const (
	// EnvironmentProduction is the live trading environment.
	EnvironmentProduction = transport.EnvironmentProduction
	// EnvironmentDemo is Kalshi's sandbox environment, backed by play money.
	EnvironmentDemo = transport.EnvironmentDemo
)
View Source
const SDKVersion = transport.SDKVersion

SDKVersion is the current release of this module.

Variables

View Source
var ParsePrivateKeyPEM = transport.ParsePrivateKeyPEM

ParsePrivateKeyPEM parses a PEM-encoded RSA private key (PKCS#1 or PKCS#8) into the form Client and streaming.WSClient use for signing.

Functions

This section is empty.

Types

type APIError

type APIError = transport.APIError

APIError is returned whenever the Kalshi REST API responds with a non-2xx status.

type Client

type Client struct {

	// Services. Each is the entry point for one bounded context.
	Exchange       *exchange.Service
	Markets        *markets.Service
	Events         *events.Service
	Orders         *orders.Service
	OrderGroups    *ordergroups.Service
	Portfolio      *portfolio.Service
	Communications *communications.Service
	APIKeys        *apikeys.Service
	Search         *search.Service
	// contains filtered or unexported fields
}

Client is the top-level Kalshi SDK client. Construct one with NewClient and reuse it — it holds a connection-pooled *http.Client and (optionally) a parsed private key for request signing.

func NewClient

func NewClient(opts ...ClientOption) (*Client, error)

NewClient constructs a Client from the given options. With no options it is an unauthenticated production client suitable for public market data.

func NewClientFromEnv

func NewClientFromEnv(opts ...ClientOption) (*Client, error)

NewClientFromEnv builds a Client from standard environment variables: KALSHI_ENV ("production" | "demo"), KALSHI_API_KEY_ID, and either KALSHI_PRIVATE_KEY (PEM contents) or KALSHI_PRIVATE_KEY_PATH. Additional options are applied after the environment-derived ones and can override them.

func (*Client) CreateWebSocket

func (c *Client) CreateWebSocket(opts streaming.WSClientOptions) *streaming.WSClient

CreateWebSocket builds a new streaming.WSClient wired with the same credentials and environment as this Client. Call Connect on the result.

func (*Client) Environment

func (c *Client) Environment() Environment

Environment returns the environment this Client is configured for.

func (*Client) IsAuthenticated

func (c *Client) IsAuthenticated() bool

IsAuthenticated reports whether this Client was constructed with API credentials.

func (*Client) WSURL

func (c *Client) WSURL() string

WSURL returns the configured WebSocket base URL.

type ClientOption

type ClientOption func(*clientConfig) error

ClientOption configures a Client. Options are applied in order, so later options can override earlier ones.

func WithAPIKey

func WithAPIKey(apiKeyID string, privateKeyPEM []byte) ClientOption

WithAPIKey configures authenticated requests using an API Key ID and a PEM-encoded RSA private key (the .key file downloaded from Account & security -> API Keys).

func WithAPIKeyFile

func WithAPIKeyFile(apiKeyID string, privateKeyPath string) ClientOption

WithAPIKeyFile is like WithAPIKey but reads the PEM from a file path.

func WithBaseURL

func WithBaseURL(url string) ClientOption

WithBaseURL overrides the REST base URL (advanced / testing).

func WithEnvironment

func WithEnvironment(env Environment) ClientOption

WithEnvironment selects production or demo. Defaults to production.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) ClientOption

WithHTTPClient injects a custom *http.Client (custom transport, proxy, mocking, etc).

func WithMaxRetries

func WithMaxRetries(n int) ClientOption

WithMaxRetries sets the max attempts (including the first) for retryable errors (429/5xx). Default 3. Set to 1 to disable retries.

func WithRateLimit

func WithRateLimit(ratePerSecond, burstCapacity float64) ClientOption

WithRateLimit enables a proactive client-side token-bucket rate limiter at ratePerSecond requests/sec, with the given burst capacity (tokens). Disabled by default; the SDK still retries 429s it receives regardless.

func WithRequestHook

func WithRequestHook(fn func(RequestInfo)) ClientOption

WithRequestHook registers an observability callback invoked after every REST request.

func WithTimeout

func WithTimeout(d time.Duration) ClientOption

WithTimeout sets the per-request timeout. Default 10s.

func WithUserAgent

func WithUserAgent(ua string) ClientOption

WithUserAgent overrides the default User-Agent header.

func WithWSURL

func WithWSURL(url string) ClientOption

WithWSURL overrides the WebSocket base URL (advanced / testing).

type ConfigError

type ConfigError = transport.ConfigError

ConfigError indicates invalid or incomplete SDK configuration.

type Environment

type Environment = transport.Environment

Environment selects which Kalshi deployment a Client talks to.

type RequestInfo

type RequestInfo = transport.RequestInfo

RequestInfo is passed to an observability hook after every REST request completes.

type TimeoutError

type TimeoutError = transport.TimeoutError

TimeoutError indicates a request exceeded its configured timeout.

Directories

Path Synopsis
Package apikeys covers /api_keys/* — manage the RSA API keys used for request signing.
Package apikeys covers /api_keys/* — manage the RSA API keys used for request signing.
Package communications covers /communications/* — request-for-quote (RFQ) negotiated trading.
Package communications covers /communications/* — request-for-quote (RFQ) negotiated trading.
Package events covers /events/* — events (groups of related markets).
Package events covers /events/* — events (groups of related markets).
examples
place_and_cancel_order command
Command place_and_cancel_order demonstrates an authenticated trading flow: check balance, place a resting limit order, then cancel it.
Command place_and_cancel_order demonstrates an authenticated trading flow: check balance, place a resting limit order, then cancel it.
public_market_data command
Command public_market_data fetches public market data — no API key required.
Command public_market_data fetches public market data — no API key required.
websocket_orderbook command
Command websocket_orderbook streams live order book updates for a market over WebSocket, demonstrating reconnect and sequence-gap handling.
Command websocket_orderbook streams live order book updates for a market over WebSocket, demonstrating reconnect and sequence-gap handling.
Package exchange covers /exchange/* — exchange-wide status, trading schedule, announcements, and fee-change history.
Package exchange covers /exchange/* — exchange-wide status, trading schedule, announcements, and fee-change history.
internal
Package markets covers /markets/* and /series/* — market discovery, order books, trades, and candlesticks.
Package markets covers /markets/* and /series/* — market discovery, order books, trades, and candlesticks.
Package ordergroups covers /portfolio/order_groups/* — coordinated risk limits across a set of orders.
Package ordergroups covers /portfolio/order_groups/* — coordinated risk limits across a set of orders.
Package orders covers /portfolio/events/orders/* (V2, recommended) and the order-read/cancel/amend surface under /portfolio/orders/*.
Package orders covers /portfolio/events/orders/* (V2, recommended) and the order-read/cancel/amend surface under /portfolio/orders/*.
Package pagination provides a lazy, generic cursor-pagination iterator shared by every bounded context that exposes a ListAll-style method (markets, events, orders, portfolio, ...).
Package pagination provides a lazy, generic cursor-pagination iterator shared by every bounded context that exposes a ListAll-style method (markets, events, orders, portfolio, ...).
Package portfolio covers /portfolio/* — account balance, positions, fills, settlements, deposits/withdrawals, and subaccounts.
Package portfolio covers /portfolio/* — account balance, positions, fills, settlements, deposits/withdrawals, and subaccounts.
Package search covers GET /search — full-text search across markets, events, and series.
Package search covers GET /search — full-text search across markets, events, and series.
Package shared holds the handful of value objects that genuinely cross bounded-context boundaries in this SDK (currently just MarketSide, which appears on markets.Trade, orders.CreateOrderLegacyRequest, portfolio.Fill, communications.Rfq/CreateRfqRequest, and streaming.OrderbookDeltaMsg).
Package shared holds the handful of value objects that genuinely cross bounded-context boundaries in this SDK (currently just MarketSide, which appears on markets.Trade, orders.CreateOrderLegacyRequest, portfolio.Fill, communications.Rfq/CreateRfqRequest, and streaming.OrderbookDeltaMsg).
Package streaming implements Kalshi's single-endpoint, multi-channel WebSocket API: a resilient, read-only client with automatic reconnect, re-subscription, and sequence-gap recovery.
Package streaming implements Kalshi's single-endpoint, multi-channel WebSocket API: a resilient, read-only client with automatic reconnect, re-subscription, and sequence-gap recovery.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL