# BoxCompute Sandbox > Build secure, persistent remote workspaces for agents and applications. Index: https://boxcompute.ai/docs/llms.txt # API keys > Create, store, scope, use, and revoke BoxCompute API keys safely. Source: https://boxcompute.ai/docs/api-keys/ · Markdown: https://boxcompute.ai/docs/api-keys/index.md Manage customer API keys at [https://app.boxcompute.ai/api-keys](https://app.boxcompute.ai/api-keys). > **Sign up before creating a key** > > Eligible new customers receive $5 in free compute credit, valid for 14 days, when they [sign up](https://app.boxcompute.ai/signup). ## Sign-up review Some sign-up requests wait for review before an account is created or a sign-up link is sent. Once approved, the request receives a sign-up link by email. Submitting the same request again does not bypass review. If you need help, contact [support@boxcompute.ai](mailto:support@boxcompute.ai); never include a password or sign-up link. Welcome credit is subject to eligibility, not granted for every new account. ## Create a key 1. Click **New API key**. 2. Keep the default name **Development** or enter a descriptive name for the integration or environment. 3. Click **Create key**. 4. Copy the `bc_live_...` secret using the copy control and store it immediately. The plaintext secret is shown only once. After you close the dialog, BoxCompute shows only the key's name, final-character hint, scopes, creation and last-use information, and revocation status. If you lose the secret, create a replacement and revoke the old key. ## Suspended accounts A suspended account cannot sign in or use its API keys. Requests with an otherwise valid key return `403 ACCOUNT_SUSPENDED`; changing its scopes or retrying will not restore access. Contact [support@boxcompute.ai](mailto:support@boxcompute.ai) if you think this is a mistake. Suspension does not revoke the key: after the account is restored, an unrevoked key can work again, but you must sign in again to use the web application. ## Store and use keys - Put production keys in a secret manager and limit who and what can read them. - Use separate, clearly named keys for development, CI, and production so each can be rotated independently. - Do not commit keys, paste them into source code, include them in URLs, or write them to logs. - Do not use a realistic-looking secret in examples. Use `bc_live_REDACTED` in static documentation. - Send the secret only in the HTTPS authorization header: ```http Authorization: Bearer bc_live_REDACTED ``` ## Scopes Keys can have these scopes: | Scope | Permitted public v2 operations | | --- | --- | | `workspace:create` | Create a persistent workspace owned by the account | | `sandbox:read` | List workspaces, active and deleted Sandboxes; inspect a Sandbox and its desktop status; read current and retained logs and Sandbox analytics; introspect the authenticated account and key with `GET /api/v2/me`; read file metadata and content; list directories; and revoke the current key with `DELETE /api/v2/auth` | | `sandbox:create` | Create a Sandbox in an owned workspace, start an existing owned slot, or stop an owned VM while keeping its disk | | `sandbox:execute` | Execute commands and manage observable operations; write, edit, rename, create, and remove paths; start/stop the desktop, capture screenshots, send desktop input, and mint/revoke control-capable viewer links; list, create and close VM browser previews (listing returns bearer URLs); activate, reconnect, and revoke an operator-selected cooperative connection; create, look up, and revoke selected TCP service access | | `sandbox:delete` | Delete an owned Sandbox | | `usage:read` | Read account usage summaries, credit balance, wallet transactions, Sandbox compute-cost reports, and the account's API audit log | | `email:send` | [Send plain-text email to the key owner's own account address](/http-api#email-your-account-early-access); requires Starter and email early access | Keys created on the API keys page get all seven scopes, including `workspace:create` and `email:send`; the page has no scope picker. Older keys do not gain new scopes automatically: replace one if it needs to create a workspace or send email and lacks the corresponding scope. Having `email:send` does not grant Starter or email early access. Use separate keys for separate integrations so you can rotate or revoke their access independently. See [Audit API activity](/audit-log) to filter API requests and key lifecycle events, page through results, and understand retention. See [Track Sandbox costs](/costs) to attribute compute to creating keys, Sandboxes, and charged runs. ## Revoke a key Use the revoke control on the API keys page. Revocation takes effect immediately, and the secret cannot authenticate another request. Revoking a key does **not** delete workspaces or Sandboxes that already exist. A key can also revoke itself: ### TypeScript ```ts await boxcompute.auth.revoke(); ``` ### Python ```python boxcompute.auth.revoke() ``` With direct HTTP: ```bash curl -X DELETE 'https://api.boxcompute.ai/api/v2/auth' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/json' ``` The response is `204 No Content`; the key is invalid immediately afterward. # Audit API activity > Review API-key requests and key lifecycle events for your account. Source: https://boxcompute.ai/docs/audit-log/ · Markdown: https://boxcompute.ai/docs/audit-log/index.md BoxCompute records API-key requests, key creation and revocation, and attempts to use a revoked key. Use the activity view on the [API keys page](https://app.boxcompute.ai/api-keys), or query the public API with a key that has `usage:read` scope. ```bash curl --get 'https://api.boxcompute.ai/api/v2/audit-events' \ -H 'Authorization: Bearer bc_live_REDACTED' \ --data-urlencode 'type=api.request' \ --data-urlencode 'outcome=error' \ --data-urlencode 'limit=50' ``` Events are newest first. The response contains up to 200 events and an opaque `nextCursor`: ```json { "events": [ { "id": "aud_REDACTED", "type": "api.request", "createdAt": 1791244800000, "requestId": "req_REDACTED", "actor": { "type": "api_key", "apiKey": { "id": "key_REDACTED", "name": "Production", "hint": "8f2c" }, "ipAddress": "203.0.113.10", "userAgent": "my-service/1.0" }, "request": { "method": "GET", "route": "/api/v2/sandboxes/:id", "path": "/api/v2/sandboxes/sbx_REDACTED", "status": 404, "durationMs": 12 }, "resourceId": "sbx_REDACTED" } ], "nextCursor": null } ``` For key lifecycle events, `request` and `requestId` can be `null`, and the actor can be a signed-in `session`. API-key metadata, IP address, user agent, route and resource ID can also be absent. ## Filter and page results | Query | Behavior | | --- | --- | | `apiKeyId` | One API key ID | | `type` | `api.request`, `api_key.created`, `api_key.revoked`, or `api_key.rejected` | | `resourceId` | One resource ID, such as a Sandbox ID | | `method` | `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE` | | `outcome` | `success` for status below 400, or `error` for status 400 and above | | `from` | Inclusive ISO 8601 timestamp | | `to` | Exclusive ISO 8601 timestamp; a `to` at or before `from` is not rejected and returns no events | | `before` | Opaque `nextCursor` from the previous page | | `limit` | 1–200 events; defaults to 50 | Pass a non-null cursor unchanged to read the next page: ```bash curl --get 'https://api.boxcompute.ai/api/v2/audit-events' \ -H 'Authorization: Bearer bc_live_REDACTED' \ --data-urlencode 'before=REDACTED_OPAQUE_CURSOR' \ --data-urlencode 'limit=200' ``` The web activity view supports filtering by key, event type, outcome and Sandbox ID, and can export up to 10,000 matching rows as CSV. ## Retention and sensitive data Read requests (`GET` and `HEAD`) are retained for 31 days. Mutating requests and key lifecycle events are retained for about 15 months. Every API response includes a `Request-Id` header. Its value matches `requestId` in the corresponding event, so record that header when investigating a request. Audit events include the normalized path, but do not store query strings, request or response bodies, bearer credentials, or API-key secrets. They are operational records, not a backup of request payloads or command output. # BoxCompute CLI > Install the bxc command, authenticate in a browser, and manage remote Sandboxes from a terminal. Source: https://boxcompute.ai/docs/cli/ · Markdown: https://boxcompute.ai/docs/cli/index.md The BoxCompute CLI connects local terminals and coding agents to isolated BoxCompute Sandboxes. It uses the customer Sandbox API v2, handles browser authentication, and can install the packaged `boxcompute-sandbox` skill for supported coding agents. New to BoxCompute? Start with the [CLI quickstart](/cli/quickstart/) for early-access registration, installation, and your first browser login. Use the CLI when you want to run commands interactively or let a local agent such as Codex CLI or Claude Code use remote compute. Use an [official SDK](/quickstart/) when you are building an application or need durable operations, idempotent Sandbox creation, or cursor-based file retrieval. The [HTTP API](/http-api/) remains available for direct clients and generated tooling. The CLI creates VM Sandboxes. Legacy `--gvisor` or `--cpu` options request a runtime the hosted API now rejects with `400 INVALID_REQUEST`; do not use them for new creation. It can also open VM browser previews, forward selected VM ports to local loopback, and, for operator-enabled Sandboxes, open experimental SSH that stays off unless `BOXCOMPUTE_ENABLE_SSH=1` is set; see the [command reference](/cli/commands/). The language runtime inside a Sandbox does not identify its isolation backend. ## Requirements - A BoxCompute account. Eligible new customers receive $5 in free compute credit, valid for 14 days. [Sign-up review and eligibility](/api-keys#sign-up-review) apply. - Node.js 20 or newer with npm. - A local terminal on macOS, Linux, Windows, or WSL. ## Install Install the public package globally. npm is the recommended path, and Bun, pnpm, and Yarn Classic can install the same package: ### npm ```bash npm install --global @boxcompute/cli ``` ### Bun ```bash bun add --global @boxcompute/cli ``` ### pnpm ```bash pnpm add --global @boxcompute/cli ``` ### Yarn Classic ```bash yarn global add @boxcompute/cli ``` Whichever package manager installs it, `bxc` runs with Node.js and still requires Node.js 20 or newer. Installing with Bun does not change the CLI runtime: the published executable starts with `#!/usr/bin/env node`. For a one-off version check without a global installation, use either package runner: ```bash npx @boxcompute/cli@latest version bunx @boxcompute/cli@latest version ``` A global install is preferable for coding-agent integration because the agent skill expects `bxc` to be available on `PATH`. Bun can report that the package's lifecycle script was blocked; this does not prevent the CLI from running. Untouched managed skills are checked and refreshed by the next ordinary `bxc` command as well. Confirm that the executable is available: ```bash bxc version ``` The older `bcompute` executable is installed as a compatibility alias. New scripts and examples should use `bxc`. ## Log in ```bash bxc login ``` On a first login, the CLI prints a short approval code and opens `https://app.boxcompute.ai` in your browser. Sign in, confirm that the code matches, and approve the device. The terminal waits for approval and then saves the CLI credential. Later logins reuse the saved web origin unless you pass `--url`. For an SSH session or another environment that cannot open a browser, print the approval URL and open it yourself: ```bash bxc login --no-open ``` For a self-hosted deployment, pass its **web origin**, without an API path: ```bash bxc login --url https://boxcompute.example.com ``` The server must support CLI browser authentication and Sandbox API v2. If either surface is unavailable, update the BoxCompute server before updating or using this CLI. ## Verify the connection ```bash bxc doctor bxc workspaces bxc sandboxes ``` `doctor` verifies the saved credential and reports the connected web origin and Sandbox count. `workspaces` lists possible parents for new Sandboxes, including workspaces that do not have a Sandbox yet. `sandboxes` lists the instances already owned by the account. ## Start and use a Sandbox Copy a workspace ID from `bxc workspaces`, then create an instance: ```bash bxc sandbox start WORKSPACE_ID ``` Save the returned Sandbox ID. Workspace IDs and Sandbox IDs are different: `sandbox start` takes a workspace ID, while `status`, `logs`, `exec`, and `delete` take the Sandbox ID returned by start. ```bash bxc sandbox status SANDBOX_ID bxc sandbox exec SANDBOX_ID -- python3 -c 'print(sum(range(100000)))' bxc sandbox logs SANDBOX_ID --source execute ``` Everything after `--` is sent as a structured argument vector. The CLI does not join those arguments into shell source. To use pipes, redirects, or other shell syntax intentionally, invoke a shell explicitly: ```bash bxc sandbox exec SANDBOX_ID -- bash -lc 'npm test 2>&1 | tee /workspace/test.log' ``` Sandboxes persist between commands and become `cold` 30 minutes after the last command or file call. A later execution starts the runtime again. Do not delete a Sandbox merely because one terminal task is finished. With CLI 0.11.0 or later, stop compute now and keep the files for your next session: ```bash bxc sandbox stop SANDBOX_ID bxc sandbox resume SANDBOX_ID ``` CLI 0.12.0 also supports early-access [self-hosted runners](/cli/commands#self-hosted-runners): run a BoxCompute chat's commands on your own machine rather than using a hosted VM. That command requires Node.js 22 or newer; the other CLI commands still support Node.js 20. ## Use different runtimes The CLI is language-agnostic: everything after `--` is an executable and its arguments. The executable must exist inside the target Sandbox. Check the available runtimes instead of assuming a version: ```bash bxc sandbox exec SANDBOX_ID -- node --version bxc sandbox exec SANDBOX_ID -- npm --version bxc sandbox exec SANDBOX_ID -- python3 --version bxc sandbox exec SANDBOX_ID -- bash --version ``` Run small Node.js, Python, and shell workloads directly: ```bash bxc sandbox exec SANDBOX_ID -- node -e 'console.log([1, 2, 3].map(x => x * x))' bxc sandbox exec SANDBOX_ID -- python3 -c 'print(sum(range(100000)))' bxc sandbox exec SANDBOX_ID -- bash -lc 'printf "%s\n" /workspace/*' ``` The current default BoxComputeAgent Sandbox image includes Node.js, npm, Python 3, pip, and Bash. It does **not** preinstall Bun. Hosted images can evolve, so `bun --version` is the authoritative check for a particular Sandbox. To use Bun when it is absent, install it under the persistent `/workspace` volume rather than a system directory. This requires outbound access to `bun.sh`: ```bash bxc sandbox exec SANDBOX_ID \ --env BUN_INSTALL=/workspace/.bun \ -- bash -lc 'curl -fsSL https://bun.sh/install | bash' bxc sandbox exec SANDBOX_ID -- /workspace/.bun/bin/bun --version bxc sandbox exec SANDBOX_ID -- /workspace/.bun/bin/bun -e \ 'console.log(Bun.version)' ``` For a Bun project already stored in the Sandbox workspace: ```bash bxc sandbox exec SANDBOX_ID \ --cwd /workspace/project \ -- /workspace/.bun/bin/bun install --frozen-lockfile bxc sandbox exec SANDBOX_ID \ --cwd /workspace/project \ -- /workspace/.bun/bin/bun test ``` Keep runtime binaries, dependency caches, and project files under `/workspace` when they must survive an idle runtime replacement. The CLI does not copy the current local directory into a Sandbox; these commands operate on files already in the remote workspace. ## Structured output Add the global `--json` flag for scripts and agents: ```bash bxc --json workspaces bxc --json sandboxes bxc --json sandbox status SANDBOX_ID bxc --json sandbox exec SANDBOX_ID -- npm test ``` JSON mode writes one JSON value to standard output. For `sandbox exec`, the value is the API's execution result (`stdout`, `stderr`, `exitCode`, `timedOut`, and truncation flags) plus `sandboxId`. ## Credential storage By default, the CLI stores configuration outside your projects: | File | Purpose | | --- | --- | | `~/.config/boxcompute/config.json` | The BoxCompute web origin and path to the credential file. | | `~/.config/boxcompute/credential` | The `bc_live_...` CLI credential. | The directory is restricted to the current user and both files are written with mode `0600`. The CLI refuses to use a credential file that is readable by group or other users. These environment variables override the defaults: | Variable | Purpose | | --- | --- | | `BOXCOMPUTE_CONFIG_DIR` | Use another BoxCompute configuration directory. | | `XDG_CONFIG_HOME` | Changes the default parent of the `boxcompute` directory. | | `BOXCOMPUTE_URL` | Override the saved BoxCompute web origin. | | `BOXCOMPUTE_TOKEN_FILE` | Read the credential from another protected file. | When using the last two variables, set both or leave the missing value available in `config.json`. Never put the credential in a repository, an agent skill, a prompt, or a command-line argument. ## Update or remove access Update to the latest npm release: ```bash bxc update ``` `bxc up` is the short alias. The updater leaves the saved connection in place and refreshes untouched CLI-managed agent skills. If npm cannot be invoked automatically, run: ```bash npm install --global @boxcompute/cli@latest ``` Revoke the current CLI credential on the server and remove the two local files: ```bash bxc logout ``` Uninstall the package separately when you no longer need the executable: ```bash npm uninstall --global @boxcompute/cli ``` ## Next steps - Read the [complete CLI command reference](/cli/commands/). - Connect [Codex CLI, Claude Code, or another coding agent](/cli/agents/). - Learn the underlying [Sandbox lifecycle](/sandboxes/) and [execution limits](/execute/). # Connect coding agents > Let Codex CLI, Claude Code, and other local coding agents run work in BoxCompute Sandboxes. Source: https://boxcompute.ai/docs/cli/agents/ · Markdown: https://boxcompute.ai/docs/cli/agents/index.md The BoxCompute CLI includes an Agent Skills-compatible `boxcompute-sandbox` skill. The skill teaches a local coding agent to discover your BoxCompute workspaces, choose or create a Sandbox instance, execute commands with `bxc`, inspect failures, and preserve the runtime for later work. The skill uses the CLI's default VM Sandbox flow. It opens a browser preview only when you ask for a shareable link, gives you a `bxc sandbox expose` command to run locally when you need to reach a VM service, and uses experimental SSH only when you explicitly ask for it. This integration does not give the agent your credential. The skill contains instructions only; every `bxc` process reads the protected credential file itself. The coding agent must run on the same machine or environment where `bxc` is installed and authenticated. ## Install once ```bash npm install --global @boxcompute/cli bxc login bxc doctor bxc skill detect bxc skill install ``` `skill install` targets every detected compatible harness. To install only for specific agents, name them: ```bash bxc skill install codex claude ``` After installation, start a new agent session. Run `bxc skill list` at any time to see detected and installed destinations. ## Codex CLI Install the managed skill explicitly if Codex was not detected: ```bash bxc skill install codex ``` The CLI installs `boxcompute-sandbox` under `$CODEX_HOME/skills`, which defaults to `~/.codex/skills`. Start a new Codex CLI session, then invoke it explicitly: ```text $boxcompute-sandbox run this project's test suite in an isolated BoxCompute Sandbox and fix the failures ``` You can also open Codex's `/skills` picker or ask naturally for work that needs remote compute. Codex may select the skill automatically when the task matches its description. See the official [Codex skills documentation](https://developers.openai.com/codex/skills/) for current skill invocation behavior. A useful first request is: ```text $boxcompute-sandbox show me my BoxCompute workspaces and Sandboxes, ask me which one to use if the choice is ambiguous, then run `node --version` there ``` Codex's normal shell, network, and approval policies still apply when it invokes `bxc`. The skill does not bypass Codex sandboxing or approval prompts. ## Claude Code Install the managed skill explicitly if Claude Code was not detected: ```bash bxc skill install claude ``` The skill is installed at `~/.claude/skills/boxcompute-sandbox/SKILL.md`. Start a new Claude Code session and invoke it by its directory name: ```text /boxcompute-sandbox run this project's test suite in an isolated BoxCompute Sandbox and fix the failures ``` Claude Code can also load it automatically from a matching natural-language request. The official [Claude Code skills documentation](https://code.claude.com/docs/en/slash-commands) explains personal skill discovery and slash-command invocation. If the top-level `~/.claude/skills` directory did not exist when an existing Claude Code session started, restart that session after installation. Claude Code's shell permissions still govern each local `bxc` invocation. ## Codex and Claude Code together Use the `both` shortcut to install both managed copies: ```bash bxc skill install both ``` The copies contain the same cross-agent instructions. They share the single BoxCompute connection stored outside either agent's configuration, so logging in or out applies to both. ## Other supported coding agents The installer supports these targets and destinations. Paths use the current user's home and XDG configuration roots. | Target | Harness | Managed skill destination | | --- | --- | --- | | `claude` | Claude Code | `~/.claude/skills/boxcompute-sandbox` | | `codex` | Codex | `$CODEX_HOME/skills/boxcompute-sandbox`, default `~/.codex/skills/boxcompute-sandbox` | | `amp` | Amp | `~/.claude/skills/boxcompute-sandbox` | | `opencode` | OpenCode | `$XDG_CONFIG_HOME/opencode/skills/boxcompute-sandbox`, default `~/.config/opencode/skills/boxcompute-sandbox` | | `cursor` | Cursor | `~/.cursor/skills/boxcompute-sandbox` | | `gemini` | Gemini CLI | `~/.gemini/skills/boxcompute-sandbox` | | `copilot` | GitHub Copilot | `~/.copilot/skills/boxcompute-sandbox` | | `cline` | Cline | `~/.cline/skills/boxcompute-sandbox` | | `roo` | Roo Code | `~/.roo/skills/boxcompute-sandbox` | | `goose` | goose | `~/.agents/skills/boxcompute-sandbox` | | `pi` | Pi | `~/.pi/agent/skills/boxcompute-sandbox` | | `windsurf` | Windsurf | `~/.agents/skills/boxcompute-sandbox` | | `agents` | Other Agent Skills-compatible harnesses | `~/.agents/skills/boxcompute-sandbox` | Some harnesses share a destination. The installer deduplicates those writes and reports every harness represented by the managed copy. Use `all` when automatic detection misses a harness or when you want to prepare every location: ```bash bxc skill install all ``` Use the portable `agents` target for another harness that reads the shared Agent Skills directory: ```bash bxc skill install agents ``` ## What the agent is instructed to do When the skill activates, the agent follows these boundaries: 1. Run `bxc --json workspaces` and `bxc --json sandboxes` instead of assuming that the first Sandbox is correct. 2. Start a Sandbox with a workspace ID only when another isolated instance is needed, then use the returned Sandbox ID for later calls. 3. Execute programs as structured arguments and use `bash -lc` only for intentional shell syntax. 4. Keep paths at `/workspace` or below it and pass `--cwd` or `--env` again on each execution when needed. 5. Treat a non-zero remote exit as evidence to diagnose, not a reason to retry blindly. 6. Inspect uncertain state before retrying a start or delete after a dropped connection. 7. Keep a Sandbox for later work: stop a VM it will reuse when finished, and delete one only when you ask or clearly designate it as disposable. 8. Never read, print, request, or transmit the saved BoxCompute credential. The agent should finish by telling you which workspace it used, the meaningful command results, and whether the Sandbox was left running. ## Prompt patterns Use explicit skill invocation when you know the work belongs remotely: ```text Use $boxcompute-sandbox to build this project and run all tests in an isolated Sandbox. Keep the Sandbox for follow-up work. ``` For Claude Code, replace `$boxcompute-sandbox` with `/boxcompute-sandbox`. Other useful requests include: ```text Use BoxCompute to reproduce this Linux-only failure without changing my local machine. Run the data-processing script remotely with MODE=staging, but do not send any local secrets. Inspect the retained execute logs for my Sandbox without waking its compute. Create a disposable Sandbox under workspace WORKSPACE_ID, run the benchmark, report the result, and delete only that Sandbox when finished. ``` For an ambiguous workspace or Sandbox choice, expect the agent to show the available IDs and ask which one to use. ## Local and cloud sessions The packaged skill is intended for local coding agents. A remote or cloud agent session does not automatically inherit your local `bxc` installation, home-directory skill, or credential file. Install and authenticate the CLI inside that remote environment only when its secret-handling policy permits it; otherwise use an [official SDK](/quickstart/) from controlled application infrastructure. ## Troubleshooting ### The agent cannot find the skill ```bash bxc skill detect bxc skill list ``` Confirm the reported path, then start a new agent session. If you intentionally edited the installed copy, the updater preserves it. Replace it only when desired: ```bash bxc skill install AGENT_TARGET --force ``` ### The agent can see the skill but cannot run `bxc` Start the agent from a terminal where `bxc version` and `bxc doctor` work. A GUI-launched agent may have a different `PATH`; restart it after installing the global npm package. ### Authentication is missing or expired Authentication belongs to you, not the agent. Run this yourself: ```bash bxc login bxc doctor ``` Do not paste a credential into the chat. The skill explicitly tells the agent not to request or inspect it. ### Remove the integration ```bash bxc skill remove codex claude --yes ``` Locally modified copies are protected. Add `--force` only if you intend to discard those modifications. Removing a skill does not revoke the BoxCompute credential; run `bxc logout` separately when access should be revoked. # CLI command reference > Commands, aliases, options, output behavior, and exit statuses for bxc. Source: https://boxcompute.ai/docs/cli/commands/ · Markdown: https://boxcompute.ai/docs/cli/commands/index.md The executable is `bxc`. The compatibility alias `bcompute` accepts the same commands. ```text bxc [--json] COMMAND [OPTIONS] ``` ## Global options | Option | Meaning | | --- | --- | | `--json` | Emit machine-readable JSON for commands that return data. It can appear before or after the command. | | `-V`, `-v`, `--version` | Print the installed CLI version. | | `-h`, `--help` | Show help. Use `bxc help COMMAND`, such as `bxc help sandbox` or `bxc help audit`, for command help. | ## Authentication and maintenance ### `bxc login` Start browser device authorization. `bxc auth` and `bxc auth login` are equivalent. | Option | Meaning | | --- | --- | | `--url URL` | Connect to this BoxCompute web origin. Defaults to the saved origin, then `https://app.boxcompute.ai`. The value must be an absolute HTTP or HTTPS origin without credentials, a path, query, or fragment. | | `--no-open` | Print the approval URL without trying to launch a browser. | The CLI supports native browser launchers on macOS, Linux, Windows, and WSL. Failure to launch a browser does not stop authorization; open the printed URL manually. ### `bxc logout` Revoke the saved credential through Sandbox API v2, then remove the local credential and configuration files. `bxc auth logout` is equivalent. Logout requires a working connection so that access is revoked server-side. ### `bxc doctor` Verify authentication by listing Sandboxes. Human output shows the connected origin and number of Sandboxes. JSON output has this shape: ```json {"connected":true,"url":"https://app.boxcompute.ai","sandboxes":2} ``` ### `bxc whoami` Show the account and API key behind the saved credential. It takes no options. Human output is the account's email (or name or ID) and ID, then the key's name, ID and scopes. JSON output is the unwrapped `{ "account": {...}, "apiKey": {...} }` object from `GET /api/v2/me`. ### `bxc update` Read the latest package version from npm and, when it is newer, run a global install of that exact version. `bxc up` is equivalent. This command does not require BoxCompute authentication. ### `bxc version` Print the installed version and exit. `bxc --version` is equivalent. ## Discover resources ### `bxc workspaces` List workspaces owned by the authenticated account: ```text WORKSPACE_IDNAME ``` Use a workspace ID with `bxc sandbox start`. In JSON mode, the result is `{ "workspaces": [...] }`. ### `bxc sandboxes` List owned Sandbox instances: ```text SANDBOX_IDSTATENAME ``` `bxc list` and `bxc ls` are aliases. In JSON mode, the result is `{ "sandboxes": [...] }`. A Sandbox state is `cold`, `pending`, `running`, or `expired`. New VM creation can remain `pending` while provisioning. ## Credit, wallet history, and usage CLI 0.8.0 added the cost commands below, and 0.10.0 added `credits`, `billing transactions` and `usage`. These account-owned reads require `usage:read`, remain available at zero credit, and never start or resume compute. See [credit and wallet history](/costs#check-credit-and-wallet-history) for monetary units, null estimates, and the difference between wallet deductions and observed AI costs. ```bash bxc credits bxc --json billing transactions --bucket promo --limit 20 bxc usage --days 7 bxc costs --from 2026-10-01T00:00:00Z --to 2026-10-11T00:00:00Z ``` | Command | Options and JSON output | | --- | --- | | `bxc credits` (or `bxc billing`) | No filters; `{ "billing": BillingSummary }` | | `bxc billing transactions` | `--from`, `--to`, `--kind`, `--bucket cash\|promo\|plan`, `--before`, `--limit` (1–200, default 50); `{ "currency": "usd", "transactions": [...], "nextCursor": string \| null }` | | `bxc usage` | `--days` (1–90, default 30); `{ "usage": Usage }` | | `bxc costs` | `--from`, `--to`, `--api-key ID` (or `none`); `{ "costs": SandboxCostReport }` | | `bxc sandbox costs SANDBOX_ID` | `{ "costs": SandboxCostDetail }` | | `bxc deleted costs DELETED_ID` | The same detail shape for a deletion-record ID | Transactions return one newest-first page. Pass its `nextCursor` as `--before` with the same filters to continue; `null` means the end. JSON preserves signed integer micro-USD amounts. ## API audit log ### `bxc audit` Page the account's [API audit log](/audit-log/), newest first. It requires `usage:read`. ```bash bxc audit --outcome error bxc --json audit --type api.request --resource SANDBOX_ID --limit 200 ``` | Option | Meaning | | --- | --- | | `--type TYPE` | `api.request`, `api_key.created`, `api_key.revoked`, or `api_key.rejected`. | | `--outcome OUTCOME` | `success` or `error`. | | `--method METHOD` | `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE`. | | `--api-key API_KEY_ID` | One API key. | | `--resource RESOURCE_ID` | One resource, such as a Sandbox ID. | | `--from TIMESTAMP`, `--to TIMESTAMP` | RFC 3339 window. The CLI requires `--from` to be earlier than `--to`. | | `--before CURSOR` | The `nextCursor` from the previous page. | | `--limit EVENTS` | Positive whole number, at most 200. Defaults to 50 on the API. | Human output is one tab-separated event per line: time, type, method and path, status, and resource ID. When more events exist, the command for the next page is printed to standard error. JSON output is `{ "events": [...], "nextCursor": string | null }`. ## Manage Sandboxes ### `bxc sandbox start WORKSPACE_ID` Create and start another Sandbox under a workspace. It returns the new Sandbox ID, state, and name. Each successful call can create a distinct instance, so save the returned ID and inspect the list before retrying after an uncertain network failure. The start command accepts `--idempotency-key KEY` (required with `--vm`; reusable for any create) and `--name NAME`. It creates a VM Sandbox by default; use `--size small` or `--size large` and `--no-wait` to return the creation receipt without waiting for a pending VM to reach running. Legacy `--gvisor` and `--cpu` options request new gVisor creation, which the hosted API rejects with `400 INVALID_REQUEST`. ### `bxc sandbox resume SANDBOX_ID` Start the same existing stopped VM with its retained disk and files; compute billing resumes. Optional `--idempotency-key KEY` recovers the same request, and `--no-wait` returns a pending receipt without polling. This calls the existing-slot start route with `{}` rather than creating a new Sandbox. An uncertain response is not automatically retried: inspect status before deciding what to do. An unavailable legacy gVisor runtime cannot be recreated through resume. ### `bxc sandbox stop SANDBOX_ID` CLI 0.11.0 and later can stop a running VM now, ending compute billing while keeping its disk and files. Running programs and connections end. Resume the same VM when you need it again: ```bash bxc sandbox stop SANDBOX_ID bxc sandbox resume SANDBOX_ID ``` Stop requires `sandbox:create`, never wakes a stopped VM, and is safe to repeat. JSON mode returns `{ "sandbox": {...}, "stopped": true }`; `stopped: false` means it was not running. An HTTP 409 means the VM is still starting, stopping or in use; retry shortly. See [VM lifecycle](/sandboxes#stop-a-vm). ### `bxc sandbox status SANDBOX_ID` Inspect one Sandbox without executing a command. JSON mode returns `{ "sandbox": {...} }`. ### `bxc sandbox exec SANDBOX_ID -- PROGRAM [ARG...]` Execute a structured argument vector inside a Sandbox: ```bash bxc sandbox exec SANDBOX_ID \ --cwd /workspace/project \ --env NODE_ENV=test \ --env CI=true \ --timeout 300 \ --max-output-bytes 1048576 \ -- npm test -- --runInBand ``` | Option | Meaning | | --- | --- | | `--cwd PATH` | Working directory at `/workspace` or below. Defaults to `/workspace`. | | `--env KEY=VALUE` | Set an environment variable. Repeat the option for more variables. The first `=` separates the name from its value. | | `--timeout SECONDS` | Positive whole-number execution timeout. The API accepts at most 900 seconds. | | `--max-output-bytes BYTES` | Positive whole-number capture limit. The API accepts at most 1,048,576 bytes per stream. | | `--` | Required separator between CLI options and the remote program. | In human mode, remote standard output stays on local standard output. Remote standard error and an execution summary are written to local standard error. The CLI exits with the remote `exitCode`. When no exit code is available, the API reports `-1` and the CLI passes it through, so the process exits with status 255 on Linux and macOS. A non-zero exit therefore reports command failure even though the API request itself succeeded. In JSON mode, standard output contains the API's execution result plus `sandboxId`: ```json { "sandboxId": "sbx_REDACTED", "stdout": "tests passed\n", "stderr": "", "exitCode": 0, "timedOut": false, "stdoutTruncated": false, "stderrTruncated": false } ``` ### `bxc sandbox logs SANDBOX_ID` Read current or retained logs without starting stopped compute: ```bash bxc sandbox logs SANDBOX_ID \ --since 2026-09-06T00:00:00Z \ --until 2026-09-07T00:00:00Z \ --stream stderr \ --source execute \ --limit 100 ``` | Option | Accepted value | | --- | --- | | `--since TIMESTAMP` | Include entries at or after this RFC 3339 time. | | `--until TIMESTAMP` | Include entries before this RFC 3339 time. It must be later than `--since`. | | `--stream STREAM` | `stdout` or `stderr`. | | `--source SOURCE` | `workload`, `execute`, or `process`. | | `--limit ENTRIES` | Positive whole number, at most 5,000. Defaults to 1,000 on the API. | Human output is one tab-separated entry per line, followed by a summary on standard error. JSON output is `{ "logs": {...} }` and preserves the structured entries, retention window, and truncation marker. See [Read Sandbox logs](/logs/) for source semantics and retention behavior. ### `bxc sandbox upload SANDBOX_ID LOCAL REMOTE` Upload one regular local file of at most 8 MiB as raw bytes to a path at `/workspace` or below. The remote parent directory must already exist; create it with `sandbox exec`. ### `bxc sandbox download SANDBOX_ID REMOTE LOCAL` Download a complete remote file, following continuation cursors to EOF. The local path must not exist. The file is written with owner-only permissions and appears only after the whole download succeeds; a failure, a file that changes size during the download, or five minutes elapsing discards the partial data. Neither transfer is retried. Both count as VM activity and start a stopped VM unless it was created with `autoResume: false`. JSON output is `{ "sandboxId": "...", "source": "...", "destination": "...", "bytes": 1234 }`. ### `bxc sandbox analytics SANDBOX_ID` Read lifecycle, operation, and resource [analytics](/http-api#response-objects) without starting compute. It requires `sandbox:read`. | Option | Meaning | | --- | --- | | `--from TIMESTAMP`, `--to TIMESTAMP` | RFC 3339 window. The CLI requires `--from` to be earlier than `--to`. | | `--resolution SECONDS` | Bucket width from 60 to 86,400 seconds. Defaults to 300 on the API. | | `--generation N` | Select one runtime generation. | Human output lists each runtime generation and the window's operation, execution, and failure totals, with a summary on standard error. Use `--json` for `{ "analytics": {...} }`, including the resource time series. ### `bxc sandbox preview SANDBOX_ID` Open a one-hour HTTP/WebSocket [browser preview](/http-api#vm-browser-previews) for one port of an owned, running, network-enabled VM, or close one early. Pass exactly one option: ```bash bxc sandbox preview SANDBOX_ID --port 3000 bxc sandbox preview SANDBOX_ID --close PREVIEW_ID ``` | Option | Meaning | | --- | --- | | `--port PORT` | VM port to publish, 1–65,535. | | `--close PREVIEW_ID` | Close that preview instead of opening one. | Opening prints the URL on standard output and the preview ID, expiry, and close command on standard error. JSON output is `{ "preview": {...} }`; closing returns `{ "sandboxId": "...", "previewId": "...", "closed": true }`. The URL is a bearer link: anyone holding it can reach the port until it expires or is closed. A VM has at most one preview at a time. Requires `sandbox:execute` and CLI 0.8.0 or later. ### `bxc sandbox expose SANDBOX_ID --port [LOCAL:]REMOTE` Forward [selected TCP ports](/http-api#selected-tcp-service-access) of an owned, running, network-enabled VM to `127.0.0.1` on this machine: ```bash bxc sandbox expose SANDBOX_ID --port 3000 bxc sandbox expose SANDBOX_ID --port 8080:80 --port 5432 ``` Repeat `--port` for up to eight unique ports; the local port defaults to the remote port. The command stays in the foreground for a one-hour lease that never renews. Ctrl-C closes it sooner and attempts to revoke the grant; run it again for a new lease. It does not create a public URL, forward UDP, or support `--json`. Requires `sandbox:execute`, Linux or macOS on x64 or arm64, and CLI 0.8.0 or later. ### `bxc sandbox ssh SANDBOX_ID` Experimental and off by default: set `BOXCOMPUTE_ENABLE_SSH=1` to use it. It opens a non-PTY SSH connection through a [cooperative connection](/http-api#operator-selected-cooperative-connections) grant that lasts at most 30 seconds. It works only for Sandboxes an operator has enabled. ```bash BOXCOMPUTE_ENABLE_SSH=1 bxc sandbox ssh SANDBOX_ID ``` | Option | Meaning | | --- | --- | | `--reconnect` | Verify one reconnect with the same grant after SSH exits. | | `--revoke ENDPOINT_ID` | Request best-effort cleanup of an earlier grant without opening SSH. | It supports Linux and macOS on x64 or arm64, never starts or provisions a Sandbox, never retries activation, and does not support `--json`. Cleanup is unconfirmed; if revocation is uncertain, the CLI prints the `--revoke` command to run. Use `sandbox exec` for ordinary work. ### `bxc sandbox delete SANDBOX_ID --yes` Destroy the Sandbox runtime and customer-facing instance. `bxc sandbox rm` is an alias. The explicit `--yes` confirmation is required. Deletion leaves the parent workspace intact and cannot be undone through the CLI. ## Deleted Sandboxes These reads use the [deletion record](/sandboxes#review-deleted-sandboxes), never restore or start compute, and require `sandbox:read` (`bxc deleted costs` requires `usage:read`; see [Credit, wallet history, and usage](#credit-wallet-history-and-usage)). ### `bxc deleted list` List up to the 100 most recently deleted Sandboxes. `bxc deleted ls` and `bxc deleted` alone are equivalent. Human output is `DELETED_ID`, original Sandbox ID, name, and deletion time per line; JSON output is `{ "deletedSandboxes": [...] }`. ### `bxc deleted logs DELETED_ID` Read a deleted Sandbox's [retained logs](/logs#after-deletion) by its deletion-record ID, the first column of `bxc deleted list`, not the original Sandbox ID. It accepts the `sandbox logs` options plus `--runtime RUNTIME`, an unchanged value from the record's `runtimes`, to read an earlier runtime instead of the most recent one. Output matches `sandbox logs`. ## Self-hosted runners ### `bxc runner` CLI 0.12.0 can run a BoxCompute chat's shell and file tools on your own machine instead of a BoxCompute VM. This requires early access, Node.js 22 or newer, and Docker for the default runner. In **Settings → Runners**, add a runner and copy its token, which is shown only once. On that machine: ```bash read -rsp 'Runner token: ' \ BOXCOMPUTE_RUNNER_TOKEN && echo export BOXCOMPUTE_RUNNER_TOKEN bxc runner ``` Keep the token in a secret store, not in command arguments or a committed file. This token is separate from `bxc login`. The runner makes an outbound connection; no inbound port is needed. In a new chat, choose the runner under **Run on**. That chat keeps its selected runner. Each chat gets its own Docker container and persistent `/workspace` volume. Containers stop after 30 minutes without commands and restart with their files on the next command. `bxc runner` stays in the foreground and does not support `--json`; Ctrl-C stops running commands but keeps containers and files. It reconnects after network failures and server restarts, but exits when its token is rejected or replaced by another connection. | Option | Meaning | | --- | --- | | `--image IMAGE` | Container image; it needs `bash` and `python3`. Defaults to `node:22-bookworm`. | | `--idle-stop DURATION` | Stop a chat's container after this long without commands, such as `2h`; `0` never stops it. Defaults to `30m`. Cannot be combined with `--dispatch`. | | `--dispatch PROGRAM` | Hand each operation to `PROGRAM` instead of Docker, which is then not required. The [CLI README](https://github.com/boxcompute/cli#custom-dispatch) documents the contract. | | `--url URL` | Connect to this BoxCompute web origin. Defaults to the saved login origin, then `https://app.boxcompute.ai`. | | `--verbose` | Also log each command's arguments. | The model, chat history and approval controls stay in BoxCompute; model usage is metered as usual, but no BoxCompute VM compute credit is charged. Commands have the runner machine's network access, including any reachable private systems. Runner chats do not offer VM management, browser previews, desktop, browser-task or Assets tools; attached files are not supported. Copy files out with Docker. Remove the runner in Settings to revoke its token and disconnect it. Its chats then refuse to run rather than silently moving to a BoxCompute VM. Removing a runner or exiting the CLI does not delete its local containers and files; clean them up on that machine when no longer needed. ## Manage coding-agent skills Skill management does not require BoxCompute authentication. ### `bxc skill detect` Check known configuration directories and executable names for compatible coding agents. Detection does not recursively search your home directory. The command reports detected harnesses and the destination for each skill. ### `bxc skill list` List harnesses that are detected or already have the BoxCompute skill installed. `bxc skill ls` is an alias. ### `bxc skill install [TARGET...]` Install a managed copy of the packaged `boxcompute-sandbox` skill. With no target, `auto` installs to every detected harness. ```bash bxc skill install bxc skill install codex claude bxc skill install both bxc skill install all ``` Targets are `claude`, `codex`, `amp`, `opencode`, `cursor`, `gemini`, `copilot`, `cline`, `roo`, `goose`, `pi`, `windsurf`, and `agents`. `both` expands to Claude Code and Codex. `all` installs every supported destination even when the harness is not detected. `bxc skill add` is an alias. The command will not replace an unrelated or locally modified directory. Pass `--force` only when you intend to overwrite that copy: ```bash bxc skill install codex --force ``` ### `bxc skill remove [TARGET...] --yes` Remove CLI-managed skill copies. With no target, `auto` selects detected or installed harnesses. Explicit confirmation is required. ```bash bxc skill remove codex claude --yes bxc skill remove all --yes ``` The command protects a locally modified copy. Add `--force` only when you intend to delete those modifications. `bxc skill rm` and `bxc skill uninstall` are aliases. ### `bxc skill info` Print the packaged `SKILL.md` to standard output. `bxc skill print` is an alias. This is useful for auditing the exact instructions before installation. ## Managed skill updates The CLI records a digest beside each installed skill. A global CLI update refreshes an untouched managed copy during package installation. If package lifecycle scripts were disabled, the next ordinary `bxc` command performs the same check. Locally modified copies are never overwritten automatically. The CLI reports their paths and leaves replacement behind the explicit `bxc skill install --force` command. Start a new agent session after an update so the harness loads the current instructions. ## Exit statuses | Status | Meaning | | --- | --- | | `0` | The CLI command succeeded. | | `1` | Authentication, network, API, update, or other runtime failure. | | `2` | Invalid CLI usage or options. | | Remote exit code | `sandbox exec` returns the executed program's non-zero exit code. Without one, the API's `-1` becomes status 255 on Linux and macOS. | Error messages are written to standard error. API failures include their HTTP status. # CLI quickstart > Sign up, install bxc, and authenticate from your terminal. Source: https://boxcompute.ai/docs/cli/quickstart/ · Markdown: https://boxcompute.ai/docs/cli/quickstart/index.md Use the BoxCompute CLI when you want to manage remote Sandboxes from a terminal or connect a local coding agent without handling API keys yourself. The CLI creates VM Sandboxes; new gVisor creation is disabled. It can also open browser previews and forward VM ports; see the [command reference](/cli/commands/). > **New to BoxCompute?** > > [Sign up](https://app.boxcompute.ai/signup). Eligible new customers receive $5 in free compute credit, valid for 14 days. No card required. ## 1. Create your account [Sign up](https://app.boxcompute.ai/signup) with your email or Google account. You must have an account before the CLI can authorize your terminal. Some requests wait for [sign-up review](/api-keys#sign-up-review) before a link is sent. Afterwards, you can sign in at [https://app.boxcompute.ai](https://app.boxcompute.ai). ## 2. Install the CLI Install Node.js 20 or newer, then install the public CLI package: ```bash npm install --global @boxcompute/cli ``` Confirm that `bxc` is available: ```bash bxc version ``` Bun, pnpm, and Yarn installation options are available in the [complete CLI guide](/cli/). ## 3. Log in ```bash bxc login ``` The CLI prints a short approval code and opens the BoxCompute app in your browser. Sign in with your registered account, confirm that the browser shows the same code, and approve the device. After approval, the terminal completes login and saves a protected CLI credential outside your project. You do not need to create or paste an API key for CLI login. If the terminal cannot open a browser, use: ```bash bxc login --no-open ``` Open the printed URL yourself, sign in, and approve the matching code. ## 4. Verify the connection ```bash bxc doctor bxc workspaces bxc sandboxes ``` `doctor` confirms that the saved credential works. `workspaces` lists the parents available for new Sandboxes, and `sandboxes` lists your existing instances. ## 5. Start and use a Sandbox Copy a workspace ID from `bxc workspaces`, then run: ```bash bxc sandbox start WORKSPACE_ID bxc sandbox exec SANDBOX_ID -- python3 -c 'print(sum(range(100000)))' bxc sandbox status SANDBOX_ID ``` Replace `SANDBOX_ID` with the ID returned by `sandbox start`. Everything after `--` is sent as a structured argument vector to the remote Sandbox. Sandboxes are persistent resources. Keep the returned ID for later work, or delete the instance explicitly when you no longer need it: ```bash bxc sandbox delete SANDBOX_ID --yes ``` Deletion is permanent and leaves the parent workspace intact. ## Next steps - Read the [complete CLI guide](/cli/) for runtimes, JSON output, updates, and credential storage. - See the [CLI command reference](/cli/commands/). - Connect [Codex CLI, Claude Code, or another coding agent](/cli/agents/). # Track Sandbox costs > Check credit and wallet history, then attribute compute cost to API keys, Sandboxes, and charged runs. Source: https://boxcompute.ai/docs/costs/ · Markdown: https://boxcompute.ai/docs/costs/index.md Use the public cost routes to attribute Sandbox compute to the API key that created each Sandbox. These routes require `usage:read` and are available through direct HTTP, SDK 0.4.0, and the CLI. In the BoxCompute app, open **Computers** and select a computer to see its **Cost so far** and the account's **Credit left**. Use the routes below for the breakdown by creating API key and charged run. ## Starter monthly compute cap On Starter, each computer is charged for at most 100 hours per UTC calendar month; its remaining compute that month is free. At current rates, the monthly compute maximum is $5 for Small, $15 for Large, $40 for XL and $80 for XXL. This is per computer, not an account-wide allowance, and excludes the Starter subscription and model usage. Purchased credit or organization membership alone does not enable the cap. A run's charge belongs to the month in which it ends. The computer's page shows progress toward the cap. Cost reports and wallet deductions use the capped charge, so `amountMicros` can be less than the listed rate multiplied by billable time, or zero. Credit checks still apply: a VM stops at zero available credit even after reaching its cap. To keep a Starter computer running between tasks, use the app's [Always on schedule](/vm-sandbox-beta#keep-a-starter-computer-on). ## Check credit and wallet history `GET /api/v2/billing` and `GET /api/v2/billing/transactions` require `usage:read`, remain readable at zero credit, and never start or resume compute. They return only the authenticated account's data. All monetary amounts are integer micro-USD: 1,000,000 micros is USD 1. ```bash curl --fail-with-body -sS 'https://api.boxcompute.ai/api/v2/billing' \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" ``` An illustrative response with no running compute: ```json { "billing": { "currency": "usd", "accountStatus": "active", "balance": { "cashMicros": 5000000, "promoMicros": 2000000, "planMicros": 0, "totalMicros": 7000000, "availableMicros": 7000000, "reservedMicros": 0, "expiringPromo": { "micros": 2000000, "expiresAt": 1791676800000 } }, "settledUsage": { "aiMicros": 0, "sandboxMicros": 3000000, "totalMicros": 3000000 }, "activeEstimate": { "status": "not_applicable", "asOf": null, "runtimeSeconds": 0, "billableSeconds": 0, "amountMicros": 0 }, "modelBillingMode": "observe" } } ``` `totalMicros` is the wallet balance; `availableMicros` is spendable credit after reservations and running compute. If live estimates fail, `activeEstimate.status` is `unavailable` and `availableMicros` is `null`—unknown, not zero. `settledUsage` is all-time verified AI and compute wallet deductions, not a date-window report, reservations, or running estimates. In `observe` model billing mode, recorded AI costs do not debit the wallet. `expiringPromo` is nullable; its `expiresAt`, like transaction timestamps, is Unix milliseconds. Read one newest-first wallet page: ```bash curl --fail-with-body -sS --get 'https://api.boxcompute.ai/api/v2/billing/transactions' \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ --data-urlencode 'bucket=promo' \ --data-urlencode 'limit=20' ``` The response is `{ "currency": "usd", "transactions": [...], "nextCursor": string | null }`. Each entry contains `id`, `kind`, `bucket`, signed `amountMicros`, `description`, `createdAt`, and nullable `expiresAt`. Positive amounts add credit; negative amounts subtract it. Entries are bucket-level, so one charge can have multiple entries. Pass `nextCursor` as `before` with the same filters until it is `null`. `limit` is 1–200 (default 50). Optional filters are inclusive `from` and exclusive `to` RFC 3339 timestamps, `kind`, and `bucket` (`cash`, `promo`, or `plan`). SDK 0.4.0 unwraps the summary, but returns the transaction page directly: ### TypeScript ```ts const billing = await boxcompute.billing.get(); const page = await boxcompute.billing.transactions({ bucket: "promo", limit: 20, }); console.log(billing.balance.availableMicros); console.log(page.nextCursor); ``` ### Python ```python billing = boxcompute.billing.get() page = boxcompute.billing.transactions( bucket="promo", limit=20, ) print(billing.balance.available_micros) print(page.next_cursor) ``` These snippets use the authenticated client from the [SDK quickstart](/quickstart); Python's date-start filter is `from_`. CLI 0.10.0 provides `bxc credits`, `bxc billing transactions`, and `bxc usage`; see [CLI commands](/cli/commands#credit-wallet-history-and-usage). ## Get an account report Request the default 30-day window: ```bash curl 'https://api.boxcompute.ai/api/v2/costs/sandboxes' \ -H 'Authorization: Bearer bc_live_REDACTED' ``` Set an inclusive `from` and exclusive `to` with RFC 3339 timestamps when you need another period. A window can span at most 400 days: ```bash curl --get 'https://api.boxcompute.ai/api/v2/costs/sandboxes' \ -H 'Authorization: Bearer bc_live_REDACTED' \ --data-urlencode 'from=2026-09-01T00:00:00Z' \ --data-urlencode 'to=2026-10-01T00:00:00Z' ``` The response groups the same costs by creating API key and by Sandbox: ```json { "costs": { "from": 1788220800000, "to": 1790812800000, "currency": "usd", "estimates": "available", "totals": { "runs": 3, "billableSeconds": 420, "settledMicros": 7000, "estimatedMicros": 500 }, "apiKeys": [ { "apiKey": { "id": "key_REDACTED", "name": "Batch jobs", "hint": "ab12", "revoked": false }, "sandboxes": 1, "runs": 3, "billableSeconds": 420, "settledMicros": 7000, "estimatedMicros": 500 } ], "sandboxes": [ { "id": "sbx_REDACTED", "sandboxId": "sbx_REDACTED", "name": "Data import", "deleted": false, "vmSandbox": true, "size": "small", "createdByApiKeyId": "key_REDACTED", "createdAt": 1788220800000, "deletedAt": null, "firstStartedAt": 1788307200000, "lastEndedAt": 1788912000000, "runs": 3, "billableSeconds": 420, "settledMicros": 7000, "estimatedMicros": 500 } ], "unattributed": { "runs": 0, "billableSeconds": 0, "settledMicros": 0, "estimatedMicros": 0 } } } ``` Amounts are integer millionths of the stated currency: `1,000,000` micros is USD 1. Settled amounts are exact ledger charges for runs that ended in the window. `estimatedMicros` covers work still running and is replaced by its settled charge after that run ends. `estimates` is `available`, `not_applicable`, or `unavailable`; an unavailable estimate does not make settled amounts approximate. Billing is per second with a 60-second minimum for each run. A report includes active and deleted Sandboxes. Its `sandboxes` rows identify the original customer-facing `sandboxId`, creation key, runtime type and size, timestamps, run count, billable seconds, and settled and estimated amounts. `unattributed` contains charges whose surviving records cannot identify a Sandbox. Filter by one creating key with `apiKeyId=key_REDACTED`. Use `apiKeyId=none` for Sandboxes created without an API key, such as from the dashboard or another account-owned product surface. Revoked keys remain identifiable in historical results. ## Inspect charged runs Get every retained charged run plus any running estimate for an active Sandbox: ```bash curl 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/costs' \ -H 'Authorization: Bearer bc_live_REDACTED' ``` For a deleted Sandbox, use the `dsb_…` deletion-record ID returned by `GET /api/v2/deleted-sandboxes`: ```bash API_BASE='https://api.boxcompute.ai/api/v2' curl "$API_BASE/deleted-sandboxes/dsb_REDACTED/costs" \ -H 'Authorization: Bearer bc_live_REDACTED' ``` The `{ "costs": ... }` detail includes Sandbox and creating-key metadata, `currency`, estimate status, settled `runs`, current `running` estimates, and `truncated`. Each settled run reports `startedAt`, `endedAt`, `runtimeSeconds`, `billableSeconds`, `rateMicrosPerMinute`, and `amountMicros`. At most 1,000 settled runs are returned; `truncated: true` means additional runs were omitted. Deleted-Sandbox detail has no running estimate. Cost lookups are limited to the authenticated account. An unknown or cross-account active or deleted Sandbox returns `404`. # Execute commands > Run a bounded command synchronously or as an independently observable operation. Source: https://boxcompute.ai/docs/execute/ · Markdown: https://boxcompute.ai/docs/execute/index.md Execute a command in an owned Sandbox by sending a structured `argv` array through an official SDK. Synchronous execute and operation start normally wake a stopped VM. If it was created with `autoResume: false`, they return `409 SANDBOX_STOPPED` instead; [start the same Sandbox](/sandboxes#start-an-existing-slot) explicitly first. Inspecting, waiting for and reading output from an existing operation never wake it. For example: ### TypeScript ```ts const result = await boxcompute.sandboxes.execute("sbx_REDACTED", { argv: ["python3", "-c", "print(sum(range(100000)))"], cwd: "/workspace/jobs", env: { MODE: "development" }, timeoutSeconds: 120, maxOutputBytes: 262_144, }); ``` ### Python ```python result = boxcompute.sandboxes.execute( "sbx_REDACTED", argv=["python3", "-c", "print(sum(range(100000)))"], cwd="/workspace/jobs", env={"MODE": "development"}, timeout_seconds=120, max_output_bytes=262_144, ) ``` For a direct HTTP client, the equivalent request is: ```bash curl -X POST 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/execute' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/json' \ -d '{ "argv":["python3","-c","print(sum(range(100000)))"], "cwd":"/workspace/jobs", "env":{"MODE":"development"}, "timeoutSeconds":120, "maxOutputBytes":262144 }' ``` ## Input - `argv` is required and contains 1–64 non-empty strings. Each string can be at most 8,192 characters. - `cwd` defaults to `/workspace` and must be `/workspace` or a path below it. - `env` is optional and can contain at most 64 variables. Names must be valid environment-variable names and values must be strings. - `timeoutSeconds` defaults to `120` and must be a whole number from 1 through 900. - `maxOutputBytes` defaults to `262144` and must be from 1 through 1,048,576. `argv` is structured data, not shell source. Pass user-controlled values as separate entries instead of interpolating them into a shell command. ## Result The response wraps the result: ```json { "result": { "stdout": "4999950000\n", "stderr": "", "exitCode": 0, "timedOut": false, "stdoutTruncated": false, "stderrTruncated": false } } ``` A non-zero exit code is still a successful HTTP response. Treat the command as successful only according to `exitCode` and `timedOut`. If either truncation field is `true`, the corresponding captured stream is incomplete. ## Run an observable operation Use an operation when a command must remain observable after the starting HTTP connection closes. The request body and execution limits are the same as synchronous execute, but `Idempotency-Key` is required: ### TypeScript ```ts let operation = await boxcompute.operations.start("sbx_REDACTED", { argv: ["python3", "-c", "print(sum(range(100000)))"], cwd: "/workspace", timeoutSeconds: 120, maxOutputBytes: 262_144, idempotencyKey: crypto.randomUUID(), }); while (operation.state === "accepted" || operation.state === "running") { operation = await boxcompute.operations.wait( "sbx_REDACTED", operation.operationId, { timeoutSeconds: 30 }, ); } const stdout = await boxcompute.operations.output( "sbx_REDACTED", operation.operationId, { stream: "stdout", offset: 0 }, ); ``` ### Python ```python import uuid operation = boxcompute.operations.start( "sbx_REDACTED", argv=["python3", "-c", "print(sum(range(100000)))"], idempotency_key=str(uuid.uuid4()), ) while operation.state.value in {"accepted", "running"}: operation = boxcompute.operations.wait( "sbx_REDACTED", operation.operation_id, timeout_seconds=30, ) stdout = boxcompute.operations.output( "sbx_REDACTED", operation.operation_id, stream="stdout", offset=0, ) ``` The SDK exposes `inspect`, `output`, `wait`, and `cancel` on its `operations` resource. With direct HTTP, start the same operation as follows: ```bash curl -X POST 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/operations' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Idempotency-Key: operation-019f1234-5678-7000-8000-000000000000' \ -H 'Content-Type: application/json' \ -d '{"argv":["python3","-c","print(sum(range(100000)))"],"cwd":"/workspace"}' ``` The `202` response contains `{ "operation": ... }`. Save its opaque `operationId`. If the response is interrupted, retry the identical request with the same account-scoped key within 24 hours to recover the original response and operation ID. ```json { "operation": { "operationId": "op_0123456789abcdef0123456789abcdef", "state": "accepted", "exitCode": null, "signal": null, "acceptedAt": "2026-09-06T07:00:00Z", "startedAt": null, "finishedAt": null, "stdoutOffset": 0, "stderrOffset": 0, "expiresAt": null } } ``` Inspect the operation or wait up to 30 seconds for progress: ```bash curl 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/operations/op_REDACTED' \ -H 'Authorization: Bearer bc_live_REDACTED' curl -X POST 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/operations/op_REDACTED/wait' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/json' \ -d '{"timeoutSeconds":30}' ``` A wait timeout returns the current operation with `200`; it is not an execution timeout and does not cancel the command. States are `accepted`, `running`, `completed`, `failed`, `timedOut`, and `cancelled`. The last four are immutable terminal states. A non-zero command exit is `completed` with a non-zero `exitCode`. Read stdout or stderr using absolute byte offsets: ```bash curl 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/operations/op_REDACTED/output?stream=stdout&offset=0&limit=65536' \ -H 'Authorization: Bearer bc_live_REDACTED' ``` The response contains base64-encoded bytes in `dataBase64` plus `offset`, `nextOffset`, `earliestOffset`, and `truncated`. Continue from `nextOffset`. `limit` defaults to 65,536 decoded bytes and can be at most 262,144. When `truncated` is true, bytes before `earliestOffset` are no longer retained. ```json { "stream": "stdout", "dataBase64": "NDk5OTk1MDAwMAo=", "offset": 0, "nextOffset": 11, "earliestOffset": 0, "truncated": false } ``` To request remote cancellation, call the cancel route explicitly: ```bash curl -X POST 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/operations/op_REDACTED/cancel' \ -H 'Authorization: Bearer bc_live_REDACTED' ``` Cancellation is confirmed only when the returned state is `cancelled`. If completion wins the race, the route returns that completed terminal result. Closing or aborting any client request never requests remote cancellation. Terminal metadata and up to `maxOutputBytes` retained bytes from each output stream remain available until `expiresAt`, 24 hours after completion. Operation IDs are scoped to their owning account and Sandbox; expired, unknown, mismatched, and cross-account IDs all return `404 NOT_FOUND`. # Manage files > Read, write, inspect, list, create, edit, rename, and remove paths in an owned Sandbox workspace. Source: https://boxcompute.ai/docs/files/ · Markdown: https://boxcompute.ai/docs/files/index.md The official SDKs provide binary-safe file operations for an owned Sandbox. Every `path` must be `/workspace` or an absolute path below it, can contain at most 4,096 characters, and cannot contain `.` or `..` path segments. Reading, listing, and inspecting require `sandbox:read`. Writing, editing, renaming, creating directories, and removing paths require `sandbox:execute`. Requests are limited to the authenticated account's Sandboxes; an unknown or cross-account Sandbox returns `404`. Every file call normally wakes a stopped VM and resumes compute billing. If it was created with `autoResume: false`, the call instead returns `409 SANDBOX_STOPPED`; [start it explicitly](/sandboxes#start-an-existing-slot) before accessing files. Reading status or retained logs does not wake it. ## Use an official SDK ### TypeScript ```ts await boxcompute.files.mkdir("sbx_REDACTED", "/workspace/src", { recursive: true }); await boxcompute.files.write( "sbx_REDACTED", "/workspace/src/main.js", new TextEncoder().encode("console.log('hello')\n"), ); const page = await boxcompute.files.read("sbx_REDACTED", "/workspace/src/main.js"); console.log(new TextDecoder().decode(page.data)); console.log({ nextOffset: page.nextOffset, fileSize: page.fileSize, eof: page.eof }); const listing = await boxcompute.files.list("sbx_REDACTED", "/workspace/src"); console.log(listing.entries); ``` ### Python ```python boxcompute.files.mkdir("sbx_REDACTED", "/workspace/src", recursive=True) boxcompute.files.write( "sbx_REDACTED", "/workspace/src/main.js", b"console.log('hello')\n", ) page = boxcompute.files.read("sbx_REDACTED", "/workspace/src/main.js") print(page.data.decode()) print({"next_offset": page.next_offset, "file_size": page.file_size, "eof": page.eof}) listing = boxcompute.files.list("sbx_REDACTED", "/workspace/src") print(listing.entries) ``` ## Direct HTTP ## Write and read a file Create parent directories first, then upload raw bytes: ```bash curl -X POST \ 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/files/directory?path=%2Fworkspace%2Fsrc' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/json' \ -d '{"recursive":true}' curl -X PUT \ 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/files/content?path=%2Fworkspace%2Fsrc%2Fmain.js' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/octet-stream' \ --data-binary @main.js ``` Both successful mutations return `204 No Content`. `recursive` defaults to `false`; when it is `true`, creating an existing directory succeeds. Download the first bounded range and save its response headers: ```bash curl --get \ 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/files/content' \ -H 'Authorization: Bearer bc_live_REDACTED' \ --data-urlencode 'path=/workspace/src/main.js' \ --data-urlencode 'offset=0' \ --data-urlencode 'maxBytes=262144' \ --dump-header range.headers \ --output main.js.part ``` Each response contains at most `maxBytes`, which defaults to 8 MiB and can be 1 byte through 8 MiB. `X-BoxCompute-Offset`, `X-BoxCompute-Next-Offset`, `X-BoxCompute-File-Size`, and `X-BoxCompute-EOF` describe the returned range. When EOF is false, the response also includes `X-BoxCompute-Next-Cursor`. Request the next range with that cursor unchanged and `offset` set to `X-BoxCompute-Next-Offset`. Stop when `X-BoxCompute-EOF` is `true`. The cursor keeps a multi-request read on one file version. If the file changes, continuation returns `409 CURSOR_STALE`; restart at offset 0 without a cursor. An invalid cursor returns `400 INVALID_CURSOR`, and an offset beyond EOF returns `400 INVALID_RANGE`. Writes remain limited to 8 MiB per complete file. ## Edit text atomically Hash the complete bytes you read, then send that hash with a conditional text replacement: ```bash expected_sha256=$(sha256sum main.js | cut -d ' ' -f1) curl -X PATCH \ 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/files/content?path=%2Fworkspace%2Fsrc%2Fmain.js' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/json' \ -d "{\"expectedSha256\":\"$expected_sha256\",\"oldText\":\"development\",\"newText\":\"production\",\"replaceAll\":false}" ``` `expectedSha256` is the 64-character lowercase hexadecimal SHA-256 of the complete file bytes you read. The file must be valid UTF-8 and at most 8 MiB. `oldText` must contain 1–65,536 characters and `newText` at most 65,536. By default, `oldText` must occur exactly once; set `replaceAll` to `true` to replace every occurrence. Success returns the SHA-256 of the new complete file: ```json { "sha256": "3a7bd3e2360a3d29eea436fcfb7e44c735d117969b4d1f7a39c842f2e8e79a9c" } ``` A stale hash returns `412 PRECONDITION_FAILED` without changing the file. Missing or ambiguous `oldText` returns `409 EDIT_CONFLICT`, and binary content returns `400 BINARY_FILE`. If the connection fails before a response arrives, read the file again before deciding whether to retry. ## Inspect and list ```bash curl \ 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/files/stat?path=%2Fworkspace%2Fsrc%2Fmain.js' \ -H 'Authorization: Bearer bc_live_REDACTED' curl \ --get 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/files/list' \ -H 'Authorization: Bearer bc_live_REDACTED' \ --data-urlencode 'path=/workspace/src' \ --data-urlencode 'pageSize=100' ``` Stat returns `{ "file": ... }` with `path`, `kind`, `isSymbolicLink`, `size`, and Unix-millisecond `mtimeMs`. Stat follows a symbolic link for its `kind`, size, and modification time while preserving `isSymbolicLink: true`. List returns `{ "entries": [...], "nextCursor": string | null }` for one page of the directory's immediate children. Each entry contains `name`, `path`, `kind`, `size`, and `mtimeMs`; list entry `kind` is `file`, `dir`, `symlink`, or `other`. `pageSize` defaults to 1,000 and can be 1–1,000. Pass `nextCursor` unchanged as the `cursor` query parameter until it is `null`. Pages preserve the filesystem's stable native directory order. A directory membership change during pagination returns `409 CURSOR_STALE`; restart from the first page. Metadata for existing children can reflect its value when each page is read. Invalid or path-mismatched cursors return `400 INVALID_CURSOR`. ## Rename or remove a path Rename a path within the same Sandbox workspace: ```bash curl -X POST \ 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/files/rename?path=%2Fworkspace%2Fsrc%2Fmain.js' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/json' \ -d '{"destination":"/workspace/src/app.js","overwrite":false}' ``` Both parent directories must exist. `overwrite` defaults to `false`, so an existing destination returns `409 DESTINATION_EXISTS`; setting it to `true` replaces the destination atomically. Success returns `204 No Content`. If the connection fails before a response arrives, inspect both paths before retrying. Remove a file, symbolic link, or directory: ```bash curl -X DELETE \ 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/files?path=%2Fworkspace%2Fsrc&recursive=true&force=false' \ -H 'Authorization: Bearer bc_live_REDACTED' ``` `recursive` and `force` each default to `false`. A successful removal returns `204 No Content`. Missing files return `404 FILE_NOT_FOUND` unless `force=true`. Traversal and symbolic-link parent components are rejected. Reading rejects a final symbolic link. Writing replaces a final symbolic link instead of following it, and removing it unlinks the link rather than its target. Stat can follow a final symbolic link only when its target remains inside `/workspace`. These routes are for paged, bounded file transfer and filesystem operations. They do not provide a shared volume, checkpoints, forks, or an interactive shell. ## Play a saved video in the app Open a video link in chat, choose a video in the chat's **Files** panel, or select its row or **Play** action in **Assets**. BoxCompute's player opens in a dialog with seeking, speed, volume, picture in picture, full screen and **Download**. Keys: Space or K plays and pauses, Left/Right and J/L seek, Up/Down set the volume, M mutes and F toggles full screen. Ctrl-, Cmd- or Shift-clicking a chat link keeps the download behavior. Only MP4, M4V, MOV, WebM, MKV and OGV files open in the player; other files download. The browser must also decode the video's codec. If it cannot play, the player says why and offers **Download**. This is an app workflow for saved assets, not a video-preview endpoint in the public v2 file API described above. # HTTP API reference > Public v2 routes, authentication, request bodies, and response shapes. Source: https://boxcompute.ai/docs/http-api/ · Markdown: https://boxcompute.ai/docs/http-api/index.md Use the fixed base URL `https://api.boxcompute.ai` and the `/api/v2` routes below. Application integrations should normally start with the [official SDK quickstart](/quickstart); use this page for direct HTTP clients, generators, and API tooling. The [public OpenAPI 3.1 contract](https://api.boxcompute.ai/api/v2/openapi.json) is served as JSON without authentication for generators, contract tests, and API tooling. It describes only this customer surface. It is not the private downstream Sandbox API. Every request except the OpenAPI document requires a BoxCompute API key: ```http Authorization: Bearer bc_live_REDACTED ``` JSON mutation examples send `Content-Type: application/json`; file uploads use `application/octet-stream`. An otherwise valid key for a suspended account returns `403 ACCOUNT_SUSPENDED`. See [API keys](/api-keys#suspended-accounts) for recovery guidance and [Limits and errors](/limits-and-errors) for other failures. ## Routes | Method | Route | Scope | Success | | --- | --- | --- | --- | | `GET` | `/api/v2/openapi.json` | None | Public OpenAPI 3.1 document | | `GET` | `/api/v2/me` | `sandbox:read` | `{ "account": Account, "apiKey": ApiKey }` | | `DELETE` | `/api/v2/auth` | `sandbox:read` | Revokes the current key; `204` | | `GET` | `/api/v2/workspaces` | `sandbox:read` | `{ "workspaces": Workspace[] }` | | `POST` | `/api/v2/workspaces` | `workspace:create` | Creates a persistent workspace; `{ "workspace": Workspace }`, `201` | | `GET` | `/api/v2/sandboxes` | `sandbox:read` | `{ "sandboxes": Sandbox[] }` | | `POST` | `/api/v2/sandboxes` | `sandbox:create` | Creates a VM; pending `202` (replayed `201` once ready); new gVisor requests return `400`; account capacity refusal returns `429` | | `GET` | `/api/v2/sandboxes/:id` | `sandbox:read` | `{ "sandbox": Sandbox }` | | `GET` | `/api/v2/sandboxes/:id/analytics` | `sandbox:read` | `{ "analytics": SandboxAnalytics }` | | `GET` | `/api/v2/sandboxes/:id/logs` | `sandbox:read` | Current or retained output; `{ "logs": SandboxLogs }` | | `POST` | `/api/v2/sandboxes/:id/start` | `sandbox:create` | Starts an existing slot without allocation; `{ "sandbox": Sandbox }`, `201` | | `POST` | `/api/v2/sandboxes/:id/stop` | `sandbox:create` | Stops a running VM now and keeps its disk; `{ "sandbox": Sandbox, "stopped": boolean }`, `200` | | `POST` | `/api/v2/sandboxes/:id/execute` | `sandbox:execute` | `{ "result": ExecutionResult }` | | `GET` | `/api/v2/sandboxes/:id/desktop` | `sandbox:read` | `{ "desktop": DesktopStatus }`; never wakes the VM | | `POST` | `/api/v2/sandboxes/:id/desktop/start` | `sandbox:execute` | Starts the desktop on a running VM; `{ "desktop": DesktopStatus }` | | `POST` | `/api/v2/sandboxes/:id/desktop/stop` | `sandbox:execute` | Stops desktop apps and viewers, not the VM; `{ "desktop": DesktopStatus }` | | `GET` | `/api/v2/sandboxes/:id/desktop/screenshot` | `sandbox:execute` | PNG or JPEG image bytes | | `POST` | `/api/v2/sandboxes/:id/desktop/actions` | `sandbox:execute` | One input action; `DesktopActionResult` | | `POST` | `/api/v2/sandboxes/:id/desktop/viewer` | `sandbox:execute` | `{ "viewer": { "url": string, "expiresAt": string } }` | | `DELETE` | `/api/v2/sandboxes/:id/desktop/viewer` | `sandbox:execute` | Ends viewer links and their connections; `204` | | `GET` | `/api/v2/sandboxes/:id/previews` | `sandbox:execute` | Lists the VM's current preview and state; `{ "previews": ListedPreview[] }`, `200` | | `POST` | `/api/v2/sandboxes/:id/previews` | `sandbox:execute` | Creates a one-hour HTTP/WebSocket preview; `{ "preview": Preview }`, `201` | | `DELETE` | `/api/v2/sandboxes/:id/previews/:previewId` | `sandbox:execute` | Closes the exact preview; `204` | | `POST` | `/api/v2/sandboxes/:id/cooperative-connection` | `sandbox:execute` | Activates an operator-selected cooperative grant; `CooperativeEnvelope`, `201` | | `POST` | `/api/v2/sandboxes/:id/cooperative-connection/:endpointId` | `sandbox:execute` | Returns the unchanged envelope for the same keys | | `DELETE` | `/api/v2/sandboxes/:id/cooperative-connection/:endpointId` | `sandbox:execute` | Requests best-effort cleanup; `CooperativeRevoke`, `202` | | `POST` | `/api/v2/sandboxes/:id/services` | `sandbox:execute` | Creates selected TCP access; `ServiceAccessEnvelope`, `201` | | `POST` | `/api/v2/sandboxes/:id/services/lookup` | `sandbox:execute` | Recovers the original live envelope without renewal | | `DELETE` | `/api/v2/sandboxes/:id/services/:generationId` | `sandbox:execute` | Revokes the exact generation; `ServiceAccessRevoke`, `200` | | `POST` | `/api/v2/sandboxes/:id/operations` | `sandbox:execute` | Starts an observable command; `{ "operation": Operation }`, `202` | | `GET` | `/api/v2/sandboxes/:id/operations/:operationId` | `sandbox:execute` | `{ "operation": Operation }` | | `GET` | `/api/v2/sandboxes/:id/operations/:operationId/output` | `sandbox:execute` | `OperationOutputChunk` | | `POST` | `/api/v2/sandboxes/:id/operations/:operationId/wait` | `sandbox:execute` | Waits up to 30 seconds; `{ "operation": Operation }` | | `POST` | `/api/v2/sandboxes/:id/operations/:operationId/cancel` | `sandbox:execute` | Returns the winning terminal operation state | | `GET` | `/api/v2/sandboxes/:id/files/stat?path=…` | `sandbox:read` | `{ "file": FileStat }` | | `GET` | `/api/v2/sandboxes/:id/files/content?path=…&offset=…&maxBytes=…&cursor=…` | `sandbox:read` | One stable bounded binary range plus continuation headers | | `PUT` | `/api/v2/sandboxes/:id/files/content?path=…` | `sandbox:execute` | Writes binary file content; `204` | | `PATCH` | `/api/v2/sandboxes/:id/files/content?path=…` | `sandbox:execute` | Atomically edits UTF-8 text; `{ "sha256": string }` | | `GET` | `/api/v2/sandboxes/:id/files/list?path=…&pageSize=…&cursor=…` | `sandbox:read` | `{ "entries": FileListEntry[], "nextCursor": string \| null }` | | `POST` | `/api/v2/sandboxes/:id/files/directory?path=…` | `sandbox:execute` | Creates a directory; `204` | | `POST` | `/api/v2/sandboxes/:id/files/rename?path=…` | `sandbox:execute` | Atomically renames a path; `204` | | `DELETE` | `/api/v2/sandboxes/:id/files?path=…&recursive=false&force=false` | `sandbox:execute` | Removes a file or directory; `204` | | `DELETE` | `/api/v2/sandboxes/:id` | `sandbox:delete` | `204` | | `GET` | `/api/v2/usage?days=30` | `usage:read` | `{ "usage": Usage }` | | `GET` | `/api/v2/billing` | `usage:read` | Credit balance and settled spend; `{ "billing": BillingSummary }` | | `GET` | `/api/v2/billing/transactions` | `usage:read` | `BillingTransactionPage` with nullable `nextCursor` | | `GET` | `/api/v2/costs/sandboxes` | `usage:read` | Compute cost by creating API key and Sandbox; `{ "costs": SandboxCostReport }` | | `GET` | `/api/v2/sandboxes/:id/costs` | `usage:read` | Charged runs and a running estimate; `{ "costs": SandboxCostDetail }` | | `GET` | `/api/v2/deleted-sandboxes` | `sandbox:read` | Up to 100 newest deletion records; `{ "deletedSandboxes": DeletedSandbox[] }` | | `GET` | `/api/v2/deleted-sandboxes/:id/logs` | `sandbox:read` | Retained logs addressed by deletion-record ID; `{ "logs": SandboxLogs }` | | `GET` | `/api/v2/deleted-sandboxes/:id/costs` | `usage:read` | Charged runs addressed by deletion-record ID; `{ "costs": SandboxCostDetail }` | | `GET` | `/api/v2/audit-events` | `usage:read` | Newest-first API activity; `{ "events": AuditEvent[], "nextCursor": string \| null }` | | `POST` | `/api/v2/emails` | `email:send` | Plain-text email to the key owner's own address; `{ "email": Email }`, `201`; Starter plus email early access required | ## Request bodies Create a workspace. `name` is required, must contain a non-whitespace character, and is at most 80 characters: ```json { "name": "Agent workspace" } ``` `POST /api/v2/workspaces` requires an `Idempotency-Key` header containing 1–255 visible ASCII characters. The key is scoped to the authenticated account. Repeating the same canonical request within 24 hours returns the exact original `201` body; using the key for a different public mutation returns `409 IDEMPOTENCY_CONFLICT`. The account can own at most 10 workspaces; exceeding the limit returns `409 WORKSPACE_QUOTA_EXCEEDED`. There is no public workspace-delete route. This creates a personal workspace. To use organization VMs, create an owned organization workspace in the app, then pass its `workspaceId` to Sandbox creation. There is no public organization selector or organization-admin API. See [personal and organization VMs](/sandboxes#personal-and-organization-vms). Create a Sandbox in an owned workspace. `name` is optional, non-empty when supplied, and at most 80 characters: ```json { "workspaceId": "ws_REDACTED", "name": "Data import", "size": "small" } ``` `POST /api/v2/sandboxes` requires the header when `vmSandbox` is explicitly `true`; an omitted `vmSandbox` may create keyless, in which case the server generates the key. See [Manage Sandboxes](/sandboxes#retry-creation-safely) and [VM Sandboxes (Beta)](/vm-sandbox-beta#2-create-once-recover-with-the-same-key-and-body) for retry guidance. New creation is VM-only: omitted or `true` creates a VM Sandbox (fixed image; no volumes, libraries, or image override), and `false` returns `400 INVALID_REQUEST` without allocation. `size` is VM-only and defaults to `small`. The [size table](/vm-sandbox-beta#choose-a-vm-size) covers `small`, `large`, `xl`, and `xxl`; XL and XXL are included with Starter and remain available through per-size early access. An account without access receives `403 FEATURE_ACCESS_DENIED`. All sizes default to a 30 GiB thin-provisioned root disk containing the OS and `/workspace`. Optional `diskGiB` is a whole number from 10 to 30; omitting it means 30. Only BoxCompute platform admins can choose a disk below 30 GiB: for any other account, a smaller `diskGiB` returns `400 INVALID_REQUEST` before anything is created, so customer VMs have 30 GiB. The disk cannot be resized after creation and counts in full toward personal or organization storage, as applicable, even while stopped. A retry with a different disk under the same idempotency key conflicts; omitting it and explicitly choosing 30 are equivalent, including for historical replay. Current SDKs and the CLI do not expose disk selection. `blockNetwork` is optional for VM creation and defaults to false, which gives the VM outbound Internet access limited to public IPv4 and DNS; set it to true for a VM with no network interface. Outbound TCP port 25 is blocked. In a network-enabled VM, use a mail provider's authenticated submission on 465/587 or an HTTPS mail API instead. The network intent and size are fixed at creation, and changing them for the same idempotency key returns `409 IDEMPOTENCY_CONFLICT`. A VM request returns `202` while durable provisioning is pending; replaying the same key and body returns `201` once ready. The service does not silently fall back to gVisor when a VM request is unavailable. Completed historical responses retain their original runtime; pending historical gVisor creates fail with `400 INVALID_REQUEST`. Legacy gVisor slots cannot be started, resumed, or recreated through the customer API. Contact support@boxcompute.ai about legacy data; a historical slot does not guarantee a recoverable disk. The SDKs (boxcompute 0.4.0, @boxcompute/sdk 0.4.0) and CLI (bxc 0.10.0) expose VM creation. Sandbox creation and starting an existing slot return `429 WORKSPACE_QUOTA_EXCEEDED` when the applicable personal or organization running-VM, Sandbox-count or storage limit is reached. Stopped Sandboxes count toward Sandbox-count and storage limits, not the running-VM limit. See [capacity recovery](/limits-and-errors#free-up-account-capacity). If a durably accepted VM create is refused for this reason, its public Sandbox becomes `expired`; replaying the same key and body returns the same `429` reason rather than resuming provisioning. Starting an existing owned slot requires an empty JSON object. It accepts `Idempotency-Key` optionally and does not accept a workspace, image, profile, library, or runtime selector: ```json {} ``` Stopping a VM takes the same empty object. Repeating it on an already stopped (`cold`) VM returns `200` with `stopped: false`; a VM still starting, stopping or in use returns `409 SANDBOX_UNAVAILABLE`. See [Stop a VM](/sandboxes#stop-a-vm). Execute a command with structured arguments: ```json { "argv": ["python3", "-c", "print('hello')"], "cwd": "/workspace", "env": { "MODE": "development" }, "timeoutSeconds": 120, "maxOutputBytes": 262144 } ``` The operation-start route accepts the same body and requires `Idempotency-Key`. Operation wait accepts an optional `timeoutSeconds` from 0 through 30, defaulting to 30. See [Execute commands](/execute) for synchronous and observable execution examples, validation, output paging, and cancellation semantics. ### Metadata and explicit wake-up control Raw HTTP create also accepts immutable `metadata` and `autoResume`. Metadata is an object of at most 16 string pairs. Keys match `^[A-Za-z0-9][A-Za-z0-9._/-]{0,62}$`; values contain at most 256 characters without control characters. Tags are visible to keys that can read the Sandbox: do not store secrets in them. ```bash curl --fail-with-body -sS 'https://api.boxcompute.ai/api/v2/sandboxes' \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: tagged-worker-v1' \ --data '{"workspaceId":"ws_REDACTED","metadata":{"app.role":"worker"},"autoResume":false}' curl --fail-with-body -sS --get 'https://api.boxcompute.ai/api/v2/sandboxes' \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ --data-urlencode 'metadata=app.role=worker' ``` Repeat `metadata=key=value` to require every pair to match exactly and case-sensitively. The value is split at the first `=`. Filters follow the same limits, with at most 16 distinct keys; invalid filters return `400 INVALID_REQUEST`. A Sandbox without the requested tag does not match. `autoResume` defaults to true. With false, execute, operation start and every file call on a stopped VM return `409 SANDBOX_STOPPED` without waking it or starting compute billing. Explicitly [start the same Sandbox](/sandboxes#start-an-existing-slot) first. This does not prevent initial provisioning or cancel a call already racing a stop. Status, logs and operation polling never wake the VM regardless of this setting. Changing nonempty metadata or `autoResume: false` under an existing creation key conflicts. Metadata key order does not matter; omitted and empty metadata are equivalent, as are omitted and true `autoResume`. Current SDKs and the CLI do not expose these create options. ## Control a VM desktop Use an owned, running, network-enabled Large, XL or XXL VM. The desktop is a 1280 × 800 session with Chrome, initially stopped. These routes never wake or replace a stopped VM, even when `autoResume` is true. Start the VM explicitly first. Reading desktop status requires `sandbox:read`; screenshots, input, start/stop and viewer links require `sandbox:execute`. The current SDKs and CLI do not expose desktop controls. ### Start and inspect ```bash curl --fail-with-body -sS \ "https://api.boxcompute.ai/api/v2/sandboxes/$SANDBOX_ID/desktop/start" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/json' --data '{}' ``` Start is idempotent and refuses a VM with an open browser preview. `GET …/desktop` returns the same status wrapper without starting anything: ```json { "desktop": { "sandboxId": "sbx_REDACTED", "phase": "running", "reason": null, "viewerConnected": false, "screen": { "width": 1280, "height": 800 } } } ``` `phase` is `running`, `stopped`, or `unavailable`; `reason` explains an unavailable desktop. Stopping with `POST …/desktop/stop` and `{}` closes desktop applications and viewers; unsaved GUI work is lost, but files and the VM remain. Desktop stop needs no credit. ### Capture and send one action ```bash curl --fail-with-body -sS \ "https://api.boxcompute.ai/api/v2/sandboxes/$SANDBOX_ID/desktop/screenshot?format=png" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" -o desktop.png curl --fail-with-body -sS \ "https://api.boxcompute.ai/api/v2/sandboxes/$SANDBOX_ID/desktop/actions" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/json' \ --data '{"action":{"type":"click","x":320,"y":240},"screenshot":"png","settleMs":300}' ``` Screenshot format is `png` (default) or `jpeg`, with at most 8 MiB of image bytes. Input coordinates are screenshot pixels: x 0–1279, y 0–799. Send one action per request: | `type` | Input | | --- | --- | | `click` | Optional x and y together; otherwise current pointer. `button`: left (default), middle or right; `clicks`: 1 (default) or 2 | | `move` | Required x and y | | `type` | Nonempty `text`, at most 4,096 bytes of printable US-ASCII, newline or tab; other text is rejected before input | | `key` | `key`: Return, Tab, Escape, BackSpace, Delete, Insert, Home, End, Page_Up, Page_Down, Left, Right, Up, Down, space, F1–F12 or one printable non-space ASCII character; optional `modifiers`: ctrl, alt, shift, super | | `scroll` | `direction`: up, down, left or right; `amount`: 1–15 wheel steps (default 3); optional x and y together | The action response contains `pointer: { x, y }`. If requested, `screenshot` contains `mimeType`, `width`, `height`, and base64 `data`, or is null if input ran but capture failed. `settleMs` is 0–2,000 (default 300) when requesting a screenshot. **Never blindly retry input.** `502 DESKTOP_INPUT_UNCERTAIN` or a lost response means the action may have run. Take a screenshot before deciding what to do next. A `409 DESKTOP_BUSY` or `DESKTOP_UNAVAILABLE` refusal means no input was sent; unavailable may require starting the VM or desktop. Ordinary screenshot failures do not undo delivered input. ### Share and revoke a viewer `POST …/desktop/viewer` with `{}` returns `{ "viewer": { "url": "…", "expiresAt": "…" } }`. Open the returned URL or embed it in an iframe. **Anyone holding it can watch and control the desktop; it is not read-only.** Keep it out of logs and third-party pages. Repeating the request with the same key and VM reuses a live link and sets expiry to one hour after the request, up to 24 hours after the link was created; beyond that, request a new link. A deployment may end it early. Revoking its API key ends the link. One viewer can connect at a time; a link waits rather than displacing the app or another viewer. `DELETE …/desktop/viewer` returns 204 and ends the VM's viewer links and connections. ## VM browser previews Browser previews publish one HTTP/WebSocket service running on guest loopback in an owned, running, network-enabled VM. Start the service in the VM, then create the preview with its port: ```bash curl --fail-with-body -sS -X POST \ "https://api.boxcompute.ai/api/v2/sandboxes/$SANDBOX_ID/previews" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/json' \ --data '{"port":3000}' ``` HTTP 201 returns a bearer URL and its fixed expiry: ```json { "preview": { "previewId": "0123456789abcdef0123456789abcdef", "sandboxId": "sbx_REDACTED", "port": 3000, "url": "https://REDACTED_PREVIEW_HOST", "expiresAt": "2026-09-24T02:00:00.000Z" } } ``` Treat `url` as a secret: anyone who has it can reach the service until the preview closes. For browser or network allowlists, BoxCompute-hosted preview URLs use random subdomains of `bxcpreview.site`. Public creation starts a one-hour HTTP/WebSocket preview, not permanent hosting, UDP, SSH, or general inbound access. An application restart preserves the URL and its original deadline and reopens it only while the VM is running. Requests can be interrupted during reconnection; WebSocket clients must reconnect. Explicit close, a VM stop, or a lost guest tunnel can end the link early. The public v2 routes do not expose keep-open or always-on controls. One preview tunnel can be active for a VM. Repeating create for the same port returns that active preview; requesting another port while it is active returns `409`. ### Inspect and recover a preview Listing requires `sandbox:execute`, not `sandbox:read`, because it returns bearer URLs: ```bash curl --fail-with-body -sS \ "https://api.boxcompute.ai/api/v2/sandboxes/$SANDBOX_ID/previews" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" ``` HTTP 200 returns `{ "previews": [...] }`, with at most one entry, including a preview being closed. Each entry adds `state` and `alwaysOn` to the preview fields. `active` serves traffic; `closing` and `cleanup_failed` still occupy the VM's preview slot. `waiting` and `reconnecting` describe app-created always-on links awaiting a tunnel; `expiresAt` can be null while waiting. After an API deployment, an open preview can be absent for up to 30 seconds while it reopens. Stopping a VM ends a plain preview. After starting the VM and its server again, create a new preview rather than reusing the old URL. An app-created always-on preview instead keeps its address and reopens when the VM runs again; it does not override an intentional stop. ### App-only always-on previews and services In the BoxCompute app, Starter includes always-on previews for Small VMs, and BoxCompute can enable them for other accounts. Use **Keep always on** in the **Previews** menu, or ask the agent to pin an open preview on a running Small VM. Without access, the same button offers Starter. Starter's [monthly compute cap](/costs#starter-monthly-compute-cap) limits a Small VM's compute to $5 at current rates; this excludes the subscription and model usage, and still requires available credit. If plan-based access ends, its pins stop being maintained at the next hourly replacement and the links expire. Pinning and unpinning follow the owner's approval settings; pinning quotes the ongoing compute cost. A pin keeps the link from expiring and prevents idle sleep, but does not bypass credit stops or guarantee uninterrupted uptime. If the platform puts the VM to sleep for maintenance, the app can wake it with the usual credit checks and reopen the same link after detecting the interruption. An owner stop, scheduled stop or credit stop is not overridden. The app can restart the latest recorded server command that names the preview port when it ends; cancelled processes and services started another way are not restarted. This is not a general-purpose supervisor. For an app that should come back after VM restarts, ask the in-app agent to use `start_service` instead of a temporary process. It installs a guest service that restarts after a crash and on each VM boot, without a process lifetime limit. Using the same service name replaces it; `stop_service` removes the service but keeps application files. A service does not keep the VM awake or bypass credit stops. These service tools and always-on controls are app-only, not public v2, SDK, CLI, or hosted MCP tools. ### Close a preview Close the exact preview when it is no longer needed: ```bash curl --fail-with-body -sS -X DELETE \ "https://api.boxcompute.ai/api/v2/sandboxes/$SANDBOX_ID/previews/$PREVIEW_ID" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" ``` HTTP 204 means the URL is disabled and cleanup completed. A `503` from close means the URL is already disabled but transport cleanup remains pending. Closing is cooperative rather than host-confirmed retirement; delete the VM when you need the whole workload gone. The SDKs (0.4.0) expose create and close as `previews.create` and `previews.close`, and the CLI as [`bxc sandbox preview`](/cli/commands/); neither lists previews. All file paths must be `/workspace` or descendants without `.` or `..` segments. File reads and writes are binary-safe; send writes as `application/octet-stream`. Ranged reads accept `offset` from 0 through JavaScript's maximum safe integer and `maxBytes` from 1 byte through 8 MiB; defaults are 0 and 8 MiB. Continue with the returned next offset and opaque cursor until EOF. Directory pages accept `pageSize` from 1 through 1,000, defaulting to 1,000, and return an opaque `nextCursor`. Pass cursors unchanged; a file or directory membership change invalidates continuation with `409 CURSOR_STALE`. Writes and complete files edited conditionally are limited to 8 MiB. Directory creation accepts `{ "recursive": false }`, defaulting to `false`. Removal accepts boolean `recursive` and `force` query parameters, both defaulting to `false`. Conditional text edit takes `expectedSha256`, `oldText`, `newText`, and `replaceAll` (default `false`). Rename takes `destination` and `overwrite` (default `false`). See [Manage files](/files) for runnable examples, conflict behavior, and transport-failure retry guidance. ## Operator-selected cooperative connections The cooperative routes are a default-off preview for operator-selected existing Sandboxes, not a generally available SSH or PTY service. Activation requires an owned active selected runtime and an exact JSON object containing `client_key`, `ssh_key`, and `recipient_key`. These are cryptographic public keys with the precise formats in OpenAPI; extra fields are rejected. Activation returns: ```json { "endpoint_id": "12345678-1234-4123-8123-123456789abc", "expires_at": 1788681630, "sealed": "BASE64_ENCRYPTED_CONNECTION_ENVELOPE" } ``` The nonrenewable grant lasts at most 30 seconds. Reconnecting with the same endpoint and exact keys returns the unchanged envelope; it does not mint another grant. Activation and reconnect are not safe to retry after an indeterminate transport failure. Revoke is best effort and always returns `202 { "endpoint_id": "…", "cleanup": "unconfirmed" }`; it is not proof of physical runtime stop. These routes never provision a Sandbox. The SDKs do not expose them; the CLI's experimental [`bxc sandbox ssh`](/cli/commands/) uses them and is off unless `BOXCOMPUTE_ENABLE_SSH=1` is set. ## Selected TCP service access The service routes are generally enabled for authenticated owners of existing running VM Sandboxes. They open only the requested TCP ports to one Tailcat client for a fixed one-hour grant. They do not provision or restart a Sandbox, create a public listener, support UDP, or provide general ingress. The SDKs do not expose them; the CLI's [`bxc sandbox expose`](/cli/commands/) forwards the ports to local loopback. Create a fresh UUIDv4 for the required `Idempotency-Key`. `requested_at` is the current Unix time in seconds. It cannot be in the future or 3,600 seconds old, and the 1–8 ports must be unique and strictly ascending: ```bash export SERVICE_ACCESS_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')" export REQUESTED_AT="$(date +%s)" curl -X POST "https://api.boxcompute.ai/api/v2/sandboxes/$SANDBOX_ID/services" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $SERVICE_ACCESS_ID" \ --data-binary @- < Connect supported agent frameworks to BoxCompute Sandboxes. Source: https://boxcompute.ai/docs/integrations/ · Markdown: https://boxcompute.ai/docs/integrations/index.md The canonical [`boxcompute/integrations`](https://github.com/boxcompute/integrations) examples connect popular agent frameworks with the official `boxcompute` Python SDK or `@boxcompute/sdk` TypeScript package. Each example keeps the BoxCompute API key and lifecycle-owned Sandbox ID outside the model-visible tool schema. Microsoft 365 Copilot is the exception: its declarative plugin consumes the public OpenAPI contract directly. To run Amp threads on BoxCompute computers with nothing to install, connect BoxCompute as Amp's orb provider: see [Run Amp threads on BoxCompute](/integrations/amp/). For local coding agents, the [BoxCompute CLI agent skill](/cli/agents/) is the shorter path. It supports Codex CLI, Claude Code, Amp, OpenCode, Cursor, Gemini CLI, GitHub Copilot, Cline, Roo Code, goose, Pi, Windsurf, and other Agent Skills-compatible harnesses without requiring you to write an integration. Choose a native sandbox or executor adapter when the framework has a suitable contract. Choose a structured command tool when its native sandbox interface is broader, unstable, or would require claiming capabilities the public BoxCompute API does not provide. The reviewed integrations targeted the non-VM Sandbox path and did not activate VM browser previews, operator-selected cooperative SSH, or selected-TCP service access. The public API can create a one-hour bearer HTTP/WebSocket preview for one port on a running network-enabled VM. The SDKs (0.4.0) expose it as `previews.create` and `previews.close`; these adapters do not use it. ## Current status The integration guides are being re-evaluated against the current public API and VM Sandboxes (Beta); they are temporarily unpublished and will be restored here as each is verified. See the [canonical `boxcompute/integrations` repository](https://github.com/boxcompute/integrations) for the source examples meanwhile. ## Hermes in the app [Hermes Agent with VM Sandboxes](/integrations/hermes/) documents the app's `/hermes` setup flow: reuse or create a VM, install the reviewed release, connect BoxCompute AI or your own provider, and check a real inference and file read before reporting ready. This app workflow is separate from the temporarily unpublished framework adapters above; it does not add Hermes-specific public API, SDK or CLI controls. # Run Amp threads on BoxCompute > Use BoxCompute computers as Amp's self-hosted orbs, with nothing to install. Source: https://boxcompute.ai/docs/integrations/amp/ · Markdown: https://boxcompute.ai/docs/integrations/amp/index.md [Amp](https://ampcode.com) runs each thread on an *orb*, a machine of its own. Connect BoxCompute as your orb provider and each Amp thread gets its own BoxCompute computer, a Linux VM billed to your BoxCompute credit. BoxCompute runs the provider for you, so nothing is installed or kept running on your machine. ## Connect Amp 1. In Amp, open **Settings → Orbs → Add Orb Provider**, name it (for example `BoxCompute`) and copy the `aop_live_…` token. Amp shows it only once. 2. In [BoxCompute](https://app.boxcompute.ai), open **Menu → Settings → Amp**, paste the token and choose **Connect**. It shows **Connected** within a few seconds; if Amp refuses the token, Settings says so and you can paste a new one. 3. In Amp, start a new thread and choose **Orb → Self-Hosted →** the provider you named. ## What happens to the computer - **A new thread** creates a Large computer (1.5 vCPU, 3 GiB of memory) named after the thread, in your first personal workspace, never an organization's. It appears under **Computers**. Starting a stopped one takes about 40 seconds. - **An idle thread** is paused by Amp. Its computer stops and keeps its files; the next message starts it again. - **A thread you delete** in Amp deletes its computer. - **At your account's computer limit**, BoxCompute deletes the computer of your least recently used *paused* Amp thread to make room. Amp sets that thread up again the next time it runs. Your other computers are never touched. ## Billing The computer's running time is charged to your BoxCompute credit at the Large rate, like any other computer. Amp's own usage is billed by Amp. If your credit runs out, the computer stops; add credit and send the thread a message to start it again. ## Disconnect In **Settings → Amp**, choose **Disconnect**. New Amp threads can no longer use BoxCompute. Computers that existing threads created stay until you delete them. ## Security - Your provider token is stored encrypted and is used only to run Amp's provider for your account. Settings shows only its last four characters. - The short-lived token Amp issues for each thread is written to its computer as a file and kept where only root can read it, never in a command line. - Amp runs inside the computer as `user`, with sudo, and with the computer's internet access. Treat it like any computer you give an agent. # Hermes Agent with VM Sandboxes > Set up Hermes Agent in the app with BoxCompute AI or your own provider, then manage its VM and credentials. Source: https://boxcompute.ai/docs/integrations/hermes/ · Markdown: https://boxcompute.ai/docs/integrations/hermes/index.md Use `/hermes` in a [BoxCompute app](https://app.boxcompute.ai) chat to install and configure Hermes Agent in a [VM Sandbox (Beta)](/vm-sandbox-beta). The default setup uses BoxCompute AI on your account's AI limits: no separate provider sign-in or API key to paste. If Hermes already has a configured provider, setup keeps it unless you explicitly ask to switch. This is an app-managed setup flow, not a Hermes adapter in the public v2 API, SDK or CLI. For other local coding agents, see the [CLI agent skill](/cli/agents/). To install OpenClaw, Claude Code or Codex in a computer instead, see [VM agent workflows](/vm-sandbox-beta#install-claude-code-codex-or-openclaw). Use a VM-backed chat with a model that supports tools. Setup manages its own installation layout; it does not automatically migrate a custom Hermes install. ## Set up Hermes 1. Open a chat in the workspace where you want Hermes and send `/hermes`. To use a particular VM, name it in your request. 2. Review any approval to create or start a VM. Without a named VM, setup reuses the workspace's Hermes VM or creates a **Large** VM named `hermes` (1.5 vCPU, 3 GiB RAM). The installer build can exhaust a Small VM's memory. 3. Let setup finish installing, connecting the provider and checking readiness. It can rejoin an installer already running or resume an interrupted setup; you do not need to create another VM to retry. 4. Use Hermes only after setup reports **ready**. Setup requires a real model inference that reads a temporary file before publishing the launcher. The reviewed installer currently pins Hermes **v0.22.5**, rather than following the latest upstream release automatically. It skips browser and computer-use installation. A successful CLI readiness check does not qualify those tools, a Hermes dashboard, messaging gateway or scheduled jobs. If your account is at its running-VM limit (two at once on a free account), approval to start Hermes can include stopping another VM while keeping its files. If you hit a VM or storage limit, choose which resources to keep; setup does not arbitrarily delete another VM. See [limits and billing](/limits-and-errors/). ## Run Hermes in the VM After setup reports ready, choose **Open Hermes** in the chat reply. It opens the computer in a new tab, selects **Terminal** and runs the launcher once. Reloading or going Back does not run it again. If the VM is stopped, the link does not wake it: choose **Start** first, and Hermes opens once the VM is ready. To launch it yourself in the computer's Terminal, run: ```bash /workspace/.boxcompute/runtimes/hermes-agent/run-hermes ``` The launcher supplies the isolated `HOME` and `HERMES_HOME` used during setup. Use it instead of starting a separate Hermes configuration under another home directory. Configuration and state live under `/workspace/.boxcompute/runtimes/hermes-agent/` and persist when the VM stops. ## Choose a model provider ### BoxCompute AI (default) `/hermes` connects a new setup to BoxCompute AI without a separate provider account. Ask explicitly to switch to BoxCompute AI if you want to replace a provider Hermes already uses. Hermes shares your account's AI allowance: the Free plan's 24-hour allowance or Starter's applicable Claude/Everyday AI allowance. Five-hour sessions reset fully at their end; weekly limits still roll. See [app AI allowances](/limits-and-errors#app-ai-allowances). VM compute is billed separately; using BoxCompute AI does not make VM runtime unlimited or free. If setup cannot pass its inference check, do not treat an installed executable as ready. A used-up AI allowance is one possible reason; inspect the reported failure before retrying. The VM receives its own AI-only key. In the computer's **Settings → BoxCompute AI**, you can see its last four characters, last use and unused-key expiry, or choose **Turn off key**. Turning it off stops that VM's BoxCompute AI requests. Run `/hermes` again to give it a new key. The key turns off after 30 days without use, checked hourly. Deleting the VM also deletes its key. Setup does not show the full key in chat; do not paste keys or credential files into a conversation or support report. ### Nous Portal Send `/hermes portal` to request Nous Portal instead. Setup supplies a browser sign-in link and code; open the link, sign in and approve that code before it expires. Keep passwords out of chat. Setup then checks the configured provider before reporting ready. The Nous free-tier bootstrap in Hermes v0.22 is desktop-only. Do not assume that installing Hermes in a VM gives you an anonymous Nous free tier. ### Another provider's key Ask to use your own provider. Open the VM's Terminal and run the setup command the app supplies, then select the provider and enter its key there, not in chat. Ask setup to check Hermes again after configuring it; authentication alone is not the readiness check. ## Keep or remove the VM - **Stop** keeps the VM's disk, Hermes configuration and sessions, but ends running processes. Start it again before using the launcher. - **Delete** destroys the VM's files and Hermes state and removes its BoxCompute AI key. Export anything you need first. Stop database writers before copying a SQLite database; a live WAL database is not a consistent backup. - The normal VM lifecycle applies: the default idle stop is 30 minutes and maintenance can stop a VM after about 42 hours. Hermes does not get the Amp-specific idle exemption. See [VM lifecycle](/vm-sandbox-beta/). CLI setup does not make Hermes an always-on service. App-only [previews and managed services](/http-api#app-only-always-on-previews-and-services) are separate capabilities, not a verified Hermes dashboard or gateway recipe. The public v2 API's temporary browser previews are not permanent hosting. # Limits, errors, and unsupported features > Public API input limits and HTTP failure behavior. Source: https://boxcompute.ai/docs/limits-and-errors/ · Markdown: https://boxcompute.ai/docs/limits-and-errors/index.md These limits are enforced by the public v2 API: | Input | Limit or default | | --- | --- | | JSON request body | 128 KiB maximum | | Workspaces per account | 10 | | Sandbox capacity | Personal VMs use account-specific limits across personal workspaces; organization VMs use their organization's limits instead. Stopped VMs still count toward total-VM and declared-storage limits | | Workspace name | 1–80 characters and must contain a non-whitespace character | | Sandbox name | 1–80 characters when supplied | | Sandbox size | VM-only string `small`, `large`, `xl` or `xxl`; defaults to `small`. XL and XXL are included with Starter or available through per-size early access; accounts without access receive `403 FEATURE_ACCESS_DENIED`. See [size eligibility](/vm-sandbox-beta#choose-a-vm-size) | | Sandbox metadata | Immutable, at most 16 string pairs; keys 1–63 characters matching `^[A-Za-z0-9][A-Za-z0-9._/-]{0,62}$`, values at most 256 characters without control characters. List filters use the same limits with unique keys and exact AND matching | | Automatic VM wake-up | `autoResume` defaults to true; false makes execute, operation start and file calls on a stopped VM return `409 SANDBOX_STOPPED`. Explicit start is still supported | | VM root disk | `diskGiB`: whole number from 10–30, defaults to 30; includes the OS and `/workspace`. Customer accounts can use only 30 for now; a smaller value returns `400 INVALID_REQUEST`. Immutable; counts in full against the applicable personal or organization storage limit while stopped too | | `Idempotency-Key` | 1–255 visible ASCII characters for ordinary mutations; required to create a workspace, explicitly request `vmSandbox: true`, or start an operation; optional when the VM selector is omitted or when starting an existing Sandbox; successful responses replay for 24 hours. Email has a [separate replay contract](/http-api#email-your-account-early-access). Selected TCP service create/lookup instead require the same lowercase UUIDv4 operation key | | VM Sandbox beta | New creation is VM-only: omitted `vmSandbox` (or `true`) creates a VM; `false` returns `400 INVALID_REQUEST`. See the [four compute sizes](/vm-sandbox-beta#choose-a-vm-size). All default to a 30 GiB thin-provisioned root disk and have no libraries or shared volumes. `blockNetwork` defaults to false (outbound public IPv4 and DNS); `true` creates no network interface | | Outbound mail | TCP port 25 is blocked for all Sandboxes; connection attempts time out. Network-enabled Sandboxes can use authenticated submission on 465/587 or HTTPS mail APIs | | Owner email (early access) | Starter plus a separate email grant; sends only to the account's own address. Shared app/API/MCP limits: 10 per rolling minute (failed attempts included), 100 per 24 hours and 1,000 per 30 days; `429 RATE_LIMITED` includes `Retry-After` | | Owner email input | Non-empty subject up to 200 characters, line breaks normalized to spaces; non-empty plain-text body up to 100,000 characters within the 128 KiB JSON limit. No recipient selection or attachments | | Idle VM stop | By default, 30 minutes after the last API call that used the VM ([what counts](/sandboxes#what-keeps-a-vm-awake)); app running windows and Starter Always on can suppress idle sleep. Stop it sooner with `POST /api/v2/sandboxes/:id/stop` | | Continuous VM uptime | Maintenance sleep is requested after about 42 hours, even with activity; idle sleep can happen sooner. Disk and Sandbox are retained, but processes and connections stop. Starter Always on can restart compute, not ordinary processes | | Stopped VM retention | Free accounts only: once the owner has been away 14 days, an email warns before stopped VMs unused for 21 days are deleted. See [who it applies to](/vm-sandbox-beta#sleep-is-not-deletion) | | `argv` | 1–64 non-empty strings | | Each `argv` entry | 8,192 characters maximum | | `cwd` | `/workspace` or a path below it; defaults to `/workspace` | | Environment | 64 variables maximum; string values and valid variable names | | Execution timeout | 1–900 whole seconds; defaults to 120 | | Synchronous or retained operation output | `maxOutputBytes` is 1–1,048,576 bytes per stream; defaults to 262,144 | | Operation wait | 0–30 whole seconds; defaults to 30 | | Operation output page | 1–262,144 decoded bytes; defaults to 65,536 | | Terminal operation retention | 24 hours, including up to `maxOutputBytes` from each stream | | File path | `/workspace` or a path below it, without `.` or `..` segments; 4,096 characters maximum | | Log query entries | 1–5,000; defaults to 1,000 | | Deleted Sandbox history | Up to 100 newest records returned; records retained about 15 months; no compute, storage or restore controls | | Deleted Sandbox logs | Same 1–5,000-entry filters as current logs; available only within provider log retention, nominally about 30 days | | File range read | 8 MiB maximum per response; `offset` defaults to 0 and `maxBytes` defaults to 8 MiB | | File write or conditional edit | 8 MiB maximum complete file | | Conditional file edit | Complete file must be UTF-8; `oldText` is 1–65,536 characters and `newText` is 0–65,536 characters | | Directory listing page | 1–1,000 immediate entries; `pageSize` defaults to 1,000 | | File continuation cursor | Opaque, path-bound, and 4,096 characters maximum | | VM browser preview | One active HTTP/WebSocket tunnel per owned running network-enabled VM; one selected port from 1–65,535; fixed lifetime of at most one hour | | VM browser preview URL | Random bearer capability; application restarts preserve the URL and original deadline, reopening only while the VM is running. Reconnect interrupted requests/WebSockets. A VM stop ends a plain link; create a new preview after restart. Public v2 has no keep-open or always-on controls; no UDP, SSH or general ingress | | VM desktop | Running, network-enabled Large, XL or XXL; 1280 × 800 screen; never wakes the VM. Screenshot PNG/JPEG up to 8 MiB; one action per request; ASCII typing up to 4,096 bytes; screenshot settling 0–2,000 ms | | Desktop viewer | Bearer URL grants viewing and input, not read-only access; one viewer at a time. Repeated request sets expiry to one hour from the request, at most 24 hours after link creation; deployment or key revocation can end it early | | Cooperative connection grant | Operator-selected preview only; one nonrenewable grant lasting at most 30 seconds | | Cooperative connection keys | Exact `client_key`, `ssh_key`, and `recipient_key` formats from OpenAPI; no extra fields | | Cooperative sealed envelope | Base64, 80–16,000 characters | | Selected TCP service grant | Generally enabled for an authenticated owner of an active VM; exactly one hour from `requested_at`, without renewal | | Selected TCP service ports | 1–8 unique TCP ports from 1–65,535 in strictly ascending order; no UDP or public listener | | Selected TCP service request | Current Unix-second `requested_at`, one nonzero Tailcat `client_key`, one canonical base64 32-byte `recipient_key`, and no extra fields | | Selected TCP sealed envelope | Base64, 80–16,000 characters; create is not mutation-retryable, and exact lookup recovers only the original live envelope | | Usage window | `days` is bounded to 1–90; defaults to 30 | | Wallet transaction page | 1–200 newest-first entries; `limit` defaults to 50; pass `nextCursor` as `before` | | Wallet transaction filters | Inclusive `from` and exclusive `to` RFC 3339 timestamps; `kind` is 1–64 characters, `bucket` is `cash`, `promo`, or `plan`; `before` is an opaque cursor of at most 512 characters | | Sandbox cost-report window | Inclusive `from` and exclusive `to` RFC 3339 timestamps; defaults to the last 30 days and can span at most 400 days | | Sandbox cost detail | Up to 1,000 settled runs; `truncated` reports omitted additional runs | | Sandbox analytics resolution | 60–86,400 whole seconds; defaults to 300 | | Sandbox analytics rate limit | 30 requests per minute per account | | Sandbox analytics retention | 30 days | | Audit event page | 1–200 newest-first events; defaults to 50; opaque cursor pagination | | Audit event retention | `GET` and `HEAD` requests for 31 days; writes and key lifecycle events for about 15 months | | Audit CSV export | Up to 10,000 matching rows from the web activity view | ## Free up account capacity These are the current hosted allowances across an account's personal workspaces. Organization VMs use [separate capacity](#organization-vm-capacity), not these allowances: | Account | Running VMs | Total VMs | Reserved VM disk | | --- | --- | --- | --- | | Free | Up to 2 | Up to 8, but storage holds four | 120 GiB: four VMs at 30 GiB each | | Holding purchased credit, or an organization member's personal VMs | Up to 3 | Up to 8, but storage holds four | 120 GiB: four VMs at 30 GiB each | | Starter subscriber | All VMs the account holds, up to 8; no separate running cap | Up to 8 | 240 GiB: eight VMs at 30 GiB each | [Starter](https://boxcompute.ai/pricing/) costs $15.99 a month and adds $40 of credit each month. The first 50 members keep that price and credit while they stay subscribed. Allow about a minute for a subscription change to reach VM admission. Higher allowances do not remove credit checks or maintenance stops. Starter separately includes a [monthly compute cap](/costs#starter-monthly-compute-cap) and optional [Always on mode](/vm-sandbox-beta#keep-a-starter-computer-on); neither is unlimited capacity. Account-specific allowances can differ. Any account can buy compute credit from **Usage & billing**, starting at $5. Purchased credit does not expire. Credit alone does not unlock XL/XXL, Starter's models, a higher AI allowance or more simultaneous chats. Use Starter or an eligible per-size early-access grant for [XL/XXL access](/vm-sandbox-beta#choose-a-vm-size). On desktop, the app's **Computers** page shows total VMs and reserved storage against the account's limits. Accounts with a separate running cap also have a **Running** bar; otherwise the running count appears beside **VMs**. Each VM reserves its full declared disk size even when stopped: a 30 GiB VM containing 2 GiB of files still counts as 30 GiB toward the storage limit. At the VM or storage limit, the app's **New computer** is disabled. If a new computer's first start is refused, the app deletes it; it held nothing yet. A computer that never started shows **Not started** with the reason, and Start and Delete still work. API creation reports a full account as `429 WORKSPACE_QUOTA_EXCEEDED` instead. If the running-VM limit is full, use **Stop** on a running VM you no longer need, or ask the agent to stop the VM you choose. Stopping ends its processes and compute billing but keeps its disk and files; the agent follows your approval settings. MCP clients can use `sandbox_stop`, and HTTP clients [the stop route](/sandboxes#stop-a-vm). If storage or total-VM capacity is full, stopping does not free room. Download needed files before deleting a VM you no longer need; deletion erases its disk. A failed new allocation does not mean the VMs you already have cannot be used. See [Sandbox lifecycle](/sandboxes/). ## Organization VM capacity Organization VM access must be enabled with a separately configured limit: running VMs, total VMs and declared disk across the organization's workspaces and creators. These VMs do not use their creators' personal VM allowance, but their creators still pay compute charges from their own credit. Personal VMs are not charged to the organization quota just because their owners are members. `429 WORKSPACE_QUOTA_EXCEEDED` can mean an organization limit was reached; read the message to distinguish it from personal capacity. Stop an unneeded VM for a running limit, delete one for total-VM or disk capacity, or contact support@boxcompute.ai to raise the limit; only BoxCompute sets it. Stopping does not release declared disk. Buying personal credit or switching the app's organization view does not raise this quota. See [organization ownership and admin controls](/sandboxes#personal-and-organization-vms). ## App AI allowances AI limits are separate from VM capacity and compute credit. In the app, the meter beside the model and **Usage & billing** show your usage and reset times. The **Chat length** ring measures conversation length, not your remaining AI allowance. A longer conversation can use more AI per message. On BoxCompute AI, a chat summarises its earlier messages at about 150,000 tokens (90,000 on Claude Haiku), where the ring is full, and then continues. Chats on your own key or endpoint use the model's whole context window. - **Free:** Claude Haiku 5.5 and DeepSeek V4.1 Flash are included. Requests count toward an allowance over a rolling 24 hours, not a calendar day. Claude Sonnet 5.5 and Opus 5.5 need Starter or your own Anthropic key. - **Starter:** **Claude** and **Everyday AI** have separate allowances. Claude Sonnet and Opus count toward Claude; Haiku, DeepSeek V4.1 Flash and other everyday requests count toward Everyday AI. Opus also has its own limit. At the Claude limit, **Continue on DeepSeek V4.1 Flash** can continue the same chat if Everyday AI still has room. - **Five-hour sessions:** each session starts with its first request and resets fully five hours later, at the time the meter shows. It is not a rolling five-hour window. Weekly limits still roll over seven days; a session reset does not clear weekly use. - **Continue when it resets:** if a limit stops the agent mid-job, it can [carry on in the same chat after the reset](#continue-a-chat-after-its-ai-limit-resets). - **Your own model key or endpoint:** the provider bills that usage, not BoxCompute AI. VM compute still costs credit. See [app agent workflows](/vm-sandbox-beta#app-agent-workflows). Scheduled prompts use the same AI and simultaneous-chat limits as interactive chats. An occurrence can be skipped when no run slot is available; a schedule does not reserve capacity or bypass credit checks. ### Continue a chat after its AI limit resets If an AI allowance stops a job, choose **Continue when it resets** in the app. The chat shows its planned continuation time and can carry on in the same chat with its selected model even when the browser is closed. You can have up to five chats waiting at once. Each choice authorizes one run, not repeated use of future allowances: if that run hits another limit, choose again to continue. This can spend the next AI allowance and wake a billed VM. Normal model access, compute credit, capacity and chat approval settings still apply; **Ask first** can pause the continued run for your approval. Later usage can push the start time back, so the displayed time is not a capacity reservation. Choose **Cancel** before it starts to end the wait. Sending a message that is accepted in the chat also cancels the wait; a message refused by the limit does not. Once the run starts, use the normal Stop control. An email reports its outcome unless you stop it yourself. This app workflow adds no public v2, SDK, CLI or hosted MCP continuation control. ## Handle errors Errors include a stable machine-readable `code` and a human-readable `error` string: ```json { "code": "NOT_FOUND", "error": "Sandbox not found" } ``` Existing integrations may continue reading `error`; branch on `code`. Clients must tolerate new codes added for compatible v2 routes. | Status | Meaning | | --- | --- | | `400` | `INVALID_REQUEST` for invalid input or new gVisor creation (`vmSandbox: false`), `INVALID_PATH` for an unsafe file path, `INVALID_RANGE` for an invalid file offset, `INVALID_CURSOR` for invalid continuation, `BINARY_FILE` for a non-UTF-8 edit, or `INVALID_IDEMPOTENCY_KEY` for a missing or malformed required key | | `401` | `AUTHENTICATION_REQUIRED` when no bearer key is sent, or `INVALID_API_KEY` for a malformed, unknown, or revoked key | | `402` | `INSUFFICIENT_CREDIT` because BoxCompute credit is exhausted | | `403` | `INSUFFICIENT_SCOPE` because the key lacks the required scope, `ACCOUNT_SUSPENDED` because its account is suspended, or `BILLING_ACCOUNT_FROZEN` because billing is frozen. For suspension, contact support@boxcompute.ai; retries or additional scopes do not restore access | | `404` | `NOT_FOUND` for an active or deleted Sandbox, owned workspace, or preview that was not found, or `FILE_NOT_FOUND` for a missing file | | `408` | `EXECUTION_TIMEOUT` because execution timed out at the Sandbox service | | `409` | `IDEMPOTENCY_CONFLICT` for conflicting key reuse, `WORKSPACE_QUOTA_EXCEEDED` at 10 workspaces, `DESTINATION_EXISTS` for a rename collision, `EDIT_CONFLICT` when edit text is missing or ambiguous, `CURSOR_STALE` when a continued file or directory changed, or `SANDBOX_UNAVAILABLE` when another VM preview is requested while one is open | | `412` | `PRECONDITION_FAILED` because a file changed after the caller read it | | `413` | `PAYLOAD_TOO_LARGE` because a JSON body or bounded file transfer exceeds its route limit | | `415` | `UNSUPPORTED_MEDIA_TYPE` because a JSON mutation or binary file upload has the wrong media type | | `429` | `RATE_LIMITED` because the account's Sandbox analytics or owner-email limit was exceeded, or `WORKSPACE_QUOTA_EXCEEDED` because running-VM, Sandbox-count or retained workspace-storage capacity is full | | `501` | `UNSUPPORTED_FEATURE` because the Sandbox runtime does not support the operation, or owner email is not configured on this deployment | | `502` | `SERVICE_UNAVAILABLE` because the Sandbox service is currently unavailable or an owner email could not be sent; retry email with its original idempotency key | | `503` | `SANDBOX_UNAVAILABLE` because the Sandbox runtime, retained-log store, or preview transport is currently unavailable; preview close can also return 503 after disabling the URL when transport cleanup remains pending | Additional desktop and lifecycle errors: `409 SANDBOX_STOPPED` requires explicit VM start when auto-resume is disabled; `409 DESKTOP_UNAVAILABLE` requires checking VM/desktop readiness; `409 DESKTOP_BUSY` means no input was sent. `502 DESKTOP_INPUT_UNCERTAIN` means input may have run: inspect a screenshot rather than blindly repeating it. `403 FEATURE_ACCESS_DENIED` means the requested XL or XXL size is not enabled for the account, or owner email needs Starter or its separate early-access grant. See [desktop controls](/http-api#control-a-vm-desktop) and [owner email](/http-api#email-your-account-early-access). `SANDBOX_UNAVAILABLE` can also describe organization access or dedicated capacity. Read both the status and message: `403` means the organization's VMs are not enabled or the creator is no longer a member; contact support@boxcompute.ai to enable them, or your organization administrator about membership, rather than polling. `429` means dedicated capacity is full; delete an unneeded VM or try later. `503` means capacity is unavailable; try later. Switching the app's organization view does not move existing VMs or transfer account credit, but it sets the default owner in the app's **New computer → For**; see [personal and organization VMs](/sandboxes#personal-and-organization-vms). The API has no capacity selector: a VM's owner follows its `workspaceId`. A command that exits non-zero still returns a successful HTTP response. Check `result.exitCode` and `result.timedOut`. Also check `stdoutTruncated` and `stderrTruncated` before assuming output is complete. The official SDKs raise `BoxComputeError` for API responses and expose its status, stable code, message, request ID when available, and retryable flag. Network failures raise `BoxComputeTransportError`. A transport error does not prove whether a mutation reached the service; use idempotency keys for supported create and operation-start retries. The public v2 routes do not expose customer APIs for workspace deletion, SDK-specific lifecycle handles, streaming command output, streaming file I/O, a generally available interactive shell or PTY, shared volumes, checkpoints, or forks. VM browser previews expose one HTTP/WebSocket port through a bearer URL for at most one hour; they are not UDP, SSH, permanent hosting, or general ingress. SDK 0.4.0 controls them with `previews.create` and `previews.close`, and CLI 0.12.0 with `bxc sandbox preview`. A separate default-off cooperative connection preview exists only for operator-selected Sandboxes and is not general SSH availability; CLI 0.12.0 opens it with `bxc sandbox ssh` when `BOXCOMPUTE_ENABLE_SSH=1` is set. Generally enabled selected-TCP access opens 1–8 requested ports on an owned active VM to one client for one hour; it is not a public listener. CLI 0.12.0 opens it with `bxc sandbox expose`; the SDKs do not. Deleted-Sandbox history, logs and costs are in SDK 0.4.0 (`deletedSandboxes`, or `deleted_sandboxes` in Python) and CLI 0.12.0 (`bxc deleted`). Observable operations provide polling, retained output, and explicit cancellation for one structured command; they are not a general-purpose process or PTY API. # Read Sandbox logs > Query current or retained workload and command output without starting compute. Source: https://boxcompute.ai/docs/logs/ · Markdown: https://boxcompute.ai/docs/logs/index.md Read logs for an owned Sandbox with the `sandbox:read` scope: ### TypeScript ```ts const logs = await boxcompute.sandboxes.logs("sbx_REDACTED", { since: "2026-09-06T00:00:00Z", stream: "stderr", source: "process", limit: 100, }); ``` ### Python ```python logs = boxcompute.sandboxes.logs( "sbx_REDACTED", since="2026-09-06T00:00:00Z", stream="stderr", source="process", limit=100, ) ``` The equivalent direct HTTP request is: ```bash curl --get 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/logs' \ -H 'Authorization: Bearer bc_live_REDACTED' \ --data-urlencode 'since=2026-09-06T00:00:00Z' \ --data-urlencode 'stream=stderr' \ --data-urlencode 'source=process' \ --data-urlencode 'limit=100' ``` Log reads do not start stopped compute. Output remains queryable after compute becomes idle and stops, for as long as the provider retains it. Deleting the Sandbox removes the active instance, but retained logs can remain available through its read-only deletion record. ## Filters | Query | Behavior | | --- | --- | | `since` | Inclusive RFC 3339 timestamp; defaults to Sandbox creation | | `until` | Exclusive RFC 3339 timestamp; defaults to now and must be later than `since` | | `stream` | `stdout` or `stderr` | | `source` | `workload`, `execute`, or `process` | | `limit` | 1–5,000 entries; defaults to 1,000 | Results are ordered by timestamp. `workload` is Sandbox workload output, `execute` is synchronous command output, and `process` includes observable-operation output. Interactive shell transcripts are not included. The response contains the customer-facing Sandbox ID, entries, a `truncated` flag, and `retention_seconds`: ```json { "logs": { "sandboxId": "sbx_REDACTED", "entries": [ { "timestamp": "2026-09-06T01:02:03.000Z", "stream": "stderr", "source": "process", "message": "database ready" } ], "truncated": false, "retention_seconds": 2592000 } } ``` If `truncated` is `true`, the result is incomplete. `retention_seconds` reports the configured retention window—nominally 30 days today—but storage pressure can shorten availability, so retained logs are not an archive. ## After deletion First use `GET /api/v2/deleted-sandboxes` to find the deletion record. Read logs with that record's `id` (`dsb_…`), not its original `sandboxId`: ```bash curl --get 'https://api.boxcompute.ai/api/v2/deleted-sandboxes/dsb_REDACTED/logs' \ -H 'Authorization: Bearer bc_live_REDACTED' \ --data-urlencode 'source=process' \ --data-urlencode 'limit=100' ``` This route accepts the same `since`, `until`, `stream`, `source`, and `limit` filters. It defaults to the newest recorded runtime. To inspect an earlier one, pass an unchanged opaque value from the record's `runtimes` array as `runtime`: ```bash curl --get 'https://api.boxcompute.ai/api/v2/deleted-sandboxes/dsb_REDACTED/logs' \ -H 'Authorization: Bearer bc_live_REDACTED' \ --data-urlencode 'runtime=REDACTED_OPAQUE_RUNTIME' ``` The read does not restore, start or bill compute. Deletion history lasts about 15 months, but its logs remain readable only while the log provider retains them—nominally about 30 days and potentially less under storage pressure. Download anything you need before deletion; retained logs are not a replacement for files or an archive. The SDKs (0.4.0) expose this route as `deletedSandboxes.logs(id, { runtime })` (`deleted_sandboxes.logs(id, runtime=...)` in Python), and the CLI as `bxc deleted logs DELETED_ID --runtime RUNTIME` with the filters below. ## Host CLI The host-side BoxCompute CLI exposes the same query: ```bash bxc sandbox logs SANDBOX_ID --source execute bxc --json sandbox logs SANDBOX_ID \ --since 2026-09-06T00:00:00Z --limit 100 ``` CLI filters are: - `--since` and `--until` for timestamps; - `--stream` for `stdout` or `stderr`; - `--source` for the output source; and - `--limit` for the maximum entry count. Without `--json`, the CLI prints entries to standard output and a count, retention value, and truncation indicator to standard error. # MCP server > Connect Claude, Claude Code, Codex, Cursor and other MCP clients to BoxCompute VMs. Source: https://boxcompute.ai/docs/mcp/ · Markdown: https://boxcompute.ai/docs/mcp/index.md BoxCompute's remote MCP server lets an AI app create Linux VMs in your account, run commands in them, read and edit their files, and open preview URLs for servers they run. ```text https://mcp.boxcompute.ai/mcp ``` You need a BoxCompute account with credit. New customers can [sign up](https://app.boxcompute.ai/signup); eligible accounts receive $5 in free compute credit, valid for 14 days. [Sign-up review and eligibility](/api-keys#sign-up-review) apply. ## Connect an app Apps that support MCP sign-in need only the URL. The first time the app connects, it opens a BoxCompute page in your browser that names the app and what it will be able to do. Choose **Allow access** to connect it, or **Cancel** to refuse. ### Claude 1. In Claude, open **Settings → Connectors**. 2. Choose **Add custom connector**, name it BoxCompute, and enter `https://mcp.boxcompute.ai/mcp`. 3. Choose **Connect** and sign in to BoxCompute when asked. The connector is then available in Claude on the web, Claude Desktop and the Claude mobile apps. ### Claude Code ```sh claude mcp add --transport http boxcompute https://mcp.boxcompute.ai/mcp ``` Then run `/mcp` in Claude Code, select **boxcompute** and choose **Authenticate**. ### Codex ```sh codex mcp add boxcompute --url https://mcp.boxcompute.ai/mcp codex mcp login boxcompute ``` `codex mcp login` opens the BoxCompute sign-in page. If Codex later stops connecting, for example after you revoke its key, run `codex mcp logout boxcompute` and then `login` again. ### Other clients Add a remote (Streamable HTTP) server with the URL `https://mcp.boxcompute.ai/mcp`. A client that supports MCP authorization signs you in through the browser. For one that does not, use an [API key](#use-an-api-key-instead). ### What an app can do The sign-in page lists what you grant: | Permission | Tools | | --- | --- | | See your VMs and their files | `account_get`, `workspace_list`, `sandbox_list`, `sandbox_get`, `file_read`, `file_list` | | Create, start and stop VMs | `sandbox_create`, `sandbox_start`, `sandbox_stop` | | Run commands and change files in your VMs | `command_run`, `process_start`, `process_get`, `process_output`, `process_cancel`, `file_write`, `file_edit`, `preview_create`, `preview_list`, `preview_close` | | Delete VMs and their disks | `sandbox_delete` | | See your credit balance | the balance in `account_get` | | Email your own account address | `email_send`; requires Starter and email early access | Allowing access creates an API key named after the app, such as **MCP · Claude**, on your [API keys](https://app.boxcompute.ai/api-keys) page. The app acts as that key: its requests appear in your [API activity](/audit-log/) and the VMs it creates are attributed to it. Revoke the key to disconnect the app. Connecting again asks you to allow it again. > **VM time is billed** > > VMs an app creates or starts are billed to your credit like any other VM. Reading a VM's state > never starts it, but commands and file tools start a stopped VM so they can run. A VM stops 30 > minutes after the last command, process start, file or preview call and keeps its `/workspace` > files; `sandbox_stop` stops it sooner. ## Use an API key instead Every client can authenticate with a [BoxCompute API key](/api-keys/) in the `Authorization` header. The key's scopes decide which tools the client is offered. The same server also answers at `https://api.boxcompute.ai/mcp`, for API keys only: sign-in through the browser works only at `mcp.boxcompute.ai`. ```sh claude mcp add --transport http boxcompute https://mcp.boxcompute.ai/mcp \ --header "Authorization: Bearer $BOXCOMPUTE_API_KEY" ``` For Cursor (`~/.cursor/mcp.json`) and similar clients: ```json { "mcpServers": { "boxcompute": { "url": "https://mcp.boxcompute.ai/mcp", "headers": { "Authorization": "Bearer ${env:BOXCOMPUTE_API_KEY}" } } } } ``` VS Code uses the same entry in `.vscode/mcp.json`, under `servers` instead of `mcpServers`. Codex reads the key from an environment variable named in `~/.codex/config.toml`: ```toml [mcp_servers.boxcompute] url = "https://mcp.boxcompute.ai/mcp" bearer_token_env_var = "BOXCOMPUTE_API_KEY" ``` Keep the key in an environment variable or secret store, not in a file you commit. ## Tools | Tool | What it does | | --- | --- | | `account_get` | The account and key in use, and the credit balance when allowed | | `workspace_list` | Workspaces in the account, with their organization | | `sandbox_list`, `sandbox_get` | VMs with their state, compute size, root-disk size when known, network access and organization | | `sandbox_create` | Create and start a VM: small (0.5 CPU, 1 GiB), large (1.5 CPU, 3 GiB), xl (4 CPU, 8 GiB) or xxl (4 CPU guaranteed, bursts to 8; 16 GiB), with or without internet; default 30 GiB root disk holding the OS and `/workspace` | | `sandbox_start`, `sandbox_stop` | Start a stopped VM, or stop one now and keep its files | | `sandbox_delete` | Permanently delete a VM and its `/workspace` | | `command_run` | Run a shell command and wait for it: 30 seconds by default, up to 60 with `timeout_seconds` | | `process_start`, `process_get`, `process_output`, `process_cancel` | Run a command in the background for up to 15 minutes, check on it and read its output; `process_get` waits up to 30 seconds for it to finish with `wait_seconds` (default 0) | | `file_read`, `file_list` | Read a file or list a directory under `/workspace` | | `file_write`, `file_edit` | Write a file (up to 2 MiB) or replace text in one | | `preview_create` | A public HTTPS URL, valid for one hour, for a port a server in the VM listens on | | `preview_list`, `preview_close` | List a VM's open preview URLs, or close one at once; an always-on URL stops being reopened | | `email_send` | Send `subject` and plain-text `text` to the account owner's own address; `email:send` scope, Starter and email early access required | `sandbox_create` accepts optional `disk_gib`, a whole number from 10 to 30, defaulting to 30. Only BoxCompute platform admins can choose below 30; any other account gets `INVALID_REQUEST` and no VM, so customer VMs have 30 GiB. The disk is fixed for the VM's lifetime and counts in full against account storage even when stopped. `sandbox_list` and `sandbox_get` return `disk_gib: null` for older VMs whose disk size was not recorded. Do not infer those disks' sizes from today's default. `sandbox_create` also accepts optional `workspace_id`; without it, the VM goes in the account's first personal workspace. A VM created in an organization workspace counts against the organization's VM limit instead of the account's, and its creator still pays for it. `workspace_list`, `sandbox_list` and `sandbox_get` return `organization` as the organization's name, or `null` when personal. See [personal and organization VMs](/sandboxes#personal-and-organization-vms). XL and XXL are included with Starter or available through per-size early access; accounts without access receive `FEATURE_ACCESS_DENIED`. The same [size eligibility](/vm-sandbox-beta#choose-a-vm-size) applies to HTTP and MCP creation. Their compute rates are 8× and 16× Small respectively; Large is 3×. See [VM sizes](/vm-sandbox-beta#choose-a-vm-size). MCP does not expose create metadata, `autoResume`, or desktop controls; use the [HTTP API](/http-api#metadata-and-explicit-wake-up-control) for those features. Each tool is marked read-only or not, and destructive or not, so your app can ask before it stops, deletes or changes anything. These annotations describe the requested operation, not a guarantee that compute stays stopped: `file_read` and `file_list` can start a stopped VM and incur compute charges, even though they do not change its files. `autoResume: false`, set through HTTP, does not apply here: command and file tools start such a VM too, and its running time is billed. `sandbox_list` and `sandbox_get` never start a VM. ## Limits - Every call answers within about 75 seconds. A VM that is still booting returns `ready: false` from `sandbox_create` or `sandbox_start`, and the boot continues; commands and file tools on it say it is still starting, and can be called again a few seconds later. - Commands longer than 60 seconds belong in `process_start`. - Files are confined to `/workspace`. `file_read` defaults to 64 KiB and allows up to 256 KiB per part; pass `next_offset` and the returned cursor to continue. Binary data is base64. `file_list` defaults to 200 entries and allows up to 500 per page; pass its cursor to continue. - `command_run` output is capped at 48 KiB per stream. `process_start` retains up to 1 MiB per stream; `process_output` reads up to 48 KiB at a time. Pass `next_offset` back to continue; `earliest_offset` identifies output already discarded. - A preview URL is public: anyone with it can reach that port until it expires. - Account limits on running and total VMs apply as they do in the app and the [HTTP API](/limits-and-errors/). Errors carry the same codes as the HTTP API, such as `INSUFFICIENT_CREDIT` or `NOT_FOUND`, with a message the app can act on. `email_send` shares the [owner-email limits](/http-api#email-your-account-early-access) with the app and API: 10 per rolling minute, 100 per 24 hours and 1,000 per 30 days. It cannot choose a recipient or send attachments. Each call is a new send, so do not blindly retry an uncertain result. Older connected-app keys may lack `email:send`; reconnect and allow access again if that tool is missing. The scope alone does not grant Starter or early access. ## Add an MCP server to your BoxCompute agents This is the other direction: BoxCompute acts as a client of a server you trust, instead of an outside app using BoxCompute's VM tools. Every account can use **Menu → MCP servers → Add server** in the app; no early-access grant is required. Servers belong to the selected workspace, with at most 20 per workspace. 1. Enter the server's public HTTPS URL on port 443 and choose **Add server**. Localhost, private-network addresses, redirects and local stdio commands are not supported for customer servers. 2. If it needs authentication, choose **Sign in** and complete the provider's browser flow. If the app asks for a client ID, register a client with the provider using the redirect URL shown in the app, then enter that client ID and any required client secret. 3. For a server that takes an API key instead, use **Advanced → Headers** in the server form. Enter credentials there, not in chat or a committed file. Connected tools become available to agents in that workspace. Actions not known to be read-only ask for approval in **Ask first**; **Full access** skips those prompts. Only connect servers you trust with the data and actions their tools can access. Sign-in renews automatically when supported; if it expires or is revoked, return to this page and sign in again. Changing the URL or importing a server into another workspace requires a new sign-in. Removing a server removes its tools from BoxCompute but does not revoke access at the provider; revoke it there too when needed. These settings have no public v2 API, SDK or CLI controls. ## Privacy and support The app receives the output of the tools it calls, such as command output and file contents, and handles it under its own terms. BoxCompute records each request in your API activity without the tool's arguments. See the [privacy policy](https://boxcompute.ai/privacy/). For help, email [support@boxcompute.ai](mailto:support@boxcompute.ai). Do not include API keys or passwords in your message. # SDK quickstart > Sign up for BoxCompute, install an official SDK, run a command in a Sandbox, and clean up. Source: https://boxcompute.ai/docs/quickstart/ · Markdown: https://boxcompute.ai/docs/quickstart/index.md Use the official SDK for application integrations. It handles authentication, typed API responses, binary file transfer, timeouts, cancellation, and structured errors for the public v2 API. If you want to use BoxCompute from a terminal or a local coding agent, follow the [CLI quickstart](/cli/quickstart/). For direct HTTP clients and generated tooling, see the [HTTP API reference](/http-api). > **New to BoxCompute?** > > [Sign up](https://app.boxcompute.ai/signup). Eligible new customers receive $5 in free compute credit, valid for 14 days. No card required. ## 1. Sign up and create an API key Some requests wait for [sign-up review](/api-keys#sign-up-review) before a link is sent. [Sign up](https://app.boxcompute.ai/signup), confirm your email, and sign in to [the BoxCompute app](https://app.boxcompute.ai). Then open [API keys](https://app.boxcompute.ai/api-keys), create a key, and include `workspace:create`, `sandbox:read`, `sandbox:create`, `sandbox:execute`, and `sandbox:delete`. On your first app visit, answer **Have you used AI before?** with **Not really**, **ChatGPT or Claude**, or **I code with AI**. The answer tailors the first tasks and the agent's explanations; the app creates your initial workspace without asking you to name it. The coding path can connect GitHub and select a repository. To work with your own tools instead, choose **Just want the computer?** to open **Computers**; the app remembers that as your home. Either route can use this SDK walkthrough. See [app agent workflows](/vm-sandbox-beta#app-agent-workflows). Copy the `bc_live_...` secret immediately—it is displayed only once—and store it in your secret manager. For this local walkthrough: ```bash read -rsp 'BoxCompute API key: ' BOXCOMPUTE_API_KEY && echo export BOXCOMPUTE_API_KEY ``` See [API keys](/api-keys) for scope and revocation details. Never put this server-side key in a browser or mobile application. ## 2. Install an SDK ### TypeScript ```bash npm install @boxcompute/sdk npm install --save-dev tsx ``` ### Python ```bash python -m pip install boxcompute ``` Both packages use `https://api.boxcompute.ai` by default. You do not need to build route URLs or authorization headers yourself. New Sandboxes are VM-only: omit `vmSandbox` or send `true`. Sending `false` returns `400 INVALID_REQUEST`; there is no fallback to gVisor. Operator-selected cooperative SSH and the selected-TCP access routes are not exposed by the current SDKs. One-hour VM browser previews are `previews.create` and `previews.close` from SDK 0.4.0; see the [preview contract](/http-api#vm-browser-previews). ## 3. Create, execute, and clean up The examples use a workspace named `SDK quickstart`, creating it on the first run. A workspace created through the API is personal, so the VM counts against your own limits rather than an organization's. Workspaces are persistent parents and are not deleted. Each example creates one Sandbox and deletes that exact instance in a `finally` block. ### TypeScript Create `quickstart.ts`: ```ts import { randomUUID } from "node:crypto"; import { BoxCompute } from "@boxcompute/sdk"; const apiKey = process.env.BOXCOMPUTE_API_KEY; if (!apiKey) throw new Error("BOXCOMPUTE_API_KEY is required"); const boxcompute = new BoxCompute({ apiKey }); const workspaces = await boxcompute.workspaces.list(); const existingWorkspace = workspaces.find((item) => item.name === "SDK quickstart"); const workspace = existingWorkspace ?? await boxcompute.workspaces.create({ name: "SDK quickstart", idempotencyKey: "sdk-quickstart-workspace-v1", }); const sandbox = await boxcompute.sandboxes.create({ workspaceId: workspace.id, name: "SDK quickstart", idempotencyKey: randomUUID(), }); try { const result = await boxcompute.sandboxes.execute(sandbox.id, { argv: ["python3", "-c", "print(sum(range(100000)))"], cwd: "/workspace", timeoutSeconds: 120, maxOutputBytes: 262_144, }); console.log(result.stdout); if (result.stderr) console.error(result.stderr); process.exitCode = result.exitCode; } finally { await boxcompute.sandboxes.delete(sandbox.id); } ``` Run it with `npx tsx quickstart.ts`. ### Python Create `quickstart.py`: ```python import os import sys import uuid from boxcompute import BoxCompute with BoxCompute(api_key=os.environ["BOXCOMPUTE_API_KEY"]) as boxcompute: workspace = next( (item for item in boxcompute.workspaces.list() if item.name == "SDK quickstart"), None, ) or boxcompute.workspaces.create( name="SDK quickstart", idempotency_key="sdk-quickstart-workspace-v1", ) sandbox = boxcompute.sandboxes.create( workspace_id=workspace.id, name="SDK quickstart", idempotency_key=str(uuid.uuid4()), ) try: result = boxcompute.sandboxes.execute( sandbox.id, argv=["python3", "-c", "print(sum(range(100000)))"], cwd="/workspace", ) print(result.stdout, end="") if result.stderr: print(result.stderr, end="", file=sys.stderr) finally: boxcompute.sandboxes.delete(sandbox.id) ``` Run it with `python quickstart.py`. `execute` waits for completion and returns stdout, stderr, exit status, timeout, and truncation fields. A non-zero command exit is still a successful API response, so inspect `exitCode` / `exit_code` rather than treating it as a transport error. For commands that must remain observable across an interrupted connection, use [durable operations](/execute#run-an-observable-operation). ## Next steps - [Manage workspaces and Sandboxes](/sandboxes). - Attribute [Sandbox compute costs](/costs) by creating API key, Sandbox, and charged run. - Review [execution inputs and durable operations](/execute). - Read, write, edit, rename, list, and remove [Sandbox files](/files). - Publish one VM HTTP/WebSocket port temporarily with a [browser preview](/http-api#vm-browser-previews). - Connect a supported agent framework with an [integration guide](/integrations/). - Use the [HTTP API reference](/http-api) for direct HTTP clients and OpenAPI tooling. # Manage Sandboxes > Create and list workspaces, then create, inspect, and delete Sandbox instances. Source: https://boxcompute.ai/docs/sandboxes/ · Markdown: https://boxcompute.ai/docs/sandboxes/index.md A BoxCompute workspace is the parent for Sandbox instances. A workspace can own multiple Sandboxes, each with a stable customer-facing `id`. All list and lookup operations are limited to the account that owns the API key. ## Personal and organization VMs A workspace is personal or belongs to an organization, chosen once when it is created. In the app, open **Menu → workspace picker → New workspace → For** and choose an organization whose VM access is enabled. Public workspace creation currently accepts only a name and creates a personal workspace; it does not accept an organization selector. SDK, CLI, MCP and HTTP clients can use the ID of an existing owned organization workspace when creating a VM. The app's **New computer → For** also lets eligible members choose **Personal** or an organization. It uses a matching workspace, creating one if needed, without switching the workspace currently open in the console. Check **For** before creating: it initially follows the current workspace's owner, or the organization selected in **Menu** when the workspace is personal. Organization VMs count against the organization's running-VM, total-VM and declared-disk limits instead of their creator's personal capacity. They do not become shared computers: only the creator uses them and the creator's compute credit pays. Organization admins can view their VMs in **Overview**, stop or delete them, and see their compute in **Billing**. Personal VMs are not listed there. An assigned dedicated server is used only for organization VMs, not a member's personal VMs. Leaving the organization prevents starting its VMs again. Existing VMs remain listed for its admins to stop or delete; leaving does not free their capacity or transfer their files. Switching the app's organization view does not move an existing workspace or VM. See [organization capacity](/limits-and-errors#organization-vm-capacity). New Sandboxes are VM-only. See [VM Sandboxes (Beta)](/vm-sandbox-beta) for the fixed profile, network choice, integration walkthrough, and current limitations. `vmSandbox: false` returns `400 INVALID_REQUEST` without allocating compute. Legacy gVisor slots cannot be started, resumed, or recreated through the customer API. Contact support@boxcompute.ai about legacy data rather than retrying start or execution; do not assume a historical slot still has a recoverable disk. ## Use an official SDK ### TypeScript ```ts import { BoxCompute } from "@boxcompute/sdk"; const boxcompute = new BoxCompute({ apiKey: process.env.BOXCOMPUTE_API_KEY! }); const workspaces = await boxcompute.workspaces.list(); const workspace = workspaces[0] ?? await boxcompute.workspaces.create({ name: "Agent workspace", idempotencyKey: "workspace-bootstrap-v1", }); const sandbox = await boxcompute.sandboxes.create({ workspaceId: workspace.id, name: "Worker 1", idempotencyKey: crypto.randomUUID(), }); const current = await boxcompute.sandboxes.inspect(sandbox.id); await boxcompute.sandboxes.delete(current.id); ``` ### Python ```python import os import uuid from boxcompute import BoxCompute with BoxCompute(api_key=os.environ["BOXCOMPUTE_API_KEY"]) as boxcompute: workspaces = boxcompute.workspaces.list() workspace = workspaces[0] if workspaces else boxcompute.workspaces.create( name="Agent workspace", idempotency_key="workspace-bootstrap-v1", ) sandbox = boxcompute.sandboxes.create( workspace_id=workspace.id, name="Worker 1", idempotency_key=str(uuid.uuid4()), ) current = boxcompute.sandboxes.inspect(sandbox.id) boxcompute.sandboxes.delete(current.id) ``` Generate one idempotency key per intended Sandbox creation and reuse it only when retrying that same creation. ## Direct HTTP ### List owned workspaces ```bash curl 'https://api.boxcompute.ai/api/v2/workspaces' \ -H 'Authorization: Bearer bc_live_REDACTED' ``` This includes owned workspaces that do not yet have a Sandbox. Use a returned workspace `id` when creating an instance. ### Bootstrap a workspace If the account has no workspace, create one with a key that has `workspace:create` scope: ```bash curl -X POST 'https://api.boxcompute.ai/api/v2/workspaces' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Idempotency-Key: workspace-bootstrap-v1' \ -H 'Content-Type: application/json' \ -d '{"name":"Agent workspace"}' ``` `name` is required, must contain a non-whitespace character, and can contain at most 80 characters. The required idempotency key is account-scoped: retrying the same canonical request within 24 hours returns the original `201` response and workspace ID. Reusing the key for a different public mutation returns `409 IDEMPOTENCY_CONFLICT`. The response is `{ "workspace": ... }`. The new workspace appears in subsequent workspace lists and can own multiple Sandboxes. Accounts can have at most 10 workspaces; exceeding that limit returns `409 WORKSPACE_QUOTA_EXCEEDED`. Workspaces persist independently of their Sandboxes. The public API does not provide workspace deletion, so create one only when the account needs another durable Sandbox parent. ### Create and start an instance ```bash curl -X POST 'https://api.boxcompute.ai/api/v2/sandboxes' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Idempotency-Key: 019f1234-5678-7000-8000-000000000000' \ -H 'Content-Type: application/json' \ -d '{"workspaceId":"ws_REDACTED","name":"Worker 1"}' ``` `workspaceId` is required and must identify a workspace owned by the key's account. `name` is optional; when supplied, it must be a non-empty string of at most 80 characters. Each accepted request creates another independent VM. Creation returns `202` while pending, and replaying the same idempotency key and body returns `201` once it is running. Both success responses contain `{ "sandbox": ... }`. The whole root disk, including the OS and `/workspace`, defaults to 30 GiB. Raw HTTP callers may set `diskGiB` to a whole number from 10 to 30 at creation, but only BoxCompute platform admins can choose below 30; any other account gets `400 INVALID_REQUEST` and no VM, so customer VMs have 30 GiB. It cannot be resized later. The full chosen disk counts toward personal or organization storage, as applicable, even when stopped. Changing it under the same idempotency key returns `409 IDEMPOTENCY_CONFLICT`; omitted and explicit 30 are equivalent. See the [disk contract](/http-api#request-bodies) before choosing a non-default size. Raw HTTP callers can also set immutable `metadata` tags and `autoResume: false` to require an explicit start before execution or file access on a stopped VM. List with repeated `metadata=key=value` filters to find tagged Sandboxes. See [metadata and wake-up control](/http-api#metadata-and-explicit-wake-up-control) for limits, a runnable example and retry equivalence. These options are not exposed by the current SDKs or CLI. Creation can return `429 WORKSPACE_QUOTA_EXCEEDED` when the account reaches its running-VM, Sandbox-count or storage limit. A durably accepted VM becomes `expired` when this limit refuses provisioning, and replay returns the `429` reason. Stop an unneeded running VM for the running limit; freeing total-VM or storage capacity requires deletion. See [capacity recovery](/limits-and-errors#free-up-account-capacity). ### Retry creation safely `Idempotency-Key` is required with explicit `vmSandbox: true` and optional when the selector is omitted. Use one whenever a create might be retried after a timeout or lost response. Generate a unique value for each intended creation and persist it until the response is safely recorded. Repeating the same canonical request with the same key on the same account within 24 hours recovers the same Sandbox ID: `202` while pending, then `201` once running. Concurrent requests with that key share the same creation. Reusing it for a different create request or another public mutation returns `409 IDEMPOTENCY_CONFLICT`. Keys are account-scoped even when the requests use different API keys. Completed historical responses retain their original runtime; pending historical gVisor creates now fail with `400 INVALID_REQUEST` without allocating compute. Omitting the header preserves non-idempotent behavior: every request can create another Sandbox. After 24 hours, a reused key can also create a new mutation, so do not use a longer retry window. ### List and inspect ```bash curl 'https://api.boxcompute.ai/api/v2/sandboxes' \ -H 'Authorization: Bearer bc_live_REDACTED' curl 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED' \ -H 'Authorization: Bearer bc_live_REDACTED' ``` The list response is `{ "sandboxes": [...] }`; inspection returns `{ "sandbox": ... }`. State is `cold`, `pending`, `running`, or `expired`. Ordinary Sandboxes use `cold` and `running`; VM beta provisioning adds `pending`, and `expired` means the VM runtime is no longer available. See [HTTP API reference](/http-api) for the complete shape. A running VM normally stops on its own 30 minutes after the last API call that used it, and the Sandbox becomes `cold`. App running-window schedules and [Starter Always on](/vm-sandbox-beta#keep-a-starter-computer-on) can suppress idle sleep. See [what keeps a VM awake](#what-keeps-a-vm-awake), or [stop it](#stop-a-vm) sooner. Its retained workload and command output can remain available without restarting it; see [Read Sandbox logs](/logs). VMs also request maintenance sleep after about 42 hours of continuous uptime, even with activity. This is not Sandbox expiry or deletion: the disk remains, but processes, shells and open connections stop. Save progress to disk and resume the same Sandbox before restarting applications. See [VM sleep and resume](/vm-sandbox-beta#sleep-is-not-deletion). ### Start an existing slot Direct HTTP clients can start an existing owned Sandbox without allocating another instance: ```bash curl -X POST 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/start' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Idempotency-Key: start-existing-v1' \ -H 'Content-Type: application/json' \ -d '{}' ``` This route requires `sandbox:create`, accepts only an empty JSON object, and returns `201` with `{ "sandbox": ... }`. The idempotency header is optional. It reuses the selected slot and its recorded runtime choice; it does not accept a workspace, image, library, profile, or runtime selector. The CLI exposes this as `bxc sandbox resume SANDBOX_ID`; the SDKs do not expose a resume method. Starting can also return `429 WORKSPACE_QUOTA_EXCEEDED` when account Sandbox or retained-storage capacity is full. It cannot start, resume, or recreate a legacy gVisor runtime. ### Stop a VM Stop a running VM when you are done with it instead of paying until the idle stop: ```bash curl -X POST 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/stop' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/json' \ -d '{}' ``` This route requires `sandbox:create`, accepts only an empty JSON object, and returns `200` with `{ "sandbox": ..., "stopped": true }`. Compute billing ends and the disk, including `/workspace`, is kept; running processes, shells and open connections end. `stopped` is `false` when the VM is already stopped (`cold`). Stopping never starts a VM and needs no credit, so it also works at zero balance. `409 SANDBOX_UNAVAILABLE` means the VM is still starting, stopping or in use; retry shortly. By default, start, execute or any file call starts it again with the same files after a cold start of about 30 seconds. With `autoResume: false`, execution, operation start and file calls instead return `409 SANDBOX_STOPPED`; use explicit start first. Each run is billed for at least 60 seconds; see [Track Sandbox costs](/costs). CLI 0.11.0 and later expose stop as `bxc sandbox stop SANDBOX_ID`; resume with `bxc sandbox resume SANDBOX_ID`. The SDKs do not expose stop yet. ### What keeps a VM awake A running VM stops 30 minutes after the last API call that used it; the check runs once a minute. This is the default without an app running-window schedule or Always on mode. These calls count: - start and execute - every file call, including stat, read and list - starting an operation - creating a preview, and requests to a preview URL (counted at most once a minute) - desktop start/stop, screenshots, actions and active viewer use on an already running VM Reading the Sandbox, desktop status, logs, analytics or costs, polling or waiting on an operation, and service access do not count. Neither does activity inside the VM: a server or other background process does not keep it running. Execute and file calls, including reads, start a stopped VM first unless it was created with `autoResume: false`. Desktop calls never start a stopped VM. `GET /api/v2/sandboxes/:id` never starts a VM. While it runs, its `lastUsedAt` is the last call that counted, so the default idle stop is due 30 minutes later. ### Delete ```bash curl -X DELETE 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED' \ -H 'Authorization: Bearer bc_live_REDACTED' \ -H 'Content-Type: application/json' ``` Deletion returns `204 No Content` and removes that Sandbox instance, its compute and its storage. It does not delete its parent workspace or sibling Sandboxes. BoxCompute keeps a read-only history row for about 15 months; this record does not retain files, consume Sandbox quota or provide restore, start or delete controls. ## Review deleted Sandboxes List up to the 100 most recently deleted Sandboxes for the account: ```bash curl 'https://api.boxcompute.ai/api/v2/deleted-sandboxes' \ -H 'Authorization: Bearer bc_live_REDACTED' ``` ```json { "deletedSandboxes": [ { "id": "dsb_REDACTED", "sandboxId": "sbx_REDACTED", "name": "Worker 1", "workspaceId": "ws_REDACTED", "workspaceName": "Agent workspace", "vmSandbox": true, "size": "small", "runtimes": ["REDACTED_OPAQUE_RUNTIME"], "deletedVia": "api", "deletedByApiKeyId": "key_REDACTED", "createdAt": 1791244800000, "lastUsedAt": 1791244860000, "deletedAt": 1791244920000 } ] } ``` `workspaceName`, `deletedByApiKeyId`, and `lastUsedAt` can be `null`. `deletedVia` is `console`, `api`, `agent`, `workspace`, `retention`, or `organization`. The last means an organization administrator deleted it; `retention` means an idle free-account VM deleted after an email warning. Treat values in `runtimes` as opaque log selectors and pass them back only as described in [Read Sandbox logs](/logs#after-deletion). The SDKs (0.4.0) list this history with `deletedSandboxes.list()` (`deleted_sandboxes.list()` in Python), and the CLI with `bxc deleted list`. The deletion record also preserves the identifier used to inspect that Sandbox's retained [compute-cost detail](/costs#inspect-charged-runs). Cost history does not restore its compute or files. # VM Sandboxes (Beta) > Create and manage VM sandboxes with the public API, including command execution, file transfer, network selection, and cleanup. Source: https://boxcompute.ai/docs/vm-sandbox-beta/ · Markdown: https://boxcompute.ai/docs/vm-sandbox-beta/index.md Create a VM, upload test data, run a command, download the results, and delete the VM using the BoxCompute API. This guide is for developers who need a full virtual machine rather than a shared-kernel Sandbox for command-line tools and agent tool execution. VM Sandboxes are available to all authenticated accounts. This beta is not intended for production workloads. The public API does not provide always-on controls; the app has [Starter Always on](#keep-a-starter-computer-on) and a [Small-VM always-on preview option included with Starter](/http-api#app-only-always-on-previews-and-services). Neither guarantees uninterrupted uptime. The walkthrough uses a small text fixture and requires no additional software inside the VM. Last updated: November 4, 2026. ## Check whether your experiment fits | Capability | Current VM beta profile | | --- | --- | | Availability | Available to all authenticated accounts. | | Selection | New creation is VM-only: omitted or `true` creates a VM; `false` returns `400 INVALID_REQUEST`. An explicit `vmSandbox: true` requires an `Idempotency-Key` header; an omitted `vmSandbox` may create keyless, in which case the server generates the key. Completed historical idempotency responses keep their original runtime; pending historical gVisor creates are rejected. | | Networking | Outbound Internet access by default, limited to public IPv4 and DNS. Set `blockNetwork: true` to create a VM with no network interface. The choice is fixed at creation and cannot be changed later. | | Resources | Four immutable compute sizes, listed below; XL and XXL are included with Starter or available through per-size early access. All default to a 30 GiB thin-provisioned root disk containing the OS and `/workspace`; only written blocks consume physical capacity. `diskGiB` accepts whole numbers from 10–30, but customer accounts can use only 30 for now; a smaller value returns `400 INVALID_REQUEST`. The chosen disk size is immutable and counts in full against the applicable personal or organization storage limit. | | Lifetime | A stopped VM keeps its disk until you delete it; on free accounts, [retention](#sleep-is-not-deletion) can delete one left unused after an email warning. By default, compute stops 30 minutes after the last API call that used it; app running windows and Starter Always on can suppress idle sleep. Maintenance still stops a boot after about 42 hours, even with activity. Resume keeps files, not running processes or connections. | | Image | Immutable, server-selected approved minimal Ubuntu image. No arbitrary image override, library profile, or attached volumes. | | User/runtime | Runs as root (UID 0) with `HOME=/workspace`. No SSH login. The minimal image has no compiler preinstalled, but root can install system packages with `apt-get`. Node.js, uv and other tool dependencies are not guaranteed. | | Serving | A direct HTTP request can create one bearer HTTP/WebSocket preview URL for one loopback port for at most one hour. No UDP, SSH endpoint, permanent hosting, or general inbound access. | | Desktop | Network-enabled Large, XL and XXL VMs support screenshots, input and a control-capable viewer link through the [desktop HTTP API](/http-api#control-a-vm-desktop). Start the VM and desktop explicitly; desktop calls never wake it. | | Storage | `/` and `/workspace` share one root filesystem that is deleted with the VM. Download needed data before you delete it. An account workspace groups sandboxes; it does not preserve their files. | | Lifecycle | Create, status, execute, files, stop, resume an existing stopped VM, delete. CLI `bxc sandbox resume` retains the same disk and files; compute billing resumes. Do not depend on snapshots, checkpoints, forks, or restore. | Start with a small payload and one command at a time. A full toolchain, browser, model or concurrent agent workload may exceed the available resources. ### Choose a VM size | `size` | Guaranteed CPU | Memory | Compute rate relative to Small | | --- | --- | --- | --- | | `small` (default) | 0.5 CPU | 1 GiB | 1× | | `large` | 1.5 CPU | 3 GiB | 3× | | `xl` | 4 CPU | 8 GiB | 8× | | `xxl` | 4 CPU; bursts up to 8 | 16 GiB | 16× | XL and XXL are included with [Starter](https://boxcompute.ai/pricing/). Accounts with provisioned organization access also qualify; otherwise each size requires its own early-access grant. Purchased credit alone does not unlock XL or XXL: it pays for compute, not Starter features. A free account without a grant receives `403 FEATURE_ACCESS_DENIED` before allocation. In the app, these sizes appear with a Premium badge; selecting a locked size opens the Starter offer. Size access does not waive compute charges, credit checks or account capacity limits. Select sizes with raw HTTP or the hosted MCP `sandbox_create` tool; the released SDKs and CLI expose only Small and Large. Size cannot be changed after creation; changing it under the same creation key conflicts. ### Sleep is not deletion A VM can stop compute while retaining its disk. By default, it stops 30 minutes after the last API call that used it ([what counts](/sandboxes#what-keeps-a-vm-awake)), or at once with [the HTTP stop route](/sandboxes#stop-a-vm). Maintenance sleep is requested after about 42 hours of continuous uptime, even if the VM is active. Sleep keeps the disk, but processes, shells and open connections stop. Save progress to disk and make long-running applications restartable. On a free account, stopped VMs can be deleted once you stop using BoxCompute. After 14 days in which you have not used the app, run an agent, called the API or CLI, or used any of your VMs, BoxCompute emails you the VMs' names and a deletion date at least seven days later. If you are still away on that date, each stopped VM unused for 21 days is deleted with its disk, and its deletion history shows `deletedVia: retention`. Coming back at any point cancels the deletion. Your account, chats and Assets stay. This never applies to Starter, accounts that have ever paid (a credit top-up counts), accounts with an early-access grant, organization members, organization VMs, or VMs with a computer schedule or an always-on preview. In the app, **Stop** keeps the computer's files; reopening its page or Terminal does not wake it. Choose **Start** before opening a new terminal. Other API execution or file calls can still wake a VM unless it has `autoResume: false`. If Stop reports that the VM is still stopping, check its status before retrying. For app computer schedules, each selected weekday is the day the running window starts. A window that crosses midnight stops the next morning: 21:00–06:30 Monday–Friday stops on Tuesday–Saturday, not the following Monday. Running windows suppress idle sleep until their stop time; Starter also offers [Always on](#keep-a-starter-computer-on) without a timed window. Resume the same Sandbox with `bxc sandbox resume SANDBOX_ID` or [the HTTP start route](/sandboxes#start-an-existing-slot). Resume requires `sandbox:create` and available credit, retains the same disk and files, and restarts compute billing. Wait for availability before restarting applications; do not create a replacement just because the VM is asleep. ## App agent workflows These workflows use the BoxCompute app, not additional public v2 API or SDK controls. On a first visit, **Have you used AI before?** tailors the first tasks and the agent's explanations. Choose **Not really**, **ChatGPT or Claude**, or **I code with AI**; the coding path can connect GitHub and choose a repository. The app creates the initial workspace and starts preparing its computer. Choose **Just want the computer?** to open Computers and make it your home instead. If an AI limit interrupts a job, you can opt in to [continue that chat after the reset](/limits-and-errors#continue-a-chat-after-its-ai-limit-resets). If you belong to several organizations, choose one from the organization heading in **Menu**. Organization admins can open **Overview** to inspect the organization's VMs and stop or delete them, and **Billing** for their compute spend; ordinary members see Members. **Personal** hides organization pages. The choice is remembered in that browser, but does not move workspaces, credit or VMs. A new workspace, or **New computer → For**, chooses who a new VM belongs to; **For** starts at the organization shown in Menu. See [personal and organization VMs](/sandboxes#personal-and-organization-vms). ### Keep a Starter computer on Open a computer's **Schedule**, select **Always on**, then **Save schedule**. This replaces its timed window, prevents idle sleep and can start a stopped computer. After a platform maintenance sleep, it starts the same computer again automatically, subject to credit and capacity. This is not uninterrupted uptime: each restart ends ordinary programs, shells and connections. Ask the in-app agent to use `start_service` for an application that should return on boot; see [managed services](/http-api#app-only-always-on-previews-and-services). An explicit stop by you or the agent, a credit stop or account suspension is not overridden. Start it again yourself, or save Always on again when ready. If Starter ends, Always on is removed and default idle sleep applies again. Purchased credit or organization membership alone does not unlock this mode. Starter's [100-hour monthly compute cap](/costs#starter-monthly-compute-cap) does not waive credit checks. Always on schedules do not create a public URL; browser previews are separate. These schedule controls are app-only. ### Use an app terminal In a chat or on a computer's Terminal page, choose **Full screen** to expand the connected shell without reconnecting or losing its screen. Use **Exit full screen** to return; Esc remains available to terminal programs such as vim. This fills the browser window, not the browser's own full-screen mode. If a connection drops, the app shows **Reconnecting…** and makes bounded automatic retries while keeping the screen. It does not resend input typed before the drop. If retries are exhausted or the server refuses the connection, use **Reconnect**. A shell that ended must be closed and a new one opened; automatic reconnect does not restart an ended shell or wake a stopped computer. On a touch screen, the terminal key row adds Esc, Tab, Ctrl, Alt, Shift, Home, End, PgUp, PgDn and arrows. Tap Ctrl then c to interrupt; modifiers apply to the next key only, including a letter on the phone keyboard. Hold an arrow to repeat it. In full screen, the phone's terminal menu provides text size, theme, Paste and Close shell; in landscape, shell tabs move into that menu. ### Ask an agent to use the desktop Use a running, network-enabled Large, XL or XXL computer for tasks that need the visible screen. The agent requests permission to control the same desktop you see in **Desktop**; it does not create, resize or wake a computer to get control. It can double-click, hover, drag and use keyboard shortcuts. Fresh desktops open windows as full-screen tabs; an already-running desktop keeps its layout. On a fresh desktop start, the BoxCompute wallpaper and top-bar **Apps**, **Terminal** and **Browser** buttons give you clickable ways to open programs. **Apps** lists installed application entries, including programs you install. Existing computers receive these controls the next time their desktop starts; an already-running desktop is not changed. Each approved session lasts up to 60 minutes, 100 inputs or 120 screenshots, whichever comes first. At a limit, the agent can ask once for another session on the same computer. Approval resets the counters without restarting Chrome or losing its profile. Denial, no answer or cancellation ends desktop control for that run. Ending control leaves the computer running and billing normally; stop the VM separately when you no longer need it. The agent's Chrome supports WebGL using CPU rendering. If a site needs WebGPU, ask the agent to enable it: this closes its managed Chrome windows and reopens Chrome with the same profile. Save unsaved browser work first. WebGPU rendering can use several times more CPU, so enable it only when needed. Closing Chrome does not end desktop input or screenshots; the agent can reopen it. These app session controls are separate from [public desktop API controls](/http-api#control-a-vm-desktop). ### Share a secret through Vault (early access) Accounts with Vault early access can open **Settings → Vault → Add secret** to save a key or password under a name such as `stripe_key`. Values are stored encrypted and are not shown again; use **Replace** to change one. You can keep up to 100 secrets per account. Never paste a secret into chat. In a supported app agent chat on a BoxCompute computer, the agent can request a secret in a masked card. **Save** puts it in your vault; **Don't share** declines. The agent receives the name and save outcome, not the value. It can pass a saved secret to a command as an environment variable for that command only. **Ask first** asks before each such command; **Full access** permits it without another prompt. Vault is not available to local runner chats or always-on managed services, and has no public v2 API, SDK or CLI controls. Tool text that echoes a saved value is masked before it reaches the agent or chat, but this is not protection from a command you do not trust: the command can read its environment. Transformed values, screenshots, the computer's Terminal, retained command/process logs and files can still expose it. Deleting a vault entry does not erase copies already written on a computer. If early access ends, saved values remain masked and can still be deleted, but cannot be added or used by new runs. ### Install Claude Code, Codex or OpenClaw 1. Open a running computer's **Overview → Agents on this computer**. 2. Choose **Install** for Claude Code or Codex CLI. Installation uses the vendor's official installer; Claude Code follows its stable channel. 3. After installation, choose **Open terminal**. Run `claude` for Claude Code or `codex login --device-auth` for Codex, then complete the provider's browser sign-in. ChatGPT must allow device-code sign-in in its security settings for that Codex flow. You sign in with your own Claude or ChatGPT account. Credentials stay in the VM; do not paste them into chat. The provider bills agent usage, while BoxCompute bills VM compute. This differs from the [CLI agent skill](/cli/agents/), which connects an agent on your own computer to remote Sandbox tools. A stopped computer must be started before installing or checking agents; opening this section does not wake it. Installation status is not sign-in status. Claude Code lists 4 GiB RAM as a requirement: use XL or larger to meet it, subject to [size eligibility](#choose-a-vm-size). A Small VM can run short of memory. Stop preserves the installed agent and credentials; delete erases them with the disk. This is not a qualification of every agent workload. OpenClaw is not in **Agents on this computer**: ask the agent in a chat to install it in a computer. It runs OpenClaw's official installer for the latest release under `/workspace/.boxcompute/runtimes/openclaw`, then replies with a setup link. The link opens the computer's Terminal in a new tab and runs OpenClaw's own `openclaw onboard` setup; enter any provider credentials there, not in chat. The agent's links for Claude Code and Codex sign-in work the same way. A link never wakes a stopped computer: choose **Start** first. ### Schedule an agent prompt Every account can use **Menu → Schedules**, or ask the agent to schedule a prompt. Choose the agent, prompt, timezone, and either a one-time local date or recurring weekdays and time. Each occurrence starts a new chat rather than adding to an existing conversation. You can keep up to 20 schedules per account. Review approvals: agent-created schedules use **Ask first**; **Full access** requires your explicit confirmation in the app. A busy previous run or full simultaneous-chat allowance can skip an occurrence rather than queue it. Schedules use normal AI limits, compute credit and VM lifecycle rules, not an always-on VM reservation. See [app AI allowances](/limits-and-errors#app-ai-allowances). ### Ask for an email when work finishes (early access) Starter accounts with separate email early access can ask the app agent to **email me when it's done**, including with the chat closed. It sends a plain-text notification only to your BoxCompute account address, without a further approval prompt; it cannot email someone else or attach files. Replies are not read. Shared app/API/MCP limits are 10 per rolling minute, 100 per 24 hours and 1,000 per 30 days. Starter alone does not enable this feature. Scripts can use the [owner-email HTTP route](/http-api#email-your-account-early-access). To give agents tools from another service, every account can now [add a trusted remote MCP server](/mcp#add-an-mcp-server-to-your-boxcompute-agents) through **Menu → MCP servers**, without early access. This is separate from connecting an external AI app to BoxCompute's own hosted MCP server. ### Connect your own model server Every account can open **Settings → Model keys → Your endpoints → Add endpoint** to add an OpenAI-compatible model. Enter its public HTTPS base address on port 443, add its key if required, and select or enter a model. The save checks require the model to call a tool; a prose-only model is not accepted. You can save up to five endpoint models; model-key and endpoint checks share a limit of ten per hour. BoxCompute connects from the internet, not your local network: `localhost`, private addresses and redirects are not supported. Never expose an unauthenticated model server. The endpoint's provider bills its model usage, not BoxCompute AI; VM compute is still billed separately. ## Before you start You need an active API key and Bash, curl, jq and Python 3 on your development machine. Run the examples in the same shell, in a new directory for your test files. Python is used only to generate unique request keys; it is not required inside the VM for this walkthrough. Use **`https://api.boxcompute.ai/api/v2`** as the API base URL. See the [OpenAPI reference](https://api.boxcompute.ai/api/v2/openapi.json) for request and response schemas. Create an [API key](/api-keys) and send it as `Authorization: Bearer `. Keep the key on your development machine or integration controller, not in the VM, test fixture, source repository, or logs. Disable shell tracing and load the key securely into `BOXCOMPUTE_API_KEY` before running the examples. No model-provider credential is needed for these offline experiments. Required scopes: `sandbox:read` for workspace listing, sandbox status and file reads; `sandbox:create` for creation; `sandbox:execute` for execution and file uploads and browser previews; `sandbox:delete` for cleanup. Workspace bootstrap additionally needs `workspace:create`. Billing/credit checks still apply. ## Run your first integration test ### 1. Select an owned workspace ```bash BASE='https://api.boxcompute.ai/api/v2' : "${BOXCOMPUTE_API_KEY:?Load your API key securely first}" curl --fail-with-body -sS "$BASE/workspaces" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" ``` HTTP 200 returns `{"workspaces":[...]}`. Deliberately select an owned workspace; do not silently use the first result. Set `WORKSPACE_ID` to its returned `id`. The workspace must belong to your account. You do not need a separate tenant ID or tenant header. If you need a new workspace, run the following optional request. HTTP 201 returns `{"workspace":{...}}`; use its `id` in the next step. Skip this request when using an existing workspace. You can create up to 10 workspaces per account. ```bash WORKSPACE_KEY="workspace-$(python3 -c 'import uuid; print(uuid.uuid4())')" curl --fail-with-body -sS -i "$BASE/workspaces" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: $WORKSPACE_KEY" \ --data '{"name":"Integration tests"}' ``` ### 2. Create once; recover with the same key and body Running this step allocates a VM. Keep the request body, key and returned sandbox ID for recovery; do not generate a new key on retry. ```bash WORKSPACE_ID='REPLACE_WITH_RETURNED_WORKSPACE_ID' CREATE_KEY="vm-$(python3 -c 'import uuid; print(uuid.uuid4())')" # Generate ONCE jq -n --arg workspaceId "$WORKSPACE_ID" \ '{workspaceId:$workspaceId,vmSandbox:true,blockNetwork:false,size:"small"}' > vm-create.json create_vm() { curl --fail-with-body -sS --connect-timeout 10 --max-time 30 \ -o vm-create-response.json -w '%{http_code}\n' "$BASE/sandboxes" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: $CREATE_KEY" \ --data-binary @vm-create.json } create_vm && jq . vm-create-response.json ``` The initial response is **202**, with a `sandbox` object normally in `pending`. After a successful response, record `sandbox.id`. On a transport timeout, the outcome is unknown: retry `create_vm` with the unchanged key/body. While work is pending, replays return 202. Once provisioning converges, the same request returns **201 with the same public sandbox ID**. Repeat at a modest interval (for example 10 seconds), with a bounded overall wait; do not loop forever or allocate a replacement on every error. A 202 is durable acceptance, **not** proof of eligibility or readiness. Provisioning failures can leave it pending. If the account has reached its Sandbox-count or workspace-storage limit, the VM becomes `expired` and replaying the same key and body returns `429 WORKSPACE_QUOTA_EXCEEDED` with the reason. Stopped Sandboxes still count; delete one you no longer need before retrying with a new creation key. If it is still pending after five minutes, stop waiting, save the ID and error response for support, and use step 5 to delete it. Five minutes is a suggested client wait limit, not a provisioning-time guarantee. Do not change the key to try to resolve a stuck request. `workspaceId`, optional `name` (1–80 trimmed characters), `vmSandbox`, optional `size`, and `blockNetwork` configure public creation: ```json { "workspaceId": "ws_REDACTED", "name": "Integration tests", "vmSandbox": true, "size": "small", "blockNetwork": false } ``` `size` is VM-only and defaults to `"small"` (0.5 vCPU / 1,024 MiB RAM); `"large"` selects 1.5 vCPU / 3,072 MiB RAM; Starter's `"xl"` and `"xxl"` sizes are listed in the [size table](#choose-a-vm-size). All default to the 30 GiB root disk, and the choice is fixed when the VM is created: replaying the same key with the same body returns the recorded VM, and changing `size` for that key returns `409 IDEMPOTENCY_CONFLICT`. This walkthrough keeps the default 30 GiB disk, the only size customer accounts can choose for now; a smaller `diskGiB` returns `400 INVALID_REQUEST`. The disk includes the OS and `/workspace`, cannot be resized later, and counts toward storage even when stopped. See the [HTTP disk contract](/http-api#request-bodies). `blockNetwork` is optional and defaults to `false`, which gives the VM outbound Internet access limited to public IPv4 and DNS. Set it to `true` for a VM with no network interface. The choice is fixed when the VM is created: replaying the same key with the same body returns the recorded VM, and changing `blockNetwork` for that key returns `409 IDEMPOTENCY_CONFLICT`. A creation key recorded before network selection existed keeps its original blocked intent when replayed. Outbound TCP port 25 is blocked for all Sandboxes; direct SMTP connections time out. In network-enabled VMs, use a mail provider's authenticated submission on 465/587 or HTTPS API instead. This does not give a `blockNetwork: true` VM network access. Do not send `image`, `backend`, `cpu`, `memoryMiB`, `workspaceMiB`, `timeoutSeconds`, `libraries`, or `volumes`. Use `size` to select the VM compute profile; the others are not public controls, and unknown fields are discarded. VM creation requires `Idempotency-Key`: 1–255 visible ASCII characters, no spaces. Keys are account-wide across mutations. Changed normalized input or reuse for a different mutation gives `409 IDEMPOTENCY_CONFLICT`. Completed receipts are retained for 24 hours after completion. A replay is a saved creation receipt, not current status; even a deleted VM's receipt can replay. ### 3. Inspect status, then execute bounded work ```bash SANDBOX_ID='REPLACE_WITH_RETURNED_SANDBOX_ID' curl --fail-with-body -sS "$BASE/sandboxes/$SANDBOX_ID" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" ``` HTTP 200 returns `{"sandbox":{...}}` with `id`, `workspaceId`, `name`, `state`, `vmSandbox`, `createdAt` and nullable `lastUsedAt`. VM responses also include `blockNetwork` for the recorded network intent. Timestamps are Unix milliseconds. States are `cold`, `pending`, `running`, `expired`; require `vmSandbox:true` and `running` before your test. Status can lag changes in VM availability, so handle errors from execute and file requests even after a `running` response. The response does not include an expiry timestamp. Compute can sleep, and free accounts' unused VMs can be deleted, as described above. ```bash curl --fail-with-body -sS "$BASE/sandboxes/$SANDBOX_ID/execute" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/json' \ --data '{"argv":["/bin/echo","beta integration"],"cwd":"/workspace","timeoutSeconds":10,"maxOutputBytes":4096}' ``` HTTP 200 wraps `result` containing `stdout`, `stderr`, `exitCode`, `timedOut`, `stdoutTruncated` and `stderrTruncated`. For this echo, expect `stdout:"beta integration\n"`, empty stderr, exit code 0, and all flags false. Check the result, not only HTTP success: nonzero exit codes also return 200. Use structured `argv`, not a `command` field. Shell syntax requires an explicit shell, and interpolating untrusted inputs into shell text is unsafe. `cwd` defaults to `/workspace` and must stay there or below it. Optional `env` is a string map (up to 64 valid environment variable names). `argv` has 1–64 nonempty entries, each at most 8,192 characters. Execution timeout accepts 1–900 seconds (default 120). Output is bounded to 1–1,048,576 bytes (default 262,144). Start with short timeouts, small output and one task at a time. Execute is not idempotent: retrying after a lost response can run the work again. ### 4. Stage fixtures and retrieve exact bytes Uploads are raw bytes, not JSON, base64 or multipart. The per-upload limit is 8 MiB. File paths must be absolute under `/workspace`, at most 4,096 characters, without `.`/`..` components or NUL. Create parent directories with execute if needed. This tiny fixture stays in the existing workspace directory. ```bash printf '0123456789abcdefghijklmnopqrstuvwxyz\n' > fixture.txt curl --fail-with-body -sS -i -X PUT \ "$BASE/sandboxes/$SANDBOX_ID/files/content?path=%2Fworkspace%2Ffixture.txt" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/octet-stream' \ --data-binary @fixture.txt # Expect 204 with no body. curl --fail-with-body -sS -D full.headers -G \ "$BASE/sandboxes/$SANDBOX_ID/files/content" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ --data-urlencode 'path=/workspace/fixture.txt' -o full.txt cmp fixture.txt full.txt curl --fail-with-body -sS -D range.headers -G \ "$BASE/sandboxes/$SANDBOX_ID/files/content" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ --data-urlencode 'path=/workspace/fixture.txt' \ --data-urlencode 'offset=7' --data-urlencode 'maxBytes=11' -o range.txt printf '789abcdefgh' > expected-range.txt cmp expected-range.txt range.txt ``` Both reads return **200**, including partial reads, not 206. Use query `offset` (default 0) and `maxBytes` (1–8,388,608; default 8,388,608), **not** an HTTP `Range` header. Inspect `X-BoxCompute-Offset`, `X-BoxCompute-Next-Offset`, `X-BoxCompute-File-Size`, and `X-BoxCompute-EOF`. A default read is only a full download if EOF is true. For larger files, repeat using the returned next offset and `X-BoxCompute-Next-Cursor` as the `cursor` query parameter, appending chunks until EOF. A file changed between pages can return `409 CURSOR_STALE`; restart the download rather than mixing versions. Stop the writer before exporting a database; byte-range transport alone does not make a live database backup consistent. ### Optional: publish a browser preview If the VM is network-enabled and an HTTP/WebSocket service is listening on a loopback port, direct HTTP clients can create one bearer preview URL for at most one hour: ```bash curl --fail-with-body -sS -X POST "$BASE/sandboxes/$SANDBOX_ID/previews" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" \ -H 'Content-Type: application/json' \ --data '{"port":3000}' ``` Record the returned `preview.previewId`, `preview.url`, and `preview.expiresAt`. Anyone who has the URL can reach the service, so do not log or publish it. Close it when finished; closing does not stop the guest process or VM: ```bash curl --fail-with-body -sS -X DELETE \ "$BASE/sandboxes/$SANDBOX_ID/previews/$PREVIEW_ID" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" ``` Only one preview tunnel can be active for a VM. This feature is temporary HTTP/WebSocket access, not UDP, SSH, permanent hosting, or general ingress. The app's Starter always-on preview option does not add a public API control. See [VM browser previews in the HTTP API reference](/http-api#vm-browser-previews) for the distinction and cleanup semantics. You can inspect the current link with `GET /api/v2/sandboxes/:id/previews`, using the same `sandbox:execute` scope. A VM stop ends this plain preview; after restarting the VM and server, create a new preview instead of reusing the old URL. ### 5. Download results; delete explicitly Compute can sleep, but the disk remains until you delete the Sandbox or, on a free account, [retention](#sleep-is-not-deletion) deletes it. Download your results as soon as the test finishes. If the VM runtime is no longer available, an execute or file request returns `503 SANDBOX_UNAVAILABLE` and the sandbox state becomes `expired`. Using that sandbox ID again does not automatically create a replacement VM. A 503 alone is not proof that the VM is gone: transport failures also use that code. ```bash curl --fail-with-body -sS -i -X DELETE "$BASE/sandboxes/$SANDBOX_ID" \ -H "Authorization: Bearer $BOXCOMPUTE_API_KEY" # Expect 204 with no body; a subsequent status GET returns 404. ``` Delete the VM when you are finished, or to cancel a pending creation. Deletion removes this sandbox and its associated storage, not the parent workspace or sibling sandboxes. A repeated DELETE after removal returns 404. Treat workspace contents as disposable: files are not exported for you when the VM is deleted. Read-only deletion history remains for about 15 months and retained logs can remain available for about 30 days, but neither includes files or a restore path. ## Troubleshooting Errors have shape `{"code":"...","error":"..."}`. Useful distinctions: 401 invalid/missing key; 403 `INSUFFICIENT_SCOPE`, `ACCOUNT_SUSPENDED` or frozen billing account; 402 `INSUFFICIENT_CREDIT`; 404 `NOT_FOUND` for missing/foreign resources or `FILE_NOT_FOUND` for missing files; 408 `EXECUTION_TIMEOUT`; 413 `PAYLOAD_TOO_LARGE`; 415 `UNSUPPORTED_MEDIA_TYPE`; 502 `SERVICE_UNAVAILABLE`; 429 `WORKSPACE_QUOTA_EXCEEDED`; 503 `SANDBOX_UNAVAILABLE`. - For 401 or `INSUFFICIENT_SCOPE`, check your key's status and scopes. - For `ACCOUNT_SUSPENDED`, contact support@boxcompute.ai; replacing the key or adding scopes will not restore access. - For 402, check your account credit before retrying. - For 409 `IDEMPOTENCY_CONFLICT`, check whether you changed the request body or reused a key for another operation. Recover the original request first. - For 429, delete an unneeded Sandbox to release both its slot and retained workspace storage; waiting or repeatedly replaying the same creation does not release account capacity. - For 503, inspect sandbox status. If it is expired, delete it; do not retry execution expecting a fresh VM. - For repeated 5xx errors or a stuck creation, contact BoxCompute with the sandbox ID, time of the request, HTTP status and error code. Never include your API key or sensitive file contents in a support report. ## Integrations See [Hermes Agent with VM Sandboxes](/integrations/hermes/) for the app-managed `/hermes` setup flow, provider choices, readiness check and VM lifecycle guidance. This does not add Hermes-specific controls to the public v2 API.