start

CLI

The CLI drives sandboxes. It is the way most people should use them — the SDK is the programmatic alternative, not the default. macOS and Linux (Intel and ARM); Windows under WSL.

install & auth

Paste an API key from your dashboard. It is verified before it is saved, so a bad paste fails here rather than on some later command.

shell
curl -fsSL https://boltzlabs.john221wick.in/cli/install.sh | sh

bzlabs auth              # paste a key, or see who you are
export BOLTZLABS_API_KEY=…  # or skip the login entirely, in CI

sandboxes

With no arguments, create asks two questions. Machine comes first because it sets the price and the memory ceiling — a 256 MB nano is a poor home for pytorch.

terminal
$ bzlabs create
Machine  (↑/↓ to move, enter to select)
  nano      1 vCPU · 256 MB RAM · 10 GB disk · $0.03*/hr
 small     2 vCPU · 512 MB RAM · 10 GB disk · $0.06*/hr
  medium    2 vCPU · 1024 MB RAM · 20 GB disk · $0.11*/hr
  large     4 vCPU · 4096 MB RAM · 40 GB disk · $0.24*/hr

Environment  (↑/↓ to move, enter to select)
  base      debian + bash, curl, git
 python    python 3 + numpy, pandas, requests
  node      node 22
  pytorch   python + torch (CPU)
  opencode  OpenCode
  claude-code Claude Code
  codex     OpenAI Codex CLI
  deepagents Deep Agents Code
  aider     Aider

Internet  (↑/↓ to move, enter to select)
 off       no outbound network (default)
  on        allow outbound internet

Name (blank for auto): scratch
Idle timeout seconds (0 = platform default) [600]:
Max lifetime seconds (0 = platform default) [3600]:

created sb-eb02cc (python, small) — $0.06*/hr
connect with: bzlabs connect scratch

* Sandbox prices have a one-hour minimum. Runtime after the first 60 minutes is billed per second.

Every prompt has a default — press enter all the way through for small / base / internet off / 10 min idle / 1 h max. Each prompt is skipped when its flag is given, so the same command runs unattended. Pass --internet when the box needs the network. Coding-agent environments default internet access on for authentication and model APIs; pass --no-internet to isolate one. A sandbox bills for as long as it exists, so cost is a column rather than a separate command — and rm is what ends the bill, not disconnecting. Names must be unique among your running sandboxes.

shell
bzlabs create --machine small --environment python --name scratch
bzlabs create --internet --idle-timeout 600 --max-lifetime 3600

bzlabs ls                    # id, name, status, runtime, cost
bzlabs status scratch       # id or name
bzlabs rm scratch            # stops the meter

bzlabs machines              # nano · small · medium · large, with prices
bzlabs environments          # runtimes plus OpenCode, Claude, Codex, Deep Agents, Aider

Naming neither is fine: the machine defaults to small and the environment to base.

driving one

exec runs one command and comes back; connect hands over your keyboard as a real PTY. Both take the sandbox id or the name you gave it. Everything after the ref is joined back into one command line, so quote anything your local shell would otherwise eat — quotes, pipes and redirects never reach the sandbox unless you do.

shell
bzlabs exec scratch pip install requests
bzlabs exec scratch "cd /app && pytest -q"
bzlabs connect scratch                    # Ctrl-D to leave

The sandbox's exit code becomes the CLI's, so && and || work as usual. There is no file-copy command, and exec does not forward local stdin. Clone inputs from the network or embed a small encoded payload in the command. Captured stdout can be redirected locally:

shell
payload=$(base64 < ./train.py | tr -d '\n')
bzlabs exec scratch "printf %s '$payload' | base64 -d > /workspace/train.py"

bzlabs exec scratch cat /workspace/results.json > ./out.json

ports

Run a server inside the sandbox, then reach it from your machine. Create with --internet when you need this.

shell
bzlabs create --name web --internet --environment python
bzlabs exec web "python -m http.server 8000" &

bzlabs url web 8000                # link you can open or curl
bzlabs open web 8000               # open it in a browser

bzlabs forward web 8000           # localhost:8000 → sandbox:8000
bzlabs forward web 9000:8000       # localhost:9000 → sandbox:8000
# Ctrl-C stops the forward

the other two

every command

commanddoesaliases / flags
bzlabs authlog in, or show who you arelogin · logout · status
bzlabs createcreate a sandbox--machine --environment --name
bzlabs lslist your sandboxeslist
bzlabs status <id|name>show one sandbox in full
bzlabs connect <id|name>open an interactive shellshell
bzlabs exec <id|name> <cmd…>run one command, print the output
bzlabs run <file>execute code — no sandbox involved--language (required)
bzlabs rm <id|name>destroy a sandboxdelete · destroy
bzlabs url <id|name> <port>URL for a port inside the sandbox
bzlabs open <id|name> <port>open that URL in a browser
bzlabs forward <id|name> [local:]remoteforward a local port into the sandboxport-forward
bzlabs environmentswhat a sandbox can ship with
bzlabs machinesmachines and prices
bzlabs languageslanguage codes run accepts
bzlabs updateinstall a newer CLI
bzlabs versionprint the installed build-v
bzlabs helpthe same list, in your terminal-h

the same thing in python

sb is a Sandbox; the module-level calls use a default client built from your key. Same two words on both sides.

clipython
bzlabs createSandbox()
bzlabs exec <id|name> <cmd>sb.exec("cmd")
bzlabs connect <id|name>sb.terminal()
bzlabs rm <id|name>sb.delete()
bzlabs lsboltzlabs.sandboxes()
bzlabs status <id|name>boltzlabs.sandbox("scratch")
bzlabs url <id|name> <port>sb.url(port)
bzlabs environmentsboltzlabs.environments()
bzlabs machinesboltzlabs.machines()
bzlabs auth statusboltzlabs.me()
bzlabs run --language …boltzlabs.execute(…, language=…)
bzlabs languagesboltzlabs.languages()
bzlabs updatepip install -U boltzlabs