> ## Documentation Index
> Fetch the complete documentation index at: https://boxrouter.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Use the CLI

> Create, use, and destroy sandboxes from your terminal or a shell script, and call any API operation by name.

The `boxrouter` command runs one-off commands in a temporary sandbox, keeps a sandbox across
several commands, and wraps every API operation. Use its output in shell scripts: `create` prints
an ID, `read` writes raw bytes, and `api` prints JSON.

## Install

The installer sets up `uv` and a supported Python if you have neither, and installs the Python SDK as a
dependency. The CLI supports Python 3.11 to 3.14. Run the same command again to upgrade. To remove the
CLI, run the installer with `--uninstall`: `curl -fsSL https://boxrouter.ai/install | sh -s -- --uninstall`, or in
PowerShell, `& ([scriptblock]::Create((irm https://boxrouter.ai/install.ps1))) -Uninstall`.

```bash theme={"theme":"css-variables"}
# macOS and Linux
curl -fsSL https://boxrouter.ai/install | sh
# Windows, in PowerShell
irm https://boxrouter.ai/install.ps1 | iex
```

It reads `BOXROUTER_API_KEY` from the environment and sends requests to
`https://api.boxrouter.ai/api/v1`. Set `BOXROUTER_BASE_URL` or pass `--base-url` to use another
deployment. [Set Up Your Account](https://boxrouter.ai/docs/setup.md) covers the key and how compute is paid for.

## Update Notice

In an interactive terminal, the CLI may print one line on stderr after a command, such as
`Update available 0.6.117 -> 0.6.118. Run: curl -fsSL https://boxrouter.ai/install | sh`. The upgrade command
matches how you installed it. A CLI installed with the installer upgrades by running the installer again, because
`uv tool upgrade` cannot move a tool installed from release files. Other installs get a pointer to this page.

The check runs at most once every 24 hours in a background process, so no command waits for it. It sends one
anonymous HTTPS request to BoxRouter (`https://boxrouter.ai/cli/latest.txt`), a text file with the newest stable
version. BoxRouter sees your IP address and the HTTP client's user agent, and the request includes no account, key,
command, or version. Nothing is sent to PyPI.

Failures are silent. The line never appears when stdout or stderr is not a terminal, when `CI` is set, or with
`--json-errors`. Set `BOXROUTER_NO_UPDATE_CHECK=1` to turn it off for a session, or put `update_check = false` in
`~/.config/boxrouter/config.toml` to turn it off for good.

## Run One Command

```bash theme={"theme":"css-variables"}
boxrouter run 'echo hello'
```

Output

```text theme={"theme":"css-variables"}
hello
```

`run` creates a sandbox, runs the command, and requests deletion when the command exits.
It copies the command's stdout and stderr to yours and exits with the command's status. A status
outside 0 to 255 becomes 1, and the CLI prints `Remote exit code: N` to stderr.

A nonzero status can also mean a client or API error, so read stderr first. To keep the sandbox ID for
[checking cleanup](https://boxrouter.ai/docs/errors.md#when-cleanup-fails), create the sandbox yourself as shown in the next section.

## Keep a Sandbox Between Commands

`create` prints the sandbox ID and nothing else. Pass that ID to the other commands.

| Task | Command |
| - | - |
| Create | `boxrouter create` |
| Run a command | `boxrouter exec ID 'echo hello'` |
| Inspect | `boxrouter get ID` |
| Write a file up to 1 MiB | `boxrouter write ID --path /tmp/in.txt --body ./in.txt` |
| Read a file up to 1 MiB | `boxrouter read ID --path /tmp/in.txt` |
| Store a larger file | `boxrouter volumes put VOLUME_ID LOCAL PATH`, `boxrouter volumes get VOLUME_ID PATH LOCAL` |
| Destroy | `boxrouter destroy ID` |

`read` writes raw bytes to stdout, so redirect it to save a binary file. `write` takes `--body` from a
file, or from stdin with `-`, and fails above 1 MiB with `413 resource_limit`. Use a [Volume](https://boxrouter.ai/docs/volumes.md)
for larger files.

The script verifies a file round trip and destroys the sandbox on exit:

```bash theme={"theme":"css-variables"}
set -euo pipefail
id=$(boxrouter create)
trap 'boxrouter destroy "$id"' EXIT
printf 'from local\n' > in.txt
boxrouter write "$id" --path /tmp/in.txt --body ./in.txt
boxrouter exec "$id" 'wc -c /tmp/in.txt'
boxrouter read "$id" --path /tmp/in.txt | cmp - in.txt
```

Output

```text theme={"theme":"css-variables"}
11 /tmp/in.txt
```

If the response to `create` is lost, the sandbox may exist without an ID. Pass your own idempotency
key and a label; the CLI generates a new key for each call. Resend the same call to get the same
sandbox back, or find it by label.
[Recover from Errors](https://boxrouter.ai/docs/errors.md#recover-a-lost-creation-response) has the rules.

This Bash example needs `python3` on your PATH to make a unique key. It prints the key before creating
the sandbox, lists matching sandboxes, and requests deletion on exit. If creation fails without an ID,
use the printed key to retry the same request or find the sandbox by its `run` label.

```bash theme={"theme":"css-variables"}
(
  set -euo pipefail
  key="docs-$(python3 -c 'import uuid; print(uuid.uuid4().hex)')"
  labels="{\"run\":\"$key\"}"
  printf 'Recovery key and run label: %s\n' "$key"
  id=$(boxrouter create --idempotency-key "$key" --labels "$labels")
  trap 'boxrouter destroy "$id"' EXIT
  boxrouter api sandboxes list --label "run=$key" --lifecycle live
)
```

## Say What the Sandbox Needs

`create` and `run` take the same options. Repeat `--providers` to limit eligible providers to those
IDs in the listed order; workspace restrictions can preserve a different order. Repeat `--require NAME`
for each requirement that needs no settings, as in `--require pause_resume --require terminal`. Pass
everything else as JSON: `--requirements` for a requirement with settings, `--resources`, `--preferences`,
`--lifetime-seconds` and `--image`. `--require` and `--requirements` cannot be combined.

A JSON value is written inline, as `@FILE`, or as `-` for stdin. The CLI rejects a misspelt field
before sending any request and lists the fields the option accepts. `boxrouter create --help`
shows each shape.

To see what a request would match before you create anything, use `boxrouter match`. It takes the
same options except `--providers`; use `--preferences '{"only":["<PROVIDER_ID>"]}'` instead. It
creates nothing. For each eligible route it prints the rank, the reason, and the hourly list price,
and it lists each excluded route and why. [Routing](https://boxrouter.ai/docs/routing.md) explains the ranking, and
[provider support](https://boxrouter.ai/docs/provider-support.md) lists what each route offers.

A name in `preferences.only` that is not a provider ID fails with `400 unrecognised_word` and creates nothing;
`details.known` lists the valid names. To let BoxRouter select from all eligible providers, omit
`--providers` or `preferences.only`.

`--timeout-seconds` is the command's deadline, `timeout_seconds` in the API. At the deadline the command
is ended, its output so far is printed, and the CLI exits with status 124. [Run commands](https://boxrouter.ai/docs/commands.md#when-a-command-runs-too-long) says what the deadline stops; the
sandbox's lifetime is a separate clock ([Lifecycle](https://boxrouter.ai/docs/lifecycle.md)).

## Attach a Volume

`boxrouter volumes create NAME` creates a volume and prints it as JSON; repeat `--label KEY=VALUE` to tag it.
`create` and `match` take `--volume` as the volume ID, as `ID:ACCESS` with `read`, `write`, `shared` or `branch`,
as `ID@CHECKPOINT` to start from a saved checkpoint, or as `ID@CHECKPOINT:ACCESS`. JSON still works, and is
the way to set `path`.

```bash theme={"theme":"css-variables"}
VOLUME_ID=$(boxrouter volumes create project-files --label team=ml | jq -r .id)
boxrouter create --volume "$VOLUME_ID"
boxrouter create --volume "$VOLUME_ID@3:read"
```

[Volumes](https://boxrouter.ai/docs/volumes.md) covers access modes, checkpoints, and uploads.

## Read Errors

An API or transport error exits with 1, and an invalid option value exits with 2. The message names
the request ID, the operation ID if one exists, the outcome, the `Retry` value, and a fix. When a request carried an
idempotency key, the message prints it too.

The CLI resends a `Retry: safe` error twice by itself, with the same key, before it reports the error.

Keep these values: you need them to check whether the work started before you retry. Pass
`--json-errors` to get the same fields as one JSON object on stderr, which `jq` can read.

`--show-response-headers` writes the HTTP status and headers to stderr, including `x-request-id`. Repeat a failed
call only as `Retry` allows; [Recover from Errors](https://boxrouter.ai/docs/errors.md#read-the-retry-field) says what each value means.

## Call Any API Operation

`boxrouter api GROUP OPERATION` runs any operation in the [API reference](https://boxrouter.ai/docs/api-overview.md) and prints
the JSON response. Path parameters are positional arguments, query parameters are options, and a
request body is `--body`, written like the JSON options above.

```bash theme={"theme":"css-variables"}
boxrouter api sandboxes list --state running --limit 100
```

These operations have no dedicated command; call them through `boxrouter api`:

* Pause, resume, and extend: [Pause and resume](https://boxrouter.ai/docs/pause-resume.md).
* Volume checkpoints, release and delete: [Volumes](https://boxrouter.ai/docs/volumes.md).
* Connecting, testing and rotating provider keys: [Provider keys](https://boxrouter.ai/docs/provider-keys.md#connect-from-the-cli).

## Open a Terminal

```bash theme={"theme":"css-variables"}
boxrouter terminal ID
```

This opens an interactive shell on routes that support terminals. Leaving the shell leaves the sandbox running, so destroy it with `boxrouter destroy ID` when you finish. [Connect](https://boxrouter.ai/docs/connect.md) covers terminals and previews.

## Next Steps

> [card] [Find an SDK or CLI Call](https://boxrouter.ai/docs/sdk.md)
>
> The CLI command for each sandbox task.

> [card] [Connect to a Sandbox](https://boxrouter.ai/docs/connect.md)
>
> Terminals and previews.

> [card] [Recover from Errors](https://boxrouter.ai/docs/errors.md)
>
> When resending a failed call is safe.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.