# The CLI

`sandboxrouter` is the Sandbox Router command line. Use it to create and run sandboxes, move files in and out of them, take their snapshots, deploy MCP servers, push images, add connectors and manage billing from a terminal or a script. Each command calls one route of the same API as the console ([The API](/docs/api)), with an API key from your workspace.

## Install the CLI

The CLI needs Node 22 or later. Install the `@sandboxio/cli` package globally:

```sh
npm i -g @sandboxio/cli
```

> The package isn't on npm yet. Once it's published, this command installs it.

This installs the `sandboxrouter` command. To list every command and option, run `sandboxrouter help`, or `sandboxrouter` on its own. Adding `--help` to any command does the same.

Some commands use other tools: `sandboxrouter registry login` runs Docker.

## Connect the CLI to your workspace

The CLI doesn't sign in with your email and password. It uses an API key, which belongs to one workspace: every command acts in the workspace of its key. The CLI also keeps that workspace's tenant ID, which names your images in the registry.

### Create an API key

1. Sign in to the [console](https://console.sandboxrouter.io/) and open **Settings**.
2. Under **Workspace**, copy the **Tenant ID**.
3. Under **API keys**, enter a **Key name**, such as `my laptop`. If you're an owner and want to use `sandboxrouter billing`, check **Billing access**.
4. Choose **Create key**, then copy the key. It starts with `ck_`, and the console shows it only once.

Owners and operators can create keys. Viewers can't.

### Configure the CLI

Save the tenant ID and key:

```sh
sandboxrouter configure --tenant <tenant-id> --key <api-key>
```

The CLI saves them in `~/.config/sandboxrouter/config.json`, or in `$XDG_CONFIG_HOME/sandboxrouter/config.json` if `XDG_CONFIG_HOME` is set. Only you can read the file. Every later command uses what you saved. To check that everything works, list your sandboxes:

```sh
sandboxrouter sandboxes list
```

Running `sandboxrouter configure` again replaces the saved tenant ID and key.

### What a key can do

A new key can:

- list, create, change, start, stop and delete sandboxes, and run commands in them
- deploy MCP servers and manage their client tokens
- push and pull images in the registry
- read connectors and the audit log

A key with **Billing access** can also read the balance and usage and add funds (`sandboxrouter billing`).

A key can never do more than the member who created it. An operator's key can't use `sandboxrouter billing`, and only an owner's key can add, change or remove connectors or connect their workspace accounts. If the member who created a key leaves the workspace, the key stops working. To revoke a key, choose **Revoke** next to it under **Settings** → **API keys**.

## Choose a workspace

The CLI always works in one workspace: the one its key was created in. A key from one workspace doesn't work in another.

To work in another workspace:

1. In the console, open the workspace card at the top of the sidebar and switch to that workspace.
2. Create an API key there, as in [Create an API key](#create-an-api-key), and copy that workspace's tenant ID.
3. Either save the new pair, which replaces the one you saved before:

   ```sh
   sandboxrouter configure --tenant <other-tenant-id> --key <other-api-key>
   ```

   Or pass the pair to a single command and keep what you saved:

   ```sh
   sandboxrouter sandboxes list --tenant <other-tenant-id> --key <other-api-key>
   ```

## Options for every command

| Option | What it does |
| --- | --- |
| `--key KEY` | Use this API key, and so its workspace, instead of the saved one. |
| `--tenant ID` | The workspace's tenant ID for the registry, instead of the saved one. Pass it with `--key` for another workspace. |
| `--api-url URL` | Use another API address. The default is `https://api.sandboxrouter.io`. It must use `https://`, except on `localhost`. `sandboxrouter configure` saves it. |
| `--json` | Print the API's JSON response instead of the text output. |
| `--idempotency-key KEY` | Retry a create, exec or payment without doing it twice: 8 to 128 letters, digits, `_` and `-`. |
| `--help` | Print every command and option. |

`sandboxrouter run`, `sandboxrouter sandboxes create`, `sandboxrouter sandboxes exec`, `sandboxrouter mcp create` and `sandboxrouter billing add-funds` send their request with an idempotency key. If the CLI can't reach the API, the error names the key. Run the same command again with `--idempotency-key KEY` to retry without creating a second sandbox, command, server or payment.

Commands that take a sandbox (`SANDBOX`) accept its ID, and commands that take an MCP server (`SERVER`) accept its ID or its name.

### Exit codes

| Code | Meaning |
| --- | --- |
| `0` | Success. |
| `1` | The request or the command failed. |
| `2` | The command line isn't valid, for example an unknown option, a missing value or a value out of range. Nothing was changed. |
| `3` | The API or the registry refused the key: it's revoked, from another workspace, or lacks the access the command needs. |

`sandboxrouter run` and `sandboxrouter sandboxes exec` exit with the command's own exit code when it's between 1 and 125, and with `1` for any other failure. Errors are printed to standard error as `sandboxrouter: <message>`. When the workspace is out of funds, the error also links to the console page where an owner adds them.

## Sizes

`--size` sets a sandbox's vCPU, memory and disk. The default is `m`.

| Size | `--size` | vCPU | Memory | Disk |
| --- | --- | --- | --- | --- |
| XS | `xs` | 1/16 | 256 MiB | 2 GB |
| S | `s` | 1/4 | 1 GiB | 4 GB |
| M | `m` | 1/2 | 4 GiB | 8 GB |
| L | `l` | 1 | 6 GiB | 12 GB |
| XL | `xl` | 2 | 8 GiB | 16 GB |
| 2XL | `2xl` | 4 | 12 GiB | 20 GB |

A sandbox that runs your own image needs size M or larger. See [pricing](/pricing) for the rates.

## Sandbox options

`sandboxrouter sandboxes create`, `sandboxrouter run` and `sandboxrouter mcp create` set up a new sandbox with these options, one for each of its fields. `sandboxrouter sandboxes update` takes `--egress`, `--idle-stop` and `--label`, the fields that can change. A list option replaces the whole list: repeat it for each item.

| Option | Field | What it does |
| --- | --- | --- |
| `--image IMAGE` | `image` | Runs your own image from the registry; see [Push images to the registry](#push-images-to-the-registry). Without it, the Default image: Alpine Linux with bash, coreutils, Node and Docker. |
| `--command "COMMAND"` | `command` | What the sandbox itself runs, a quoted command line. Without it, its image's own entrypoint and command; the Default image has none, and waits for the commands you run in it. |
| `--env NAME=VALUE` | `env` | Sets a variable, up to 64. |
| `--size SIZE` | `size` | Its size; see [Sizes](#sizes). |
| `--egress HOST` | `egress` | A host it may reach, or `*.example.com` for the names under example.com (list `example.com` as well if the sandbox calls it), up to 32; other hosts are blocked. `--egress '*'` allows every host, as a sandbox does without the option. A command that starts with `npx` also gets `registry.npmjs.org`, unless a listed host covers it. |
| `--idle-stop MINUTES` | `idleStopMinutes` | Stops it after this many minutes with no call in flight (a command, a file, a snapshot or an MCP request): 5 to 240. The default is 15. |
| `--label KEY=VALUE` | `labels` | Sets a label, up to 16. A name is a label: `--label name=web`. |
| `--provider PROVIDER` | `provider` | Where it runs: `auto`, the default (Sandbox Router tries the providers of its size in order), or a provider such as `cloudflare` (Native). |
| `--connector ID` | `connectors` | A connector whose account its requests carry on the connector's hosts, up to 32; see [Connectors](#connectors). |
| `--user USER` | `user` | Starts it for one of your users, by your ID for them: its connectors use their connections, and it gets their logins. A sign-in they make with a code, such as Codex (ChatGPT)'s, is `sandboxrouter users sign-in`, made before the sandbox needs it; see [Your users](#your-users). |

`sandboxrouter sandboxes update` also takes `--no-egress` and `--no-labels`, which empty a field.

## Run a command in a new sandbox

`sandboxrouter run` is the shortcut: it creates a sandbox, whose create answers once it runs, runs one command in it, waits for the command to end, and prints its output:

```sh
sandboxrouter run -- node --version
sandboxrouter run --label name=build-agent --size xl -- node task.js
sandboxrouter run --image worker:latest -- python task.py
sandboxrouter run --detach -- node long-task.js
sandboxrouter run --egress api.example.com -- node --version
sandboxrouter run --connector github -- git clone https://github.com/acme/private-repo
```

The command runs as an executable with its arguments, not through a shell. To use shell features, run a shell:

```sh
sandboxrouter run -- sh -c 'node build.js && node test.js'
```

Options come before `--`, and everything after `--` is the command. `sandboxrouter run` takes the [sandbox options](#sandbox-options), and:

| Option | What it does |
| --- | --- |
| `--timeout SECONDS` | The command's time limit: 1 to 7200, or 1 to 86400 with `--detach`. The default is two hours. |
| `--detach` | Returns as soon as the command starts, with its exec ID. |

When `sandboxrouter run` is done, the sandbox keeps running until its idle stop. Delete it with `sandboxrouter sandboxes delete` when you no longer need it. With `--detach`, check on the command later with `sandboxrouter sandboxes exec <sandbox> <exec-id>`. To put files in it, write each with [`sandboxrouter sandboxes files put`](#files).

## Manage sandboxes

Each `sandboxrouter sandboxes` command calls one route of [the API](/docs/api); `SANDBOX` is a sandbox's ID.

| Command | What it does |
| --- | --- |
| `sandboxrouter sandboxes create [OPTIONS]` | Creates a sandbox with the [sandbox options](#sandbox-options) and prints it once it runs. |
| `sandboxrouter sandboxes list` | Lists the sandboxes, newest first: each one's ID, state, size and image. `--label KEY=VALUE` (repeat it to match several) and `--provider` filter them; `--limit` sets the page size, 1 to 100. If there are more, the CLI prints a `--cursor` for the next page. A new sandbox shows within seconds. |
| `sandboxrouter sandboxes get SANDBOX` | Shows the sandbox: its fields, its state and why it stopped, and its provider's ID. |
| `sandboxrouter sandboxes update SANDBOX [OPTIONS]` | Replaces each field you give: its outbound hosts, idle stop or labels. |
| `sandboxrouter sandboxes start|stop|restart SANDBOX` | Starts, stops, or stops and starts it, and prints it once it settled. |
| `sandboxrouter sandboxes delete SANDBOX` | Deletes it, with its provider's snapshots of it. |
| `sandboxrouter sandboxes logs SANDBOX` | Prints its log, oldest first: each state it reached, and why. `--since TIME` starts at a time, epoch milliseconds or an ISO 8601 time (UTC unless it names a zone); `--follow` keeps printing new entries. |
| `sandboxrouter sandboxes metrics SANDBOX` | Prints its provider's own samples of its CPU, memory and disk, oldest first, `-` where the provider doesn't measure one. `--since TIME` and `--until TIME`, each epoch milliseconds or an ISO 8601 time (UTC unless it names a zone), bound them: from its create and until now without them. See [Metrics](/docs/sandboxes#metrics). |
| `sandboxrouter sandboxes port SANDBOX PORT` | Prints the provider's own URL of a port the running sandbox serves; see [Port URLs](#port-urls). |

```sh
sandboxrouter sandboxes create --label name=web --image web:1 --env MODE=production --connector github
sandboxrouter sandboxes update <sandbox> --egress api.github.com --label team=web --idle-stop 60
sandboxrouter sandboxes logs <sandbox> --follow
```

A stopped sandbox stays stopped until you start it: a command or a file sent to it fails with `not_running`.

### Run commands in a sandbox

```sh
sandboxrouter sandboxes exec <sandbox> -- node --version
sandboxrouter sandboxes exec <sandbox> --env NODE_ENV=test --workdir /workspace/app -- npm test
echo hello | sandboxrouter sandboxes exec <sandbox> --stdin -- cat
sandboxrouter sandboxes exec <sandbox> --timeout 7200 --detach -- node long-task.js
sandboxrouter sandboxes exec <sandbox> <exec-id>
sandboxrouter sandboxes exec <sandbox> <exec-id> --cancel
```

With a command after `--`, `exec` runs it, waits for it, prints its output and exits with its exit code. With an exec ID instead, it shows that command's state, exit code and output, or cancels it with `--cancel`.

| Option | What it does |
| --- | --- |
| `--env NAME=VALUE` | Sets a variable for this command, over the sandbox's. |
| `--workdir DIR` | Runs it in this directory; the sandbox's working directory by default. |
| `--stdin` | Sends standard input to the command, up to 64 KiB. |
| `--timeout SECONDS` | The command's time limit: 1 to 7200 seconds (two hours), or up to 86400 with `--detach`. The default is 600 (10 minutes). |
| `--detach` | Prints the command's exec ID and returns without waiting. |

As with `sandboxrouter run`, the command runs without a shell. The exec ID is the command's idempotency key, so `--idempotency-key` names it.

### Files

```sh
sandboxrouter sandboxes files put <sandbox> /workspace/config.json config.json
sandboxrouter sandboxes files get <sandbox> /workspace/config.json > config.json
sandboxrouter sandboxes files delete <sandbox> /workspace/config.json
```

| Command | What it does |
| --- | --- |
| `sandboxrouter sandboxes files get SANDBOX PATH` | Prints a file's bytes. |
| `sandboxrouter sandboxes files put SANDBOX PATH [FILE]` | Writes `FILE` (standard input without it, or with `-`) as the file at `PATH`, whole. |
| `sandboxrouter sandboxes files delete SANDBOX PATH` | Deletes a file. |

`PATH` is absolute. Each is the provider's own file operation on the running sandbox.

### Snapshots

A snapshot is the provider's own copy of a sandbox, on providers that keep them.

```sh
sandboxrouter sandboxes snapshots create <sandbox>
sandboxrouter sandboxes snapshots list <sandbox>
sandboxrouter sandboxes snapshots delete <sandbox> <snapshot-id>
```

| Command | What it does |
| --- | --- |
| `sandboxrouter sandboxes snapshots create SANDBOX` | Has the provider take a snapshot of the running sandbox. |
| `sandboxrouter sandboxes snapshots list SANDBOX` | Lists the snapshots, newest first: ID, whether it kept memory, and when it was taken. |
| `sandboxrouter sandboxes snapshots delete SANDBOX SNAPSHOT_ID` | Deletes a snapshot. |

Snapshots stay until you delete them. See [Sandboxes](/docs/sandboxes#snapshots) for how snapshots work.

### Port URLs

A sandbox's ports are served at its provider's own URLs: `sandboxrouter sandboxes port <sandbox> 3000` prints the one for port 3000 while the sandbox runs, where the provider gives one. Who can open a URL is up to the provider; Sandbox Router serves none itself. See [Port URLs](/docs/sandboxes#port-urls).

## MCP servers

`sandboxrouter mcp create` puts an MCP server online at its own endpoint, `https://mcp.sandboxrouter.io/<server-id>`. The server runs in a sandbox, a new one or one you have, which `sandboxrouter sandboxes` manages like any other, and serves MCP over Streamable HTTP on a port of it:

```sh
sandboxrouter mcp create --image server:1.2.3 --http 3000
sandboxrouter mcp create --image server:1.2.3 --http 3000 --command "node http.js"
sandboxrouter mcp create --sandbox <sandbox> --http 3000 --name build-tools
```

| Option | What it does |
| --- | --- |
| `--name NAME` | The server's name, unique in the workspace: up to 63 lowercase letters, digits and hyphens. Without it, the server is named `mcp-` and 12 hex digits. |
| `--image IMAGE` | Runs your own image from the registry, or `default`, in a new sandbox. Without `--command`, the image's own entrypoint and command run. |
| `--command "COMMAND"` | With `--image`: the command that starts the server. |
| `--http PORT` | The port the server serves Streamable HTTP on, 1024 to 65535. |
| `--path PATH` | With `--http`: the path it serves MCP at, `/mcp` by default. |
| `--sandbox SANDBOX` | Adds the server to an existing sandbox, which runs it itself. |

`--command` takes a quoted command line. The CLI splits it into words like a shell does, with quotes and backslashes, but doesn't run it through a shell. A new sandbox takes the [sandbox options](#sandbox-options) (but `--command`, which is the server's).

The CLI prints the server's endpoint, its sandbox, and a first client token that can call every tool and doesn't expire. The token is shown only once. Clients send a token as an `Authorization: Bearer` header. A retry with the same `--idempotency-key` doesn't print the token again; create a new one with `sandboxrouter mcp tokens create`.

| Command | What it does |
| --- | --- |
| `sandboxrouter mcp list` | Lists the servers: ID, name, state, sandbox and endpoint. |
| `sandboxrouter mcp get SERVER` | Shows a server: its endpoint, sandbox, transport, state and its gateway's last 24 hours. |
| `sandboxrouter mcp update SERVER` | Renames it (`--name`) or changes its port and path (`--http PORT` with `--path`). |
| `sandboxrouter mcp traces SERVER` | Prints its newest 100 requests: time, status, tool, client, duration and why one was blocked. |
| `sandboxrouter mcp stats SERVER` | Prints its gateway's last 24 hours: requests, blocked, errors, timeouts and median latency. |
| `sandboxrouter mcp delete SERVER` | Deletes the server, its tokens and traces, and the sandbox it was created with; `--keep-sandbox` keeps that sandbox. |

### Create from a file

`sandboxrouter mcp create -f FILE` (or `--file FILE`) creates a server declared in a YAML or JSON file, the same format the console's **Deploy from a server file** page takes:

```yaml
server: search-mcp
source:
  image: search-mcp:1
  port: 8080
size: m
idle: stop after 30m
tokens:
  - name: laptop-claude
    tools: [search.query, search.fetch]
  - name: ci-pipeline
    tools: [search.query]
    expires: 30d
egress: [api.search.example.com]
```

```sh
sandboxrouter mcp create -f search-mcp.yaml
```

The CLI checks the whole file before it creates anything, creates the server and its tokens in one request, and prints the endpoint and each token's tools, expiry and secret once. The file declares the server and its sandbox's image, size, idle stop and hosts, so options for those can't be combined with `-f`; `--env`, `--label` and `--connector` can. A server's sandbox gets keys through the [connectors](/docs/connectors) its create names (`--connector`). A file without `egress` lets the server reach every host; a file with it reaches only the hosts it lists. See [MCP servers](/docs/mcp) for every field.

### Client tokens

```sh
sandboxrouter mcp tokens list <server>
sandboxrouter mcp tokens create <server> --name "Claude Desktop"
sandboxrouter mcp tokens create <server> --name ci --expires-days 30 --allow-tool search.query
sandboxrouter mcp tokens revoke <server> <token-id>
```

| Command | What it does |
| --- | --- |
| `sandboxrouter mcp tokens list SERVER` | Lists the server's client tokens: ID, name, whether each is active, revoked or expired, the tools it may call, and its calls and errors. |
| `sandboxrouter mcp tokens create SERVER --name LABEL` | Creates a token and prints its secret once. |
| `sandboxrouter mcp tokens revoke SERVER TOKEN_ID` | Revokes a token. The server keeps running. |

`create` takes these options:

| Option | What it does |
| --- | --- |
| `--name LABEL` | Required. A label for the client that uses the token. |
| `--expires-days DAYS` | When the token expires: 1 to 365 days. The default is 90. |
| `--allow-tool NAME` | Let the token call only this tool. Repeat it for each tool. Without it, the token can call every tool. |

## Push images to the registry

Your workspace has its own namespace in the Sandbox Router registry, `registry.caas.sh/<tenant-id>/`. Log Docker in with your saved key, then build and push a `linux/amd64` image:

```sh
sandboxrouter registry login
docker build --platform linux/amd64 -t registry.caas.sh/<tenant-id>/server:1.2.3 .
docker push registry.caas.sh/<tenant-id>/server:1.2.3
sandboxrouter mcp create --image server:1.2.3
```

`sandboxrouter registry login` runs `docker login registry.caas.sh`, with your tenant ID as the username and your API key as the password. New keys can push and pull.

Wherever a command takes `--image`, you can write the image as `NAME[:TAG]` or `NAME@sha256:DIGEST`, optionally under `registry.caas.sh/` or `registry.caas.sh/<tenant-id>/`. The CLI looks a tag up in the registry (`latest` when you give none) and always deploys its digest. Images from other registries or other workspaces are refused: push them to your namespace first. See [Images and the registry](/docs/images).

## Connectors

Sandbox Router keeps your API keys and tokens out of your sandboxes. A connector is a service your sandboxes reach: a tool of the catalog, an MCP server, or an API by its hosts. A sandbox gets the connectors its create names (`--connector`), and Sandbox Router adds each one's key to its HTTPS requests to that connector's hosts. A sandbox started without a user gets your workspace's account; one started for a user gets only that user's. See [Connectors](/docs/connectors) for how it all fits together.

Only an owner's key can add, change or remove connectors and connect their workspace accounts. Sign-ins finish in the console.

```sh
sandboxrouter connectors catalog
sandboxrouter connectors add anthropic
sandboxrouter connectors account anthropic --api-key < anthropic-key.txt
sandboxrouter connectors add github --reach team=ci
sandboxrouter connectors account github --sign-in
sandboxrouter connectors add --api --host registry.npmjs.org --id npm --env NPM_TOKEN
sandboxrouter connectors account npm --api-key < npm-token.txt
sandboxrouter connectors list
sandboxrouter connectors get github
sandboxrouter connectors remove npm
```

| Command | What it does |
| --- | --- |
| `sandboxrouter connectors catalog` | Lists the catalog's tools, and whether each is available or added. |
| `sandboxrouter connectors list` | Lists your connectors: whom each reaches, its workspace account's status and how many users connected, and how to reconnect any that need it. |
| `sandboxrouter connectors get ID` | Shows one connector, its hosts and its variables. |
| `sandboxrouter connectors add CATALOG_ID` | Adds a tool of the catalog, such as `github`, `linear`, `anthropic` or `openai`. |
| `sandboxrouter connectors add --mcp URL` | Adds an MCP server by its address. `--keys` when its accounts connect with a key. |
| `sandboxrouter connectors add --api --host HOST` | Adds an API by its hosts; repeat `--host` for each, up to 16. See the options below. |
| `sandboxrouter connectors update ID` | Changes its `--name`, its `--reach` (or `--reach-all`) and its `--env` (or `--no-env`). |
| `sandboxrouter connectors account ID METHOD` | Connects its workspace account. See the methods below. |
| `sandboxrouter connectors account ID --disconnect` | Disconnects its workspace account. |
| `sandboxrouter connectors remove ID` | Removes it, with its workspace account and every user's connection. |

`add` also takes `--id ID`, `--name NAME`, `--env VAR` (one more variable that holds the placeholder) and `--reach KEY=VALUE`, repeated for each label a workflow's sandbox must have to get the connector (every workflow's sandbox without it).

| Option | What it does |
| --- | --- |
| `--host HOST` | With `--api`: a host the key is sent to; `*.example.com` covers `example.com` and every name under it. |
| `--header NAME` | With `--host`: the request header to set. The default is `authorization`. |
| `--scheme SCHEME` | With `--host`: `bearer` (the default), `basic` or `raw` (the value as is). |
| `--username USER` | With `--scheme basic`: the username. Required for `basic`. |

`account` takes exactly one method, among those the connector takes:

| Method | What it does |
| --- | --- |
| `--api-key` | Reads the key from standard input. The CLI never takes a key as an option. |
| `--sign-in` | Signs in on the tool's page (GitHub through Sandbox Router's GitHub App). |
| `--install` | For GitHub with your own GitHub App: installs it on the account it acts for. |
| `--device` | For Codex: prints a code and OpenAI's page to enter it on. |

A sign-in or install prints a page to open. It returns to the connector's page in the console, which finishes it while you're signed in there as the owner who started it. The CLI waits until the account is connected; `--detach` returns at once instead.

### Your own app

```sh
sandboxrouter connectors app example --preset google --client-id 1234.apps.googleusercontent.com \
  --scope https://www.googleapis.com/auth/drive.readonly < client-secret.txt
sandboxrouter connectors app example --remove
sandboxrouter connectors github-app github --client-id Iv1.0123abcd --slug acme-bot < private-key.pem
sandboxrouter connectors github-app github --remove
```

`sandboxrouter connectors app` has an MCP server's or an API's accounts sign in through your own OAuth app; on an API it's allowed only while the connector has no accounts. `sandboxrouter connectors github-app` lets GitHub's workspace account install your own GitHub App. The client secret or private key is read from standard input.

| Option | What it does |
| --- | --- |
| `--client-id ID` | The app's client ID. For a GitHub App, its Client ID. |
| `--scope SCOPE` | Each scope to ask for. Required for an API's app. |
| `--preset PRESET` | For an API: `google`, `gitlab` or `linear`, which fills in the provider's endpoints. |
| `--authorize-url URL`, `--token-url URL` | For an API with no preset: the provider's endpoints, `https://` URLs on a public host. |
| `--token-auth METHOD` | With those endpoints: how the token endpoint takes the client secret, `post` (the default) or `basic`. |
| `--slug SLUG` | With `github-app`: the app's slug, as in `github.com/apps/<slug>`. |

Set the app's redirect URI (for a GitHub App, its callback and setup URL) to `https://api.sandboxrouter.io/v1/credential-interactions/callback`.

### Your users

`sandboxrouter users` manages your users' connections one user at a time (see [Connect each user's account](/docs/connectors#connect-each-users-account)); `USER` is your ID for the user. Codex (ChatGPT)'s users sign in with a code, which `sandboxrouter users sign-in` gets them:

```sh
sandboxrouter users sign-in user_123
sandboxrouter users link user_123 --connector linear --return-url https://app.acme.com/settings
sandboxrouter users get user_123
sandboxrouter users disconnect user_123 --connector linear
```

| Command | What it does |
| --- | --- |
| `sandboxrouter users sign-in USER` | Prints the user's code for Codex (ChatGPT) and OpenAI's sign-in page to enter it on, the same code while one still works, then waits until they're signed in; `--detach` returns at once. For a user who's signed in, it signs them in again: their sign-in keeps working until they enter the new code. `--connector ID` names the connector, your Codex (ChatGPT) one by default. |
| `sandboxrouter users link USER --connector ID` | Makes a connect link for one connector, with `--return-url URL` to send them back to. |
| `sandboxrouter users get USER` | Shows each of the user's connections with its status, and a code under way with until when it works (the code itself only to a key with `sandbox:write`). |
| `sandboxrouter users disconnect USER --connector ID` | Disconnects the user from one connector. Their sandboxes' requests to it are refused from the next one. |

Sign a user in with `sandboxrouter users sign-in` before a sandbox of theirs needs it: until they enter their code, their sandboxes' requests to the connector are refused (see [Sign users in with a code](/docs/connectors#sign-users-in-with-a-code)).

### Logins

`sandboxrouter logins` manages the Logins connector's logins: a user's with `--user USER`, else your workspace's (see [Logins](/docs/logins)). `LOGIN` is a login's ID, or its site where there's one login there.

```sh
sandboxrouter logins add --user user_123 --site github.com --username ana@acme.com --methods passkey,typed --password-stdin < password.txt
sandboxrouter logins link --site github.com --user user_123 --return-url https://app.acme.com/settings
sandboxrouter logins list --user user_123
sandboxrouter logins check github.com --user user_123
```

| Command | What it does |
| --- | --- |
| `sandboxrouter logins list` | Lists the logins: site, account, how each signs in, which browser, passkeys and saved sign-in. `--site SITE` for the logins that cover a site; `--limit` and `--cursor` page through them. |
| `sandboxrouter logins get LOGIN` | Shows one login. |
| `sandboxrouter logins add --site SITE` | Adds a login. See the options below. |
| `sandboxrouter logins update LOGIN` | Changes what you name, with the options of `add` but `--site`; `--no-password`, `--no-code` and `--no-deliver` remove them. A new account name, password or setup key needs every secret the login keeps piped again, as Sandbox Router keeps no copy. |
| `sandboxrouter logins remove LOGIN` | Removes it, its passkeys and its saved sign-in. The site still lists its passkeys until you remove them there. |
| `sandboxrouter logins passkeys LOGIN` | Lists its passkeys; `rename PASSKEY --name LABEL` and `remove PASSKEY` change one. |
| `sandboxrouter logins session LOGIN` | Shows its saved sign-in; `upload FILE` uploads Playwright's storage state (`-` for standard input), not for a Browserbase login, and `remove` marks it signed out (on Browserbase, the user's next Browserbase browser starts signed out of every site). |
| `sandboxrouter logins live LOGIN` | Opens a browser at its sign-in page and prints its live view: sign in there, then press Enter to save the sign-in, or Ctrl-C to cancel and save nothing. Whoever has its address controls that browser until then. |
| `sandboxrouter logins check LOGIN` | Opens a browser to see whether the site still has them signed in, such as "user_123 is signed in to github.com", and exits with status 1 when not. |
| `sandboxrouter logins link LOGIN --user USER` | Makes a link for a user to sign in themselves, with `--return-url URL`; `--site SITE` in place of `LOGIN` makes the login too. |

| Option | What it does |
| --- | --- |
| `--username NAME` | The account name, as the site shows it. |
| `--methods LIST` | How Sandbox Router signs in, in order: `passkey`, `typed` or `readable`, such as `passkey,typed`. |
| `--browser BROWSER` | `native` (Sandbox Router's own browsers, the default) or `browserbase`. |
| `--password-stdin`, `--code-stdin` | Read the password, or the authenticator setup key, from standard input; with both, the password then the setup key on the next line. The CLI never takes a secret as an option. |
| `--signer SIGNER` | Who signs with its passkeys: `sandboxrouter` (the default) or `browser`. |
| `--creators LIST` | Who may make a passkey: any of `user` (your user, by link), `platform` (your team) and `agent` (the AI), or `none`. |
| `--deliver HOST:HEADER:VALUE` | Adds its `password`, `username` or `code` to a header of its sandboxes' requests to the host, such as `registry.npmjs.org:npm-otp:code`; add `:bearer` or `:basic` after the value for those schemes. Repeat it for each. |

It prints sentences and tables, never a secret: "Added github.com for user_123. user_123's sandboxes sign in with a passkey, then the password." Browsers that `live` and `check` open count as browser time.

## Providers

A sandbox runs on Native, Sandbox Router's own runtime, or on a provider of your own account, such as Daytona or E2B, once an owner connects its key. That provider bills you directly for what runs there. Only an owner's key can connect or remove a provider's key.

```sh
sandboxrouter providers list
sandboxrouter providers key set e2b --api-key < e2b-key.txt
sandboxrouter providers key set e2b --api-key --domain e2b.app < e2b-key.txt
sandboxrouter providers get e2b
sandboxrouter providers key remove e2b
```

| Command | What it does |
| --- | --- |
| `sandboxrouter providers list` | Lists the providers, whether each can take a sandbox now, and whether it runs on your key. |
| `sandboxrouter providers get ID` | Shows one provider, and where its key stands. |
| `sandboxrouter providers key set ID --api-key` | Connects your key of the provider, read from standard input. The provider checks it before sandboxes run there, and on Daytona and E2B its webhook is registered, so what the provider does to your sandboxes shows at once. |
| `sandboxrouter providers key remove ID` | Stops your sandboxes on that provider, removes its webhook, and removes the key. |

`key set` also takes the provider's other fields, each optional:

| Option | What it does |
| --- | --- |
| `--domain DOMAIN` | For E2B: the domain of the region your project is in. Without it, `e2b.app`. |
| `--provider-api-url URL` | For Daytona: its API's `https://` URL. |
| `--target TARGET` | For Daytona: its target region, such as `us`. |

## Billing

`sandboxrouter billing` needs a key with **Billing access**, which only an owner can create.

```sh
sandboxrouter billing balance
sandboxrouter billing add-funds 25
sandboxrouter billing auto-recharge show
sandboxrouter billing auto-recharge set --below 10 --recharge-to 50
sandboxrouter billing auto-recharge set --off
sandboxrouter billing usage
sandboxrouter billing usage --month 2026-09
sandboxrouter billing usage --csv > sandboxrouter-usage.csv
```

| Command | What it does |
| --- | --- |
| `sandboxrouter billing balance` | Prints the balance, the auto-recharge setting and recent top-ups with their receipts. |
| `sandboxrouter billing add-funds AMOUNT` | Adds funds: $5 to $2,000 of credits, written as `25`, `25.50`, `$25.50` or `1,000`. It charges the card on file, or prints a Stripe Checkout link to pay and save a card. |
| `sandboxrouter billing auto-recharge show` | Prints the auto-recharge setting. |
| `sandboxrouter billing auto-recharge set` | Turns auto-recharge on, changing only the amounts you give. With `--off`, turns it off. |
| `sandboxrouter billing usage` | Prints a month's spend: the total so far, the projection and today while the month is current, the amount for each meter, and the spend alert. |

| Option | What it does |
| --- | --- |
| `--below AMOUNT` | With `auto-recharge set`: recharge when the balance falls below this, $5 to $1,000. It's $10 until you change it. |
| `--recharge-to AMOUNT` | With `auto-recharge set`: buy credits back up to this, at least $5 above `--below` and at most $2,000, charging the card for them. It's $50 until you change it. |
| `--off` | With `auto-recharge set`: turn auto-recharge off. You get an email when the balance falls below the amount instead. |
| `--month YYYY-MM` | With `usage`: the month to show. The default is this month. |
| `--csv` | With `usage`: print the month as CSV, one row per UTC day and meter, with the columns `day,meter,quantity,unit,rate_usd,amount_usd`. The amounts add up exactly to the month's total. Can't be combined with `--json`. |

Auto-recharge starts off, so adding funds never charges your card again on its own. Sandboxes run only while the balance is above $0. See [Billing and usage](/docs/billing).

## Workspace settings and the audit log

```sh
sandboxrouter settings
sandboxrouter settings --time-zone Europe/Berlin
sandboxrouter audit
sandboxrouter audit --action sandbox --since 2026-10-01
sandboxrouter audit --action sandbox.stop --outcome failure --limit 20
```

`sandboxrouter settings` prints the workspace's time zone, its one setting. `--time-zone ZONE` sets it, an IANA name such as `Europe/Berlin`.

`sandboxrouter audit` lists who changed what in the workspace, newest first: the time, who did it, the action, its target, the outcome and the request ID. Records are kept for 365 days.

| Option | What it does |
| --- | --- |
| `--action ACTION` | An action, such as `sandbox.stop` or `api_key.create`, or a group: `sandbox`, `command`, `mcp_server`, `mcp_token`, `connector`, `workspace_account`, `connection`, `end_user`, `login`, `passkey`, `provider`, `api_key`, `member`, `invite`, `tenant` or `billing`. |
| `--actor ID` | A user or API key ID, a sandbox's ID, one of your users' IDs, or `operator` or `system`. |
| `--target ID` | The ID of what was changed. |
| `--outcome OUTCOME` | `success` or `failure`. |
| `--since TIME`, `--until TIME` | Epoch milliseconds or ISO 8601 times, such as `2026-10-01` or `2026-10-01T12:00:00Z` (UTC unless it names a zone), as in `sandboxes logs` and `sandboxes metrics`. |
| `--limit N` | Records per page, 1 to 100. |
| `--cursor CURSOR` | The next page: the value the previous page printed. |

An API key never sees records about API keys, members or invites, and sees billing records only with **Billing access**. In what a key reads, users show as their user ID rather than their email.
