CLI
Install the sandal CLI, sign in from the browser, and drive sandboxes, runners, and simulators from your terminal.
Install#
npx sandal initsandal init installs the CLI into your project (or globally with -g), writes a sandal.json config stub, and walks you through authentication if you aren't signed in yet.
Prefer a global install:
npm install -g sandalSign in#
sandal loginsandal login opens your default browser to sandal.dev/cli-auth, pre-filled with a short-lived pairing code. Approve the request in the browser and the CLI receives a scoped API key over a local callback and stores it in your OS keychain (~/.sandal/credentials as a fallback on headless machines).
$ sandal login
Opening https://sandal.dev/cli-auth?code=WKMD-7X2Q in your browser...
Waiting for approval...
✔ Signed in as jakub@example.com (org: acme)On a machine without a browser (CI, SSH), pass --no-browser to get a code you approve from any other device:
sandal login --no-browser
# Visit https://sandal.dev/cli-auth and enter code: WKMD-7X2QYou can also skip the browser flow entirely with a long-lived key:
sandal login --api-key sk_live_...Global flags#
--json, emit machine-readable JSON instead of formatted tables on any command. Scripts and CI should always use this.--profile <name>, select a non-default credential profile (multiple orgs/accounts).--region <eu|us>, pin the request to a region instead of using the account default.-q, --quiet, suppress non-essential output.
Sandboxes#
sandal new#
Create a sandbox from a template.
sandal new --template node-24
sandal new --template python-3.12 --cpu 4 --memory 8gb --json{
"id": "sb_9f1c2a",
"status": "running",
"template": "node-24",
"region": "eu",
"createdAt": "2026-08-25T10:02:11Z"
}sandal list#
List sandboxes, runner pools, or devices.
sandal list sandboxes
sandal list sandboxes --status paused --json
sandal list devices
sandal list runnerssandal exec#
Run a command inside a sandbox and stream output.
sandal exec sb_9f1c2a -- pnpm test
sandal exec sb_9f1c2a --json -- pnpm test --reporter=jsonExit code is passed through, so sandal exec composes cleanly in CI scripts.
sandal ssh#
Open an interactive shell.
sandal ssh sb_9f1c2aWorks with any SSH-compatible tool via a local ProxyCommand, so scp, rsync, and VS Code Remote-SSH work unmodified once you add the printed Host block to ~/.ssh/config:
sandal ssh sb_9f1c2a --print-config >> ~/.ssh/configsandal expose#
Expose a port on the sandbox as a public HTTPS URL.
sandal expose sb_9f1c2a 3000$ sandal expose sb_9f1c2a 3000
✔ https://sb-9f1c2a-3000.sandal.runsandal pause / sandal resume#
Pause a sandbox to stop billing everything except storage, and resume it later from a warm snapshot.
sandal pause sb_9f1c2a
sandal resume sb_9f1c2aPaused sandboxes cost $0 in compute. Resume targets a warm claim under 200ms when capacity is available, falling back to a cold restore (<1s) otherwise.
sandal fork#
Fork a running or paused sandbox into a new, independent sandbox that shares the parent's filesystem state at fork time.
sandal fork sb_9f1c2a
sandal fork sb_9f1c2a --count 5 --jsonsandal snapshot#
Take a manual point-in-time snapshot, or list/restore existing ones. Sandal also takes incremental encrypted snapshots automatically every minute.
sandal snapshot create sb_9f1c2a --label "before-migration"
sandal snapshot list sb_9f1c2a
sandal snapshot restore snap_4471 --as newsandal extend#
Extend the TTL on a sandbox, runner reservation, or device session before it auto-expires.
sandal extend sb_9f1c2a --hours 4Account#
sandal api-key#
Manage API keys for scripts, CI, and SDKs, separate from the browser-based sandal login session.
sandal api-key create --name "ci-runner" --scope sandboxes:write
sandal api-key list --json
sandal api-key revoke key_88ab--json everywhere#
Every subcommand accepts --json and prints a single JSON object or array (never mixed with log lines, those go to stderr). This makes the CLI safe to pipe:
sandal list sandboxes --json | jq -r '.[] | select(.status=="running") | .id'