A C++23 client for Kalshi's Predictions API. It covers every
REST operation and WebSocket channel.
Its types and methods come from Kalshi's OpenAPI and AsyncAPI documents in
spec/, so their names
match Kalshi's API docs. Kalshi's separate Margin
and Perpetuals API is out of scope. The
API reference lists every type and method.
You need a C++23 compiler, CMake 3.31+, OpenSSL 3, libcurl, and libwebsockets.
CI tests GCC 13 on Ubuntu 24.04, Apple Clang on Apple silicon, and MSVC on
Windows, where vcpkg.json
supplies the dependencies. On macOS, the deployment target must be 13.3 or later.
# macOS
brew install cmake openssl libwebsockets pkg-config
# Ubuntu 24.04, whose apt CMake is older than 3.31
sudo apt install build-essential pkg-config pipx libssl-dev libcurl4-openssl-dev libwebsockets-dev
pipx install cmake && pipx ensurepath # then open a new shellAdd the library to your CMake project:
include(FetchContent)
FetchContent_Declare(kalshi
GIT_REPOSITORY https://github.com/Reddimus/kalshi-cpp.git
GIT_TAG v0.6.2)
FetchContent_MakeAvailable(kalshi)
target_link_libraries(myapp PRIVATE kalshi::kalshi)Or add kalshi-cpp to your vcpkg.json, and vcpkg builds
it along with OpenSSL, libcurl, and libwebsockets, so you can skip the package
installs above. vcpkg and cmake --install both provide the same target through
find_package(kalshi CONFIG REQUIRED). Until 1.0, a minor release may break
the API. CHANGELOG.md has migration notes.
Public endpoints need no key.
#include <kalshi/kalshi.hpp>
#include <iostream>
int main() {
kalshi::KalshiClient client{kalshi::HttpClient{}};
kalshi::GetMarketsParams params;
params.series_ticker = "KXHIGHNY"; // New York City's daily high temperature
params.status = kalshi::GetMarketsStatus::Open;
kalshi::Result<kalshi::GetMarketsResponse> page = client.get_markets(params);
if (!page) {
std::cerr << page.error().message << '\n';
return 1;
}
for (const kalshi::Market& market : page->markets) {
std::cout << market.ticker << ' ' << market.yes_bid_dollars << '\n';
}
}kalshi::collect_pages follows cursors when you want every page.
Calls that can fail return kalshi::Result<T>, which is
std::expected<T, kalshi::Error>, instead of throwing. Error::code classifies
the failure, such as RateLimited or NetworkError. When Kalshi rejects a
request, Error::http_status and Error::api_code hold its HTTP status and
error code.
Create an API key at https://kalshi.com/account/profile. Save the private key
file and note the key ID. Signer loads unencrypted RSA and Ed25519 PEM keys.
kalshi::Result<kalshi::Signer> signer = kalshi::Signer::from_pem_file("your-key-id", "kalshi.key");
if (!signer) {
std::cerr << signer.error().message << '\n';
return 1;
}
kalshi::KalshiClient client{kalshi::HttpClient{*signer}}; // Signer is cheap to copy
kalshi::Result<kalshi::GetBalanceResponse> balance = client.get_balance();Kalshi's demo exchange uses play money and its own keys. To use it, pass
kalshi::ClientConfig::for_environment(kalshi::Environment::Demo) to
HttpClient after the signer. kalshi::WsConfig::for_environment does the same
for WebSocketClient.
This places a real order unless client points at the demo exchange.
kalshi::CreateOrderV2Request order;
order.ticker = "KXHIGHNY-26SEP25-T70";
order.side = kalshi::BookSide::Bid; // Bid buys Yes, Ask sells Yes
order.count = "10.00"; // contracts
order.price = "0.5600"; // dollars
order.time_in_force = kalshi::TimeInForce::GoodTillCanceled;
order.self_trade_prevention_type = kalshi::SelfTradePreventionType::TakerAtCross;
kalshi::Result<kalshi::CreateOrderV2Response> placed = client.create_order(order);Counts and prices are fixed-point strings, not doubles. The client checks them
and the required fields before sending, so order.price = "56c" fails locally
with InvalidRequest.
kalshi::WebSocketClient ws(*signer);
ws.on_message([](const kalshi::WsMessage& message) {
using Delta = kalshi::ws::Update<kalshi::ws::OrderbookDelta>;
if (const Delta* delta = std::get_if<Delta>(&message)) {
std::cout << delta->msg.market_ticker << ' ' << delta->msg.delta_fp << '\n';
}
});
kalshi::Result<kalshi::ws::Subscription> book = ws.subscribe(
kalshi::ws::Channel::OrderbookDelta, {.market_tickers = {"KXHIGHNY-26SEP25-T70"}});
kalshi::Result<void> connected = ws.connect();connect() waits until the connection opens or fails. Callbacks then run on the
client's network thread until you call disconnect() or destroy ws, so keep
your program running. After a dropped connection, the client reconnects,
resubscribes, and keeps your ws::Subscription handles valid. When it sees a
skipped order book sequence number, it reports the gap to on_error and
requests fresh snapshots. docs/channels.md lists each
channel's message types.
Both are transports you stack under the client:
std::shared_ptr<kalshi::HttpClient> http = std::make_shared<kalshi::HttpClient>(*signer);
std::shared_ptr<kalshi::RateLimitedTransport> paced =
std::make_shared<kalshi::RateLimitedTransport>(http, kalshi::RateLimitConfig{});
kalshi::KalshiClient client{std::make_shared<kalshi::RetryingTransport>(paced)};By default, RetryingTransport repeats a POST, PUT, or DELETE only after a 429,
so it never places an order twice. RateLimitConfig{} matches Kalshi's Basic
tier. To match your account's limits, pass the results of
get_account_api_limits() and get_account_endpoint_costs() to
kalshi::rate_limit_config().
examples/ has
four programs: public market data, your portfolio, an order placed and canceled
on the demo exchange, and a live order book. From a clone,
make run-market_data runs the first one without a key. For the others,
make run-<name> loads your key from .env, as the
examples README
shows.
CONTRIBUTING.md covers building from source, tests, updating the specs, and releases. Report security issues as SECURITY.md describes.