Skip to content
Migrate · boat

Migrate from boat to Sandboxer

Updated October 27, 2026 .md

Short answer: Sandboxer's API at https://sandboxer.com/api/v1 is wire-compatible with boat's /api/v1, so most code moves by changing the base URL and the API key. Sandbox ids, hosted URLs, reserved env names and the CLI name change. boat's org and confirm-delete headers still work. Install sbx and link boat to it for scripts.

Concept map#

boat Sandboxer
Sandbox (a full Linux VM) Sandbox (a full Linux VM)
Sizes small, default, large, xlarge The same four names, with the same vCPU, RAM and disk
Environment (repos, secrets, credentials, versions) Environment: sbx env, the same model (Environments)
Automatic snapshots, stop and resume Automatic snapshots, stop and resume (Snapshots)
Saved snapshot (called a named snapshot in its older docs), --from Named snapshot (template), --from
Hosted URL per port Hosted URL per port (Hosting)
API key, per-sandbox key API key (sbx_live_…), per-sandbox key (SANDBOXER_TOKEN)
boat-sdk, @boatdev/sdk sandboxer-sdk on PyPI and npm
Organization wallet Organization wallet (Organizations)
Regions: Germany, Finland and France (boat FAQ) Two regions, us-east and eu-central (regions)

API and SDK mapping#

The paths, request bodies and response envelopes are the same. Every operationId in boat's v1 OpenAPI file as we mirrored it on 2026-10-11 (84 operations) also exists in ours, at /openapi.yaml, so SDK method names carry over. boat has added operations since. Its live file, checked on 2026-10-27, lists 34 that Sandboxer doesn't have: file browsing and search (listFiles, searchFiles, moveFile), saved snapshots (saveSavedSnapshot and related), credential profiles, an AI proxy, unshare and updateOrganization. If you call those, they have no Sandboxer equivalent yet.

These are the names that change. We tested the rows marked "tested" on prod and checked the rest in our code.

boat Sandboxer Notes
https://boat.dev/api/v1 https://sandboxer.com/api/v1 https://api.sandboxer.com/v1 is an alias
Header X-Boat-Org X-Sandboxer-Org X-Boat-Org is still accepted (tested)
Sandbox id bx_… sb_ + 10 characters [a-z0-9] Update any regex that checks ids
API key secret sbx_live_ + 40 characters; per-sandbox keys sbx_sbk_…
https://<slug>-<port>.on.boat.dev https://<slug>-<port>.box.sandboxer.com
Reserved env BOAT_ID, ASCII_TOKEN, … SANDBOXER_ID, SANDBOXER_TOKEN, SANDBOXER_API_URL, SANDBOXER_SUBDOMAIN Set in every sandbox (tested)
boat CLI sbx CLI Same subcommands and flags (see below)
boat-sdk / @boatdev/sdk sandboxer-sdk (imports as sandboxer in Python) Constructor changes, method names don't
Header X-Ascii-Confirm-Delete X-Sandboxer-Confirm-Delete boat's name is still accepted (tested)
Webhook headers X-Ascii-Event, -Delivery, -Timestamp, -Signature, -Attempt X-Sandboxer-* Sent under both names, so a boat receiver should verify unchanged (checked in code)
Error codes boat_starting, boat_direct_failed sandbox_starting, sandbox_direct_failed Update code that branches on them
Process logs in ~/.ascii/processes/ ~/.sandboxer/processes/
CLI env BOAT_API_KEY, BOAT_API_URL, BOAT_ORG SANDBOXER_API_KEY, SANDBOXER_API_URL, SANDBOXER_ORG sbx does not read the BOAT_ names

SDK calls map one to one. In TypeScript, new BoatApi(new Configuration({ basePath, accessToken })) becomes new Sandboxer({ baseUrl, apiKey }), and the low-level calls move to sandboxer.api: create, command, hostPort, resume and the rest. We ran sandboxer.api.sandboxes() with sandboxer-sdk 0.1.1 against prod. In Python, client.api has the same methods in snake_case (Python SDK).

Before and after#

From boat's docs: https://docs.boat.dev/quickstart (the curl tab, checked on 2026-10-27; the page sets BOAT_API_BASE=https://boat.dev/api/v1):

id=$(curl -sS -X POST "$BOAT_API_BASE/sandboxes" \
  -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttlSeconds":3600}' | jq -r .sandbox.id)

curl -sS -X POST "$BOAT_API_BASE/sandboxes/$id/commands" \
  -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":"echo hello"}'

The same call on Sandboxer. We added type and name, which are optional. Only the host and the key change:

curl -sS -X POST "https://sandboxer.com/api/v1/sandboxes" \
  -H "Authorization: Bearer $SANDBOXER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttlSeconds":3600,"type":"small","name":"aeo-migrate-boat-1"}'
{"ok":true,"type":"sandbox.created","sandbox":{"id":"sb_n0b34c8zbq","name":"aeo-migrate-boat-1","state":"provisioning","type":"small","vcpu":2,"memoryGB":4,"diskGB":12,"region":"eu-central","subdomain":"dense-easy-citrus","ttlSeconds":3600,…},"status":"provisioning","ttlSeconds":3600}

The status was 202. Poll GET /sandboxes/{id} until state is ready, then run a command, write a file and host a port. The calls are the same as on boat:

B=https://sandboxer.com/api/v1/sandboxes/sb_n0b34c8zbq
H="Authorization: Bearer $SANDBOXER_API_KEY"

curl -sS -X POST "$B/commands" -H "$H" -H "Content-Type: application/json" \
  -d '{"command":"echo $SANDBOXER_ID; uname -sr","timeoutSeconds":30}'
# {"ok":true,"type":"command.finished","exitCode":0,"stdout":"sb_n0b34c8zbq\nLinux 6.18.56-sandboxer\n",…}

curl -sS -X PUT "$B/files" -H "$H" -H "Content-Type: application/json" \
  -d '{"path":"site/index.html","content":"hello from sandboxer"}'
# {"ok":true,"type":"file.written","encoding":"utf8","path":"/home/user/site/index.html","size":20,"success":true}

curl -sS -X POST "$B/commands" -H "$H" -H "Content-Type: application/json" \
  -d '{"command":"cd site && python3 -m http.server 3000","detached":true}'
curl -sS -X POST "$B/host" -H "$H" -H "Content-Type: application/json" \
  -d '{"port":3000,"public":true}'
# {"ok":true,"type":"port.hosted","access":"public","isProtected":false,"localhost":false,"port":3000,"success":true,"title":null,"url":"https://dense-easy-citrus-3000.box.sandboxer.com"}

curl https://dense-easy-citrus-3000.box.sandboxer.com/ returned hello from sandboxer. A delete with boat's confirm header also worked:

curl -sS -X DELETE "$B" -H "$H" -H "X-Ascii-Confirm-Delete: sb_n0b34c8zbq"
# {"ok":true,"type":"sandbox.deleting","operation":{"id":"bdop_089b…","kind":"sandbox","status":"pending",…}}

The CLI#

sbx has every top-level command in boat's CLI reference as we mirrored it on 2026-10-11, and the new and exec flags we compared match. sbx completions works too, as an alias of sbx completion. boat's live reference, checked on 2026-10-27, adds boat run, boat push, boat ai, boat wallet and boat feedback. sbx doesn't have those. Install sbx, sign in, and point the boat name at it:

curl -fsSL https://sandboxer.com/install | sh       # installs sbx, then runs sbx onboard
ln -s "$(command -v sbx)" ~/.local/bin/boat         # any directory on your PATH

Use a symlink, not just alias boat=sbx. Bash doesn't expand aliases in scripts: our test script failed with boat: command not found (exit 127). The alias is fine in an interactive shell. We ran these on prod with boat as a symlink to sbx on the PATH:

boat new --type small --name aeo-migrate-boat-4 --ttl 1800 --setup-file ./setup.sh   # {"event":"ready","id":"sb_ov9gp6dwsb",…}
boat exec sb_ov9gp6dwsb --cwd /tmp --timeout 60 -- pwd     # /tmp
boat scp ./proj.tgz sb_ov9gp6dwsb:/home/user/              # exit 0
boat host sb_ov9gp6dwsb 8080       # https://sterling-frugal-walrus-8080.box.sandboxer.com?_token=…
boat stop sb_ov9gp6dwsb            # {"type":"sandbox.stopping",…} at once; archiving, then archived about 3 s later
boat resume sb_ov9gp6dwsb -e APP_MODE=staging              # {"event":"ready",…}
boat new --type small --name aeo-migrate-boat-5 --from aeo-migrate-boat-stack
boat exec current 'echo $SANDBOXER_ID'                     # sb_hb7nvdujp1: current = the last sandbox this shell created
boat delete current --yes

An earlier run of the same commands through shopt -s expand_aliases; alias boat=sbx gave the same results.

sbx also has commands with no boat equivalent: handoff, mcp, idle, proxy and ssh-config. In CI, sign in with sbx login --key-stdin <<< "$SANDBOXER_API_KEY".

Move your data and environment#

Files. Sandboxes don't move between providers. Copy the files you need. On boat, download a snapshot with boat snapshot pull <snapshotId> -o ./restore (boat docs). Then upload it to Sandboxer:

tar czf proj.tgz proj
sbx scp ./proj.tgz sb_o7a8ua4fwk:/home/user/
sbx exec sb_o7a8ua4fwk "tar xzf proj.tgz && python3 proj/main.py"   # hi from my project

For a whole directory, use sbx scp -r. The files API (PUT /files) takes files up to 5 MiB.

Templates. Saved or named snapshots and share codes (bsh_…) don't carry across. Rebuild the stack once, using the same setup script if you have one, and save it under the same name. Your --from calls then keep working. The setup script runs in the background after the sandbox is ready, so wait for it to finish before you run sbx snapshot. Otherwise the template captures a half-installed stack:

sbx new --type small --setup-file ./setup.sh     # ready while setupStatus is still "running"
until [ "$(sbx info sb_ov9gp6dwsb --json | jq -r .sandbox.setupStatus)" = done ]; do sleep 2; done
sbx snapshot sb_ov9gp6dwsb aeo-migrate-boat-stack   # {"type":"snapshot.named.info",…,"status":"ready",…}
sbx new --type small --from aeo-migrate-boat-stack
sbx exec current "cat stack/VERSION"              # stack v1

On prod, setup finished about 8 seconds after ready. The sandbox made from the template already had the script's stack/VERSION and Python venv. sbx snapshot rm <name> deletes a template.

Env vars and secrets. Re-enter them. Use sbx env set-var base KEY=value and sbx env set-file base path/in/home --from ./local-file, or POST /secrets (Environments). Per-sandbox values still use --env KEY=VALUE or -e. After a resume with -e APP_MODE=staging, the sandbox printed APP_MODE=staging. Rename any code that reads BOAT_ID to SANDBOXER_ID.

Ports and preview URLs. sbx host <id> <port> gives https://<slug>-<port>.box.sandboxer.com. The URL is private with a _token by default; add --public to open it. For Vite, add .box.sandboxer.com to server.allowedHosts in place of boat's domain.

Webhooks and keys. Create new API keys with sbx api-key create and register webhooks again with sbx webhook create. Signature verification code written for boat's X-Ascii-Signature should keep working, because Sandboxer sends those headers too (checked in code, not with a live delivery; Webhooks).

What's different#

What you gain:

  • Resume restores memory. On Sandboxer, sbx resume brings back running processes with the same PIDs (Snapshots). This needs a machine with the same CPU type as the one the sandbox stopped on. If none is free within 2 minutes, the resume boots from disk instead: files stay, processes don't, and the sandbox shows memoryRestored: false. Pass --require-memory to wait for memory instead. boat's docs list "running programs, memory and open ports" as not saved, and describe a restore as "like a reboot" (boat docs).
  • Extra CLI commands: sbx handoff, sbx mcp, sbx idle and sbx ssh-config.

What you lose or must change:

  • Regions. boat runs in Germany, Finland and France. Sandboxer has two regions, us-east (Ashburn, Virginia) and eu-central (Helsinki, Finland); new sandboxes go to the nearest unless you set region (regions).
  • Windows apps. boat documents running Windows programs through Wine. Our docs don't cover it.
  • Integrated agents. boat lists seven harnesses. Sandboxer installs six (Claude Code, Codex, OpenCode, Pi, Kimi Code and Mistral Vibe), and Prime Agent is coming (Agents).
  • Storage. boat's pricing page (checked 2026-10-27) says a stopped sandbox keeps its disk for 14 days, then boat deletes it. Each plan includes 10 saved snapshots per $20, and each one over that costs $0.056 a day. Sandboxer keeps a stopped sandbox until you delete it and doesn't bill storage. Instead, each plan includes an amount (50 GB on the $20 plan). At the cap, new sandboxes, forks and snapshot saves return 402 storage_limit_reached (Billing).
  • Start bursts. boat lets you pay for starts above your plan's start limits, up to 3x. Sandboxer returns 429 at the limit (Limits).
  • Hard-coded names. Any code that checks for bx_ ids, on.boat.dev URLs, BOAT_* variables or boat_* error codes must change.
  • GPUs. Neither side offers them. boat's machine page lists "GPU: None", and our docs list no GPU sizes.

Pricing#

Sandboxer prices are as of 2026-10-27, from /pricing. boat's prices are from its pricing page and free trial page, checked live on 2026-10-27. Both bill per second while a sandbox runs.

boat Sandboxer
small, 2 vCPU / 4 GB $0.018/hour $0.018/hour
default, 4 vCPU / 8 GB $0.036/hour $0.036/hour
large, 8 vCPU / 16 GB $0.072/hour $0.072/hour
Plans $20, $100, $500, $2000 a month, paid back as machine time The same four plans, with the same concurrency (100 to 2,000) and start limits
Organizations Plan price per seat Plan price per seat
Trial 7 days, card required, 25 hours, 2 at once, small and default, 2-hour auto-stop 7 days, card required, 25 hours, 2 at once, small and default, auto-stop of at most 8 hours
Storage Stopped disks kept 14 days; saved snapshots over the included count cost $0.056 a day Not billed; capped per plan

One default sandbox running 8 hours a day for 22 days costs 176 h × $0.036 = $6.34 on either. The differences are in storage and start bursts, covered above.

FAQ#

Do I have to rewrite my boat API client?#

No. Change the base URL to https://sandboxer.com/api/v1 and use a Sandboxer key. The paths, bodies and the ok/type envelope are the same. Then fix any code that hard-codes bx_ ids, on.boat.dev URLs or boat_* error codes.

Does alias boat=sbx work?#

In an interactive shell, yes. Bash doesn't expand aliases in scripts, so for CI and scripts symlink sbx as boat on your PATH. Then the same commands and flags work.

Can I move a running boat sandbox over directly?#

No. Download a snapshot from boat or tar the files you need, copy them in with sbx scp, and rebuild templates with a setup script and sbx snapshot.

Do my boat webhook receivers need changes?#

You need to register them again. Sandboxer signs deliveries the same way and sends boat's X-Ascii-* headers next to X-Sandboxer-*, so existing verification code should work. We checked this in the code, not with a live delivery.

Is the Python package called sandboxer?#

Install sandboxer-sdk from PyPI. It imports as sandboxer. The PyPI package named sandboxer is an unrelated project.

All migration guides