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
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 linesThe 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:
| Source | How | What a change does |
|---|---|---|
| A project directory | langbot bots deploy . | a new build, then a new preview |
| One script | langbot bots deploy bot.py | the same, from a single file |
| A GitHub repository | langbot bots connect-github wx-bot owner/repo | every 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
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
| Variable | Preview | Live |
|---|---|---|
KALSHI_API_BASE / KALSHI_WS_URL | the paper exchange | Langbot's order proxy (or Kalshi, with order_proxy: false) |
KALSHI_API_KEY_ID / KALSHI_PRIVATE_KEY | a paper key Langbot issued | a proxy key (or your own restricted Kalshi key, with order_proxy: false) |
KALSHI_ENV | paper | prod or demo |
LANGBOT_API_TOKEN / LANGBOT_API_BASE | a token scoped to this bot: it reads Langbot data and this bot only | same |
LANGBOT_BOT_ID, LANGBOT_DEPLOYMENT_ID, LANGBOT_TARGET | ids, preview | ids, live |
your env: names | the values you set | the 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:
langbot bots paper-key > .env.paper # KALSHI_API_BASE, KALSHI_API_KEY_ID, KALSHI_PRIVATE_KEY
set -a; . ./.env.paper; set +a
python bot.pyGoing live
- 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.
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.
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.