Run it live

Host your own bot

Deploy your own Kalshi bot code from a directory, a script or a GitHub repository; every deploy is a preview on Langbot's paper exchange, and promoting it runs it on your own Kalshi account behind your guardrails.

On this page

Bring your own code. Write a Kalshi bot in Python, Node, Rust, Go or anything with a Dockerfile, and Langbot builds it, runs it in a sandbox and points it at the right exchange:

  • every deploy starts a preview on Langbot's paper exchange: Kalshi's own API, on the order books Langbot records live, with Kalshi's fees. No real money.
  • promote a preview and it becomes a live deployment on your own Kalshi account.
  • roll back to an earlier live deployment, or stop the bot (the kill switch), with one command or one click.

The same code runs on both, unchanged: it reads KALSHI_API_BASE, KALSHI_API_KEY_ID and KALSHI_PRIVATE_KEY from its environment and talks Kalshi's Trade API v2.

Start in a minute

Terminal
langbot bots init wx-bot --template python    # or --template node
cd wx-bot
langbot bots deploy .                          # build + a preview on the paper exchange
langbot bots logs wx-bot -f                    # stdout, stderr and Langbot's own lines

The template is a small market maker for one Kalshi series: it lists the open markets, keeps one small bid a cent above the best bid where the spread is wide, and cancels what it no longer wants. Change decide() to make it yours.

Three ways to give Langbot your code:

SourceHowWhat a change does
A project directorylangbot bots deploy .a new build, then a new preview
One scriptlangbot bots deploy bot.pythe same, from a single file
A GitHub repositorylangbot bots connect-github wx-bot owner/repoevery push and pull request builds a preview; the production branch builds the production candidate

.env files and private keys (*.pem, *.key) are never uploaded; the CLI says which files it left out. Add patterns to .langbotignore to leave out more.

langbot.yaml

YAML
name: wx-bot
runtime: python            # python | node | rust | go | docker
entrypoint: bot.py         # or: command: ["python", "-m", "wxbot"]

markets:                   # what the bot may trade; orders anywhere else are refused
  series: [KXHIGHNY]       # also: tickers, events, query, category, speaker, kind, calendar

budget_usd: 50             # the paper balance on previews; the most at risk on live

limits:                    # guardrails, enforced outside your code
  max_order_contracts: 10
  max_order_usd: 10
  max_daily_loss_usd: 12.5
  max_orders_per_min: 10
  stop_after_errors: 3

env: [OPENWEATHER_KEY]     # secrets your code reads (values: `langbot bots env set`)
egress: [api.open-meteo.com]   # extra HTTPS hosts it may reach
order_proxy: true          # live orders go through Langbot's order proxy (the default)

resources: {cpu: 0.5, memory_mb: 512}
schedule:                  # optional: when it may open positions
  windows: recurring
  recurring: [{days: [mon, tue, wed, thu, fri], start: "09:00", end: "16:00"}]
processes:                 # optional: more processes beside the main one
  feed: {command: ["python", "feed.py"]}
health: {type: http, port: 8080, path: /healthz}   # optional; default: the process is alive
restart: {policy: on-failure, max_restarts: 5, window_s: 600}

A guardrail is turned off by setting it to null and naming it under confirm_off (you are saying the bot may lose its whole budget). A single script can carry the same settings in a comment block, and a Python script's dependencies in a standard # /// script block:

# /// script
# dependencies = ["httpx>=0.27"]
# ///
# /// langbot
# markets: {series: [KXHIGHNY]}
# budget_usd: 25
# ///

How it is built

A Dockerfile in the project is built as written. Otherwise Langbot detects the runtime from your files and writes the image for you: requirements.txt or pyproject.toml (Python), package.json with its lockfile (Node), Cargo.toml (Rust), go.mod (Go). Every image runs as an unprivileged user with a read-only file system; write to /tmp. The build log streams to the CLI, the web page and langbot bots logs --build bld_….

What your bot runs with

VariablePreviewLive
KALSHI_API_BASE / KALSHI_WS_URLthe paper exchangeLangbot's order proxy (or Kalshi, with order_proxy: false)
KALSHI_API_KEY_ID / KALSHI_PRIVATE_KEYa paper key Langbot issueda proxy key (or your own restricted Kalshi key, with order_proxy: false)
KALSHI_ENVpaperprod or demo
LANGBOT_API_TOKEN / LANGBOT_API_BASEa token scoped to this bot: it reads Langbot data and this bot onlysame
LANGBOT_BOT_ID, LANGBOT_DEPLOYMENT_ID, LANGBOT_TARGETids, previewids, live
your env: namesthe values you setthe values you set

KALSHI_PRIVATE_KEY is a PEM; if your platform flattened it, replace a literal \n with a newline before loading it (both templates do).

Env values are write-only: langbot bots env set wx-bot OPENWEATHER_KEY prompts for the value, seals it, and nothing ever shows it again. Every log line is scrubbed of your secrets before it leaves the sandbox host.

The paper exchange

The paper exchange speaks Kalshi's Trade API v2: the same paths, the same request signing (RSA-PSS or Ed25519, KALSHI-ACCESS-KEY / -TIMESTAMP / -SIGNATURE) and the same response shapes for markets, events, order books, balance, positions, fills, orders (create, amend, decrease, cancel, batches) and the WebSocket channels (orderbook_delta, ticker, fill, user_orders).

Fills follow the Simulator's model, on the books Langbot records live: a marketable order takes the recorded depth up to its limit, a resting order fills when a later book trades through its price, and fees are Kalshi's (taker 7%, maker 1.75% of C·P·(1−P), rounded up to the cent). What differs from Kalshi: there is no market impact and no queue position, liquidity your order took returns with the next recorded book, markets settle on the results Langbot records, and rate limits are not enforced. Each preview has its own paper balance (its budget_usd).

Run a bot on your own machine against it with a paper key:

Terminal
langbot bots paper-key > .env.paper     # KALSHI_API_BASE, KALSHI_API_KEY_ID, KALSHI_PRIVATE_KEY
set -a; . ./.env.paper; set +a
python bot.py

Going live

  1. Connect your Kalshi key on the bot's page while signed in (the same one-step setup as strategy bots: a sub-account with the budget moved into it and a restricted, trade-only key). Keys are never accepted from the CLI, the MCP or chat.
  2. langbot bots promote wx-bot (or Promote on the page). Langbot promotes the production candidate when there is one, else the newest preview; -d hd_… picks one.

Live orders go through Langbot's order proxy by default. Your bot signs its requests with a proxy key; the proxy checks each order against your markets and limits (contracts, dollars at risk, orders per minute, daily loss, the budget, errors in a row), writes it to the bot's audit log, and only then signs it with your Kalshi key and sends it on. Your Kalshi key never enters the sandbox, and your code cannot get past your guardrails. Cancels and reads always pass, even after a guardrail has stopped the bot. With order_proxy: false your code gets your restricted Kalshi key and talks to Kalshi directly; the guardrails are then your code's own.

Every deployment, kept

Each deploy is an immutable deployment (hd_…) with its own image, settings, logs and paper or live ledger. A new preview replaces the running preview; a promote or rollback replaces the running live deployment.

Terminal
langbot bots deployments wx-bot          # deployments, builds, env names
langbot bots rollback wx-bot hd_…        # run an earlier live deployment again
langbot bots stop wx-bot                 # the kill switch (--target live|preview)

Stopping kills the bot's sandboxes within seconds and the order proxy refuses its new orders at once. Orders already resting on Kalshi stay there: cancel them on Kalshi if you want them gone.

A bot that keeps crashing is restarted with a growing delay; more than max_restarts restarts within window_s stops the deployment (failed, crash_loop) so a broken bot cannot hammer the exchange.

Sandbox

Each deployment runs in its own sandbox with a user-space kernel between your code and the host, a read-only file system and a size-capped /tmp, no Linux capabilities, and CPU, memory and process limits. It has no network of its own: it reaches only the paper exchange (previews) or the order proxy (live), the Langbot API, and the HTTPS hosts you list under egress:. It can never reach Langbot's internal services or other users' bots.

From Claude and other agents

With hosted code bots enabled, the MCP server has code_bot_init, code_bot_deploy, code_bot_status, code_bot_logs, code_bot_promote, code_bot_rollback, code_bot_stop, code_bot_env and code_bot_connect_github. Promote and rollback trade real money and need the user's explicit yes.