Skip to main content

Crate kalshi_pmx

Crate kalshi_pmx 

Source
Expand description

§kalshi-pmx

kalshi-pmx is a high-level async Rust SDK for Kalshi’s Trade API v2 and authenticated WebSocket streams.

It is built for Tokio applications that want typed request builders, typed query params, typed response models, ergonomic REST namespaces, and first-class streaming support without hand-writing raw HTTP calls.

§Features

  • Tokio async REST client with production, demo, and custom environments.
  • RSA API-key signing for REST and WebSocket authentication.
  • High-level REST namespaces for exchange, markets, events, orders, portfolio, communications, API keys, account, live data, milestones, search, FCM, historical data, multivariate markets, structured targets, incentive programs, and order groups.
  • Generated OpenAPI structs, enums, query params, request builders, and typed operation methods.
  • WebSocket subscriptions for public and private channels, including ticker, trades, orderbook deltas, fills, user orders, market positions, lifecycle, communications, order groups, and CF Benchmarks channels.
  • LiveOrderbook helper that applies snapshots and deltas into local best-bid state.
  • Conservative optional rate limiter and retry handling for 429 responses.
  • Offline unit and route tests plus ignored live demo integration tests.

§Install

[dependencies]
kalshi-pmx = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

The crate uses Rust 1.85 or newer.

§Authentication

Public market-data REST calls can be made without credentials:

use kalshi_pmx::params::GetMarketsParams;
use kalshi_pmx::{Kalshi, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let kalshi = Kalshi::demo().build()?;

    let markets = kalshi
        .markets()
        .list(&GetMarketsParams::new().status("open").limit(10))
        .await?;

    println!("{:#?}", markets.markets);
    Ok(())
}

Authenticated REST and WebSocket calls need an API key id and RSA private key:

use kalshi_pmx::{Kalshi, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let kalshi = Kalshi::builder()
        .from_env()?
        .build()?;

    let balance = kalshi.portfolio().balance().await?;
    println!("{balance:#?}");

    Ok(())
}

For examples and live tests, create a .env file:

KALSHI_ENV=demo
KALSHI_API_KEY_ID=your-demo-api-key-id
KALSHI_PRIV_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
KALSHI_TICKER=replace-with-active-market-ticker

KALSHI_PRIV_KEY may be PEM text or a path to a PEM file. KALSHI_ENV accepts demo/sandbox, production/prod, or a custom REST base URL paired with KALSHI_WS_URL.

You can also configure credentials manually:

use kalshi_pmx::{Kalshi, Result};

fn client() -> Result<kalshi_pmx::KalshiBuilder> {
    Kalshi::demo().with_key_file("your-api-key-id", "kalshi-private-key.pem")
}

§High-Level REST

Normal SDK usage should go through namespace clients and typed params:

use kalshi_pmx::params::{GetMarketOrderbookParams, GetMarketsParams, GetTradesParams};
use kalshi_pmx::{Kalshi, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let kalshi = Kalshi::demo().build()?;

    let markets = kalshi
        .markets()
        .list(&GetMarketsParams::new().status("open").limit(1))
        .await?;

    if let Some(market) = markets.markets.first() {
        let book = kalshi
            .markets()
            .orderbook(&market.ticker, &GetMarketOrderbookParams::new().depth(5))
            .await?;

        let trades = kalshi
            .markets()
            .trades(&GetTradesParams::new().ticker(&market.ticker).limit(5))
            .await?;

        println!("{book:#?}");
        println!("{:#?}", trades.trades);
    }

    Ok(())
}

Order requests use generated builders and enums. Building a request is side-effect free; calling orders().create(...) is the step that actually submits an order.

use kalshi_pmx::generated::{BookSide, CreateOrderV2Request, SelfTradePreventionType};
use kalshi_pmx::models::TimeInForce;
use kalshi_pmx::Result;

#[tokio::main]
async fn main() -> Result<()> {
    let order = CreateOrderV2Request::new(
        "KXEXAMPLE-26JUN24-B100",
        BookSide::Bid,
        "1.0000",
        "0.0500",
        TimeInForce::GoodTillCanceled,
        SelfTradePreventionType::TakerAtCross,
    )
    .client_order_id("client-order-id")
    .post_only(true);

    println!("{}", serde_json::to_string_pretty(&order)?);
    Ok(())
}

§Typed OpenAPI Layer

The higher-level namespace clients delegate to generated typed operation methods. You can also call the generated layer directly when you want exact operation names from the OpenAPI spec:

use kalshi_pmx::params::GetMarketsParams;
use kalshi_pmx::{Kalshi, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let kalshi = Kalshi::demo().build()?;

    let status = kalshi.typed().get_exchange_status().await?;
    let markets = kalshi
        .typed()
        .get_markets(&GetMarketsParams::new().status("open").limit(5))
        .await?;

    println!("{status:#?}");
    println!("{:#?}", markets.markets);

    Ok(())
}

§WebSockets

WebSocket streams are authenticated and use typed subscription builders:

use kalshi_pmx::ws::Subscription;
use kalshi_pmx::{Kalshi, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let kalshi = Kalshi::demo()
        .with_key_file("your-api-key-id", "kalshi-private-key.pem")?
        .build()?;

    let mut stream = kalshi.stream().connect().await?;
    stream
        .subscribe(Subscription::ticker().markets(["replace-with-active-market-ticker"]))
        .await?;

    while let Some(message) = stream.next().await {
        println!("{:#?}", message?);
    }

    Ok(())
}

For orderbook streams, LiveOrderbook keeps local state:

use kalshi_pmx::ws::{LiveOrderbook, Subscription};
use kalshi_pmx::{Kalshi, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let kalshi = Kalshi::demo()
        .with_key_file("your-api-key-id", "kalshi-private-key.pem")?
        .build()?;

    let mut stream = kalshi.stream().connect().await?;
    stream
        .subscribe(
            Subscription::orderbook()
                .markets(["replace-with-active-market-ticker"])
                .use_yes_price(true),
        )
        .await?;

    let mut book = LiveOrderbook::new();
    while let Some(message) = stream.next().await {
        let message = message?;
        if book.apply_message(&message)? {
            println!("best yes bid: {:?}", book.best_yes_bid());
        }
    }

    Ok(())
}

§Market Data Granularity

Kalshi’s public streams provide market data such as ticker updates, trades, and aggregate orderbook snapshots/deltas. They are suitable for maintaining a local aggregate orderbook with LiveOrderbook, but they are not a feed of every individual order submitted by every participant. Private streams such as fills and user orders report your authenticated account activity.

§Examples

Run public REST examples without credentials:

cargo run --example list_markets
cargo run --example market_snapshot
cargo run --example typed_operations
cargo run --example order_request_builder

Run authenticated read or stream examples after creating .env:

cargo run --example read_portfolio
cargo run --example stream_ticker
cargo run --example live_orderbook

§Specs and Generation

The repository keeps the Kalshi OpenAPI and AsyncAPI references used by this SDK in specs/:

  • specs/openapi.yaml
  • specs/asyncapi.yaml

From a repository checkout, regenerate typed schemas, params, and operation methods after updating the OpenAPI spec:

cargo run --manifest-path xtask/Cargo.toml -- generate-openapi-types

The crates.io package ships the generated Rust source, not the upstream spec files, because the checked-in specs carry their own upstream licensing metadata.

§Tests

Offline tests cover auth signing and redaction, authenticated request headers, encoded path segments, API error bodies, 429 retry behavior, generated builders and enums, model serialization, route construction, typed query encoding, pagination helpers, WebSocket message parsing, WebSocket client command flow, reconnect behavior, and spec coverage:

cargo test

Read-only live demo tests are ignored by default. They load .env, use demo mode unless configured otherwise, and exercise authenticated REST, public market data, portfolio reads, typed operations, and WebSocket subscriptions:

cargo test --test live_demo -- --ignored --test-threads=1

Release checks:

cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
cargo check --examples
cargo package
cargo publish --dry-run

§API Coverage

The SDK includes generated types and typed operations for the checked-in OpenAPI spec, plus high-level REST namespace clients for the documented API areas. WebSocket channels from the checked-in AsyncAPI spec are represented as typed subscriptions and parsed stream messages, with raw message preservation for forward compatibility.

Re-exports§

pub use client::Kalshi;
pub use client::KalshiBuilder;
pub use config::Environment;
pub use config::KalshiConfig;
pub use error::Error;
pub use error::Result;

Modules§

auth
RSA request signing helpers for Kalshi API credentials.
client
Core HTTP client and builder for the SDK.
config
Runtime configuration for REST and WebSocket clients.
error
Error and result types used by the SDK.
generated
Generated OpenAPI component schemas. Regenerate with cargo run --manifest-path xtask/Cargo.toml -- generate-openapi-types.
models
Shared model helpers used by the handwritten SDK surface.
pagination
Small helpers for cursor-based Kalshi pagination.
params
Generated OpenAPI query parameter structs. Regenerate with cargo run --manifest-path xtask/Cargo.toml -- generate-openapi-types.
rate_limit
Optional client-side REST rate limiting.
rest
High-level REST namespace clients.
typed
Generated typed Trade API methods. Regenerate with cargo run --manifest-path xtask/Cargo.toml -- generate-openapi-types.
ws
Authenticated Kalshi WebSocket streams.