Skip to content

VM Sandboxes (Beta)

Create and manage VM sandboxes with the public API, including command execution, file transfer, network selection, and cleanup.

Updated View as Markdown

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 or always-on services.

The walkthrough uses a small text fixture and requires no additional software inside the VM.

Last updated: October 12, 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 size selects small (default, 0.5 vCPU / 1,024 MiB RAM) or large (1.5 vCPU / 3,072 MiB RAM). Both default to a 30 GiB thin-provisioned workspace: the declared size is the hard quota, while only written blocks consume pool capacity.
Lifetime No automatic expiry of the Sandbox or retained disk. Compute can sleep when idle or for maintenance after about 42 hours of continuous uptime, 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.
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, 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.

Sleep is not deletion

A VM can stop compute while retaining its disk. Maintenance sleep is requested after about 42 hours of continuous uptime, even if the VM is active. The Sandbox does not expire, but processes, shells and open connections stop. Save progress to disk and make long-running applications restartable; no automatic lifetime expiry does not mean uninterrupted uptime.

Resume the same Sandbox with bxc sandbox resume SANDBOX_ID or the HTTP start route. 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.

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 for request and response schemas.

Create an API key and send it as Authorization: Bearer <API_KEY>. 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

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.

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.

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:

{
  "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. Both default to the 30 GiB workspace, 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.

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.

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

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. The Sandbox has no automatic expiry, but its compute can sleep as described above.

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.

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:

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:

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. See VM browser previews in the HTTP API reference for response and cleanup semantics.

5. Download results; delete explicitly

The Sandbox and its disk have no automatic expiry. Compute can sleep, but the disk remains until you delete the Sandbox. 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.

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 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/403, check your key’s status and scopes.
  • 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 for an integration assessment, bounded tool experiments, and the current blockers to hosting Hermes.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close