# CLI reference

https://instabox.sh/docs/cli.md

Every command. Add --help to any of them for the details.

### Auth
```
login [--provider github|google]   sign in with a device code
login --agent [--name N] [--scope S ...] [--expires-in-days N] [--no-wait]
                                   agent sign-up with no browser: print a claim page and code for the
                                   account owner, wait for their approval, store the API key they grant
claim <code> [--scope S ...] [--expires-in-days N] [--deny] [--yes]
                                   signed in: approve (or deny) an agent that printed <code>
logout                             revoke and forget stored credentials
whoami                             show the signed-in user
```

### Machines
```
templates [--json]
ls [--meta K=V ...] [--json]          --meta: only machines with that metadata (AND)
create --name N [--template T | --snapshot S]
       [--auto-suspend 10m|0] [--delete-after 1h] [--meta K=V ...] [--no-wait] [--fail-fast] [--setup-file PATH|-] [--wait-setup]
       [--env K=V ...] [--env-file PATH|-] [--devcontainer [PATH|DIR]] [--json]
                                      --env, --env-file: environment variables for the whole machine (readable inside it; not for secrets)
                                      --fail-fast: when no host has room, fail at once and create nothing;
                                      create exits 75 when no host has room (retryable no_capacity), with or without --fail-fast;
                                      run exits 75 only with --fail-fast, else 125 (retry later, or another size or template)
                                      --setup-file: run a script (at most 64 KB) once it runs;
                                      a failed script exits with its code and keeps the machine;
                                      get --json shows metadata.setupStatus, setupExitCode, setupLog
                                      --devcontainer: apply a devcontainer.json (default .devcontainer/devcontainer.json):
                                      push the workspace into workspaceFolder, containerEnv/remoteEnv, features,
                                      onCreate/updateContent/postCreate/postStartCommand; image/build not booted
auto-suspend <id-or-name> <duration>|0   pause it once idle that long; 0 turns it off
max-run <id-or-name> <minutes>        pause after that many running minutes, even while active; -1: unlimited, 0: now
                                      (free plan: fixed at 480); the first of the idle and runtime limits pauses it
get <id-or-name> [--json]
rename <id-or-name> <new-name>        in place, in any state: the ID, port URLs and SSH certificate stay;
                                      renames its Host block in ~/.ssh/config too
exec <id-or-name> [--cwd D] [--env K=V ...] [--timeout S] [--stdin] [--stream] [--json] -- <cmd...>   run one command; exits with its code;
                                      one argument is a shell string ('a | b'), several run as that exact argv;
                                      --timeout S: whole seconds before the command is killed (default 60, at most 600; exit 124);
                                      --stdin: pipe input to the command, up to 5 GiB, binary too (larger than 1 MiB
                                      or not UTF-8: the CLI uploads it as a file first); a CLI built before 2026-10-26
                                      refuses stdin past 1 MiB: run instabox update;
                                      --stream: print the output as the command writes it, not when it ends (no 1 MiB cap;
                                      with --json, one {type: stdout|stderr, data} object per line, then {type: exit, exitCode, ...});
                                      --json: {stdout, stderr, exitCode, signal, timedOut, truncated, invalidUtf8, durationMs};
                                      exits 65 when the command exited 0 but its output was binary (copy files with pull or cp)
exec --background <id-or-name> [--cwd D] [--env K=V ...] [--json] -- <cmd...>
                                      start the command detached (no time limit) and print its job ID (--json: {jobId, pid})
jobs ls <id-or-name> [--json]         list its background jobs: ID, status (running, exited(N), lost), start time, command
jobs logs <id-or-name> <job> [-f] [--json]   print a job's output; -f follows it until the job ends, then exits with its exit code
jobs wait <id-or-name> <job> [--timeout D] [--json]   wait for a job to end and exit with its exit code; after --timeout, exit 124 and leave it running
jobs kill <id-or-name> <job> [--signal SIG] [--json]   send a signal (default TERM) to the job's process group
ps <id-or-name> [--all] [--json]      list its processes (pid, ppid, user, CPU and memory %, elapsed, command); --all adds kernel threads
history <id-or-name> [--limit N] [--cursor C] [--json]   the commands run in it through exec (by anyone, also while it is stopped),
                                      newest first: ID, start time, duration, exit code, command; the server keeps the newest 200
                                      with the first 64 KiB of each output stream; --limit 1-200 (default 50), --cursor: the next page
output <id-or-name> [<command-id>] [--json]   print a past command's stored stdout and stderr (default: the last one)
                                      and exit with its exit code, as exec did; --json: the record with stdout and stderr
run [--template T | --from SNAPSHOT] [--name N] [--push DIR] [--cwd D] [-e K=V ...] [--timeout 60s] [--keep] [--fail-fast] [--setup-file PATH|-] [--devcontainer [PATH|DIR]] [--json] -- <cmd...>
                                      one-shot: create a machine, run the command, delete the machine
                                      (also on error or Ctrl-C); exits with its code (124: timed out);
                                      run's own failures: 75 --fail-fast and no room, 125 API, network, create, upload or cleanup error, 130 interrupted;
                                      --push: upload DIR's unignored files into the working directory first;
                                      a killed CLI's machine is deleted by the server at its deleteAt, 5 minutes after --timeout
                                      (10 with --push); a failed cleanup delete is retried 5 times with backoff;
                                      --keep keeps it and prints its name to stderr (not when the create failed)
files <id-or-name> [PATH] [--json]   list a live directory or print a text file (as root)
push <id-or-name> LOCAL [REMOTE] [-p] [--all]   copy a file or directory in (default: HOME); a file streams up to 5 GiB
                                      (64 MiB and up: 4 parallel PUTs);
                                      in a git repo, tracked files only (--all: untracked too); -p creates REMOTE's parents
pull <id-or-name> REMOTE [LOCAL]      copy a file or directory out (default: the current directory); a file streams, any size
cp [-r] [-p] SRC DST                  scp-style: cp ./x.tgz box:/data/home/x.tgz, cp box:/data/home/x.tgz .
                                      directories (push, pull, cp): SRC into an existing DST lands in DST/<name>
                                      (push box app /data/w gives /data/w/app); SRC/, SRC/. or . copies the contents
                                      into DST itself (push box app/ /data/w puts app's files in /data/w);
                                      with DST omitted it keeps its name (push box . gives HOME/<dirname>)
start|stop|suspend|resume|rm <id-or-name> [--no-wait]   suspend: pause
restart <id-or-name> [--timeout 60s] [--json]   power-cycle (recover works too): stop, then start;
                                      disks kept; a guest not shut down by --timeout is killed
restore <id-or-name> [--snapshot S] [--stopped] [--yes] [--json]   disks back to a snapshot's, in place: same ID,
                                      name, hostname and URLs; everything since the snapshot is lost (asks first)
resize <id-or-name> --disk N | --vcpus N --mem-mib M [--restart] [--json]
                                      grow or shrink /data to N GB (paid plans; a running machine's disk grows live), or give it
                                      another of the plan's sizes; a shrink or a new size applies while it is stopped:
                                      --restart stops, resizes and starts it (files stay, processes do not)
disk <id-or-name> [--json]            how full /data and / are (ok below 80%, high from 80%, critical from 90%, full); never wakes it
metrics <id-or-name> [--since 1h] [--json]   CPU, memory, network and disk I/O, recorded every 30s (--since up to 24h); never wakes it
logs <id-or-name> [--tail N] [-f] [--json]   console log and the recent operations, in any state; -f follows it; never wakes it
rm <id-or-name> --yes                 rm and the other rm commands ask y/N first; --yes (-y, --force) skips it, needed off a terminal
usage [--json]                        your machine and snapshot counts, and limits
```

### Large uploads
For large uploads use `instabox push` or `instabox cp` (the HTTP API, parallel-friendly: a file of 64 MiB or more goes up as 4 parallel PUTs), not scp or rsync over `ssh.instabox.sh`, which keeps about 2 MiB in flight per connection and so slows with distance. Measured: at a 5 ms round trip both paths do ~104 MB/s; at 305 ms the API does 11-13 MB/s per stream and SSH 3-6 MB/s; 4 parallel API PUTs did 39 MB/s total.

`instabox exec box --stdin -- <cmd>` takes up to 5 GiB of input, binary too: input larger than 1 MiB, or not UTF-8, goes up as a file first (its SHA-256 checked) and the command reads it from there, so `instabox exec box --stdin -- tar -xz -C /data/workspace < src.tgz` works for a large bundle. A CLI built before 2026-10-26 refuses stdin past 1 MiB (`stdin is larger than 1048576 bytes`): run `instabox update` instead of splitting the input. A big file can also go in with push first, then exec reads it: `instabox push box bundle.tgz /tmp/`, then `instabox exec box -- tar -xzf /tmp/bundle.tgz -C /data/workspace`. Past 5 GiB, `split` the file, push the parts and `cat` them together in the box.

### Snapshots
```
snapshot save <id-or-name> --name N [--description D] [--root-only]   --root-only: installed software, not home and workspace
snapshot ls [--json]
snapshot files <id-or-name> [PATH] [--json]   list a directory of its installed files or print a text file
snapshot rm <id-or-name> [--yes]
fork <id-or-name> [--name N] [--count N] [--keep-snapshot] [--json]
                                      copy into --count (1-5) machines on its host (no snapshot; else snapshot + create)
```

### SSH
```
ssh <id-or-name> [--no-paste] [-- ssh args or command]
ssh-config <id-or-name>   print a ~/.ssh/config Host block
ssh-cert <id-or-name>     refresh the SSH certificate only
ssh-token <id-or-name> [--ttl 7d] [--mosh] [--json]   print a plain ssh (or mosh) command (no CLI or key needed)
ssh-token --revoke <id-or-name>              revoke the machine's ssh commands
mosh <id-or-name> [-- command]               like ssh, with mosh (needs mosh installed)
port-forward <id-or-name> [LOCAL:]REMOTE [...] [--remote R [--local L]] [--address A]
                                      forward local ports over SSH until Ctrl-C (forward works too)
```

### Ports
```
port publish <id-or-name> <port> [--name NAME] [--private|--public] [--rotate-token] [--tcp] [--json]
                                      give a port an HTTPS URL until you unpublish it
                                      (--private: only the printed link with its _token opens it;
                                      --tcp: a public raw TCP service at a TLS address (SNI))
port ls [<id-or-name>]                list published ports of one or all machines
port unpublish <id-or-name> <port>    remove the URL and close its connections
port setup <id-or-name>               install the instabox-port command in the machine
```

### Orca
```
orca connect <id-or-name> [--scope runtime|mobile]   print a one-time Orca connection (JSON)
```

### Secrets
```
secrets set NAME [--host HOST] [--ai PROVIDER] [--org ORG]    read a value from stdin or a hidden prompt
secrets import FILE [--host HOST] [--ai PROVIDER] [--org ORG] atomic import; use - for stdin
secrets ls [--org ORG] [--json]               metadata only
secrets rm NAME [--org ORG] [--yes]
secrets show <id-or-name>                     selection and delivery status
secrets select <id-or-name> --secrets all|none|NAMES
secrets push <id-or-name>                     refresh delivery on a running machine
create ... [--secrets all|none|NAMES]
```
`secret` aliases `secrets`. Org commands default to your personal org (with `INSTABOX_API_KEY`, the key's org). Setting, importing and removing values require the org owner or admin: a signed-in session or a workspace's `workspace:admin` key in `INSTABOX_API_KEY`. `--host` can be repeated for a Secret used with several hosts; without it, every public HTTPS destination is allowed. `--ai anthropic|openai|gemini|openrouter` adds that provider's API host. Names in selection flags can be comma-separated or a JSON array. `--no-vault` disables only personal Vault delivery. To disable both stores on a fresh sandbox, also pass `--secrets none`.

### Webhooks
```
webhooks create URL [--event TYPE ...] [--description D] [--disabled] [--org ORG]   prints the secret once
webhooks ls | get ID | update ID [--url U] [--event T ...] [--enable|--disable] | rm ID
webhooks rotate ID | test ID | deliveries ID [--limit N] | retry ID DELIVERY | events
```
`webhook` aliases `webhooks`. Org commands default to your personal org (with `INSTABOX_API_KEY`, the key's org). See [Webhooks](https://instabox.sh/docs/webhooks.md) for the events and signature.

### Vault
Vault remains available for personal env and file delivery, with metadata selectors. Use `set`, `set-file`, `import`, `ls`, `rm`, `show` and `push`. Org Secrets is independent; `--no-vault` does not clear Secret selections. Export recovers items retained by an earlier explicit migration and contains real values; keep the output private.
```
vault set NAME                            read an env value from stdin or a hidden prompt
vault set-file PATH                       read file contents from stdin
vault import FILE                         import a .env file
vault ls [--json]
vault rm NAME [--yes]
vault show <id-or-name>                    inspect matching items and last delivery
vault push <id-or-name>                    refresh Vault delivery
vault export NAME --out recovered.json    create a new private file; never print the value
```
The CLI warns about a future Vault retirement, but these commands remain supported. Export is limited to retained migration items; regular reads return metadata only.

### API keys
```
keys create --name N [--scope S ...] [--expires-in-days N]   prints the secret once
keys ls [--json]
keys rm <key-id> [--yes]
```
With `INSTABOX_API_KEY=ibk_...` (and optionally `INSTABOX_API_URL`) set, every other command uses the key instead of a sign-in.

### Feedback
```
feedback --type T --component C --title TEXT --detail TEXT [--json]
                                      report a problem with instabox itself to the instabox team;
                                      T: bug, feature-request, friction, other; C: cli, api, sandbox, dashboard, skills, docs, other
```

### Output
`--json` works on every command (each command's `-h` lists it); `-o json`, `--output json` and `--format json` are the same. The only exceptions are `ssh`, `mosh` and `port-forward`: they are interactive and refuse it with the JSON error code `json_unsupported` (use `exec --json`). With JSON output a failure prints `{"error":{"code","message",...}}` on stdout instead of text on stderr and still exits non-zero (2 for usage errors, code `usage`); API errors keep the server's `code`, `status`, `requestId` and `retryable` (present when true). When stdout is not a terminal (a pipe or a file), the list and status commands print JSON without it: `ls`, `get`, `templates`, `usage`, `snapshot ls`, `port ls`, `keys ls`, `secrets ls`, `vault ls` and `workspace ls|get|members|invites`. `INSTABOX_NO_AUTO_JSON=1` or `--output table` keeps the table. Every other command (`exec`, `ssh`, `files`, `create`, ...) prints the same output piped or not.

### Exit codes
A failed command exits with a code for the kind of error, so a script can tell them apart; each is non-zero, so `if ! instabox ...` and `$? -ne 0` checks work as before. `--json` errors exit the same way, with the same `{"error":{...}}` output.

| Code | Meaning |
| --- | --- |
| 0 | success |
| 1 | any other error |
| 2 | usage: an unknown command or flag, a bad flag value, a missing argument |
| 3 | auth: not signed in, a bad, expired or revoked key or session, no permission (`401`, `403`) |
| 4 | not found: no such machine, snapshot, key or other resource (`404`) |
| 5 | quota or plan limit: `quota_exceeded`, `credit_exhausted`, `spend_limit_reached`, `spec_not_allowed`, `template_not_allowed`, `template_limit`, `rate_limited` and other `402` and `429` errors |
| 75 | retryable: a `retryable` error such as `no_capacity` or `host_unavailable`, or another server error (`5xx`); try again later |

Commands that run something exit with its code instead, such as `exec`, `ssh`, `jobs wait`, `jobs logs -f`, `agent prompt -f`, `output` and `run` (whose own failures have their own codes: 2, 75, 125, 130).

### The CLI itself
```
version                   print the CLI version
update                    replace this CLI with the latest instabox npm package (self-update works too)
completion bash|zsh|fish|powershell   print a tab-completion script for commands, flags and machine names
```
`update` downloads the latest version of the `instabox` npm package (the build `npx instabox@latest` installs), checks it against the registry's integrity hash and swaps it in place; it says "already up to date" when the CLI already is that version. `INSTABOX_NPM_REGISTRY` names a registry mirror. Once a day (cached in `update-check.json` in the config directory, with a 1.5-second limit; an unreachable registry is ignored) the CLI compares its version with the registry's latest, and while they differ every command except `update` ends with one line on stderr, never stdout (`ssh`, `mosh` and `port-forward` print it before they connect): `instabox <version> is outdated (latest <npm version>): run instabox update`. A `--json` failure leaves the line out (its stderr stays empty), and `instabox version --json` adds `"outdated"` (and `"latest"` when it is true). Development builds are not checked. `INSTABOX_NO_UPDATE_CHECK=1` turns the check and the line off. To complete as you type, add `eval "$(instabox completion bash)"` to `~/.bashrc` or `eval "$(instabox completion zsh)"` to `~/.zshrc` (`instabox completion help` covers fish and PowerShell). Machine names come from your machine list, which never wakes a machine.

### Global flags
```
--server URL              API server (env INSTABOX_SERVER or INSTABOX_API_URL)
--ca-cert FILE            extra CA certificate for the server (env INSTABOX_CA_CERT)
--insecure-skip-verify    do not verify the server certificate (development only)
```
