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; use this page for
direct HTTP clients, generators, and API tooling.
The public OpenAPI 3.1 contract 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:
Authorization: Bearer bc_live_REDACTEDJSON 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 for recovery guidance and
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 } |
Request bodies
Create a workspace. name is required, must contain a non-whitespace character, and is at most 80 characters:
{
"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.
Create a Sandbox in an owned workspace. name is optional, non-empty when supplied, and at most 80 characters:
{
"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 and
VM Sandboxes (Beta) 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 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. Disks below 30 GiB
require early access; other accounts receive 400 INVALID_REQUEST. The disk cannot be resized
after creation and counts in full toward account storage 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; use authenticated mail 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
account reaches its running-VM, Sandbox-count or workspace-storage limit. Stopped Sandboxes count
toward Sandbox-count and storage limits, not the running-VM limit. See capacity recovery.
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:
{}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.
Execute a command with structured arguments:
{
"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 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.
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 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
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:
{
"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
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:
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:
{
"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:
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, accounts with always-on previews enabled can use Keep always on in the Previews menu, or ask the agent to pin an open preview on a running Small VM. 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:
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. Preview
create and close are not exposed by the current SDKs or CLI.
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 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:
{
"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 current SDKs and CLI do not expose them.
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 current SDKs and CLI do not expose them.
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:
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 @- <<JSON
{
"requested_at": $REQUESTED_AT,
"client_key": "$TAILCAT_CLIENT_KEY",
"recipient_key": "$RECIPIENT_KEY_BASE64",
"ports": [3000, 8080]
}
JSONThe response is a capability sealed to recipient_key; it contains no plaintext connection state:
{
"generation_id": "12345678-1234-4123-8123-123456789abc",
"expires_at": 1788681900,
"sealed": "BASE64_ENCRYPTED_CONNECTION_ENVELOPE"
}Expiry is exactly requested_at + 3600; lookup does not extend it. The create mutation is not safe
to retry after an indeterminate transport failure. Instead, send the exact same body and
Idempotency-Key to POST /api/v2/sandboxes/:id/services/lookup to recover the original envelope
while it is live. Revoke the returned generation_id with the DELETE route. A successful revoke is
200 with the generation ID and a cleanup value of guardian-confirmed or
untrusted-guest-report. For VMs, a guest report is never independent proof that a process or
machine physically stopped.
Unavailable or uncertain access returns 503 SANDBOX_UNAVAILABLE. Resources owned by another
account are indistinguishable from absent resources and return 404. These routes require an owned,
active, network-enabled VM and the sandbox:execute scope. Platform signing and service-access
configuration must also be available; satisfying the scope alone does not override those checks.
Response objects
Me (from GET /api/v2/me) confirms the account and key a bearer token represents:
{
"account": { "id": "acc_REDACTED", "email": "you@example.com", "name": "Your account" },
"apiKey": {
"id": "key_REDACTED",
"name": "Production",
"scopes": ["sandbox:read", "sandbox:create"],
"createdAt": 1788681600000,
"lastUsedAt": 1788681600000
}
}account.email and account.name can be null; apiKey.lastUsedAt can be null. apiKey.scopes lists
the exact scopes granted to the key.
Workspace:
{
"id": "ws_REDACTED",
"name": "My workspace",
"createdAt": 1788681600000
}Sandbox:
{
"id": "sbx_REDACTED",
"workspaceId": "ws_REDACTED",
"name": "Data import",
"state": "running",
"vmSandbox": true,
"blockNetwork": false,
"size": "small",
"metadata": {},
"autoResume": true,
"createdAt": 1788681600000,
"lastUsedAt": 1788681600000
}state is cold, pending, running, or expired; vmSandbox is always present; VM responses
also include blockNetwork for the recorded network intent and size (small, large, xl, or xxl) for the
recorded compute profile. Optional diskGiB is the immutable whole root-disk size; it is omitted
for historical responses and older VMs whose size was not recorded. Do not infer their disk size
from the default. For known sizes, the full disk counts toward the account’s storage limit.
metadata defaults to {} and autoResume to true; historical saved create responses can omit
these fields, meaning no tags and wake-on-use behavior. Also,
lastUsedAt can be null. While a VM runs, lastUsedAt is the last API call that kept it awake and
the idle stop is due 30 minutes later (what counts); once it
stops, lastUsedAt is when it stopped. Ordinary Sandboxes use cold and running; VM provisioning adds
pending, and expired means the VM runtime is no longer available. The sbx_… Sandbox
id is the only public runtime identifier. Provider runtime, image, Pod, and
process identifiers are not part of the customer contract.
ExecutionResult:
{
"stdout": "hello\n",
"stderr": "",
"exitCode": 0,
"timedOut": false,
"stdoutTruncated": false,
"stderrTruncated": false
}Operation contains an opaque operationId; a state of accepted, running, completed, failed, timedOut, or cancelled; nullable exitCode, signal, startedAt, finishedAt, and expiresAt; acceptedAt; and absolute stdoutOffset and stderrOffset byte counts. completed, failed, timedOut, and cancelled are terminal.
OperationOutputChunk contains stream, base64-encoded dataBase64, absolute offset, nextOffset, earliestOffset, and truncated fields.
Preview contains a 32-character lowercase hexadecimal previewId, the public sandboxId, the
selected port, the bearer url, and ISO 8601 expiresAt timestamp.
ListedPreview additionally contains state and boolean alwaysOn; its expiresAt is nullable
and, for an always-on link, describes the current tunnel deadline rather than the link’s lifetime.
File range responses return binary content with Accept-Ranges: bytes, X-BoxCompute-Offset, X-BoxCompute-Next-Offset, X-BoxCompute-File-Size, and X-BoxCompute-EOF headers. A non-final range also supplies X-BoxCompute-Next-Cursor.
FileStat contains path, target kind (file, dir, or other), isSymbolicLink, size, and mtimeMs. For a symlink, target fields follow the link while isSymbolicLink describes the path itself. A FileListEntry contains name, path, entry kind (file, dir, symlink, or other), size, and mtimeMs. FileList contains up to 1,000 entries and nullable nextCursor. A successful conditional edit returns { "sha256": string } for the complete updated file.
Usage contains since, operations, executions, executionTimeMs, outputBytes, failedOperations, agentRuns, toolCalls, activeSandboxes, sandboxSlots, and up to 20 recent operation records. Each recent record contains id, workspaceId, nullable sandboxId, action, durationMs, outputBytes, success, and createdAt. Timestamps are Unix milliseconds.
BillingSummary contains currency: "usd", accountStatus (active or frozen), balance,
all-time settledUsage, activeEstimate, and modelBillingMode (observe or enforced).
balance.availableMicros is nullable: unavailable running estimates mean unknown spendable credit,
not zero. BillingTransactionPage contains currency, transactions, and nullable nextCursor.
Each transaction has id, kind, bucket (cash, promo, or plan), signed amountMicros,
description, Unix-millisecond createdAt, and nullable expiresAt. See
credit and wallet history for complete examples and filters.
SandboxCostReport contains the selected from and to Unix-millisecond bounds, currency
(usd), estimate status, totals, API-key groups, Sandbox rows, and unattributed totals.
SandboxCostDetail contains one Sandbox row, nullable creating-key metadata, currency and estimate
status, up to 1,000 settled runs, running estimates, and a truncated flag. Monetary amounts are
integer millionths of the currency. See Track Sandbox costs for filters, runnable requests,
field semantics, and the deleted-Sandbox identifier.
DeletedSandbox contains a deletion-record id, the original sandboxId, name, workspaceId,
nullable workspaceName, vmSandbox, size, opaque runtimes, deletedVia, nullable
deletedByApiKeyId, createdAt, nullable lastUsedAt, and deletedAt. Use the deletion-record
id in the retained-log route. See Manage Sandboxes for a
response example and the no-restore boundary.
AuditEvent contains id, type, createdAt, nullable requestId, actor, nullable request,
and nullable resourceId. See Audit API activity for filters, paging, the response
shape, and retention.
GET /api/v2/sandboxes/:id/analytics returns bounded lifecycle, operation, and resource aggregates
for one owned Sandbox, scoped to sandbox:read. Query parameters are optional from and to
(ISO 8601 instants with an offset), resolutionSeconds (whole seconds 60–86,400, default 300), and
generation (a specific runtime generation). Retention is 30 days, and requests are rate-limited to
30 per minute per account (429 RATE_LIMITED when exceeded):
curl --get 'https://api.boxcompute.ai/api/v2/sandboxes/sbx_REDACTED/analytics' \
-H 'Authorization: Bearer bc_live_REDACTED' \
--data-urlencode 'resolutionSeconds=300'HTTP 200 returns { "analytics": SandboxAnalytics }. The object contains sandboxId, from, to,
resolutionSeconds, retentionSeconds, generationsTruncated, operationsTruncated, a nullable
selectedGeneration, up to 32 generations (each with generation, runtimeClass container or
vm, start/ready/stop/finalize timestamps, and startupDurationMs/runtimeDurationMs), up to
1,001 operations time buckets (each with bucketStart, nullable generation, operations,
executions, durationMs, executionDurationMs, outputBytes, and failures), and a resources
object. resources.status is available, partial, unavailable, or not_configured;
resources.coverage is available, partial, or expired; and resources.series holds up to nine
per-generation series (cpu_usage_cores, memory_working_set_bytes,
network_receive_bytes_per_second, network_transmit_bytes_per_second,
filesystem_read_bytes_per_second, filesystem_write_bytes_per_second,
filesystem_usage_bytes, restarts_total, oom_kills_total) with a unit, an availability of
available or no_data, and timestamped points. Missing resource telemetry has no_data and
empty points, not numeric zeros; the response never exposes provider or Kubernetes identifiers.
Each resource point summarizes the preceding resolutionSeconds interval: CPU, network and disk
I/O rates are averaged; memory, filesystem space, restart totals and out-of-memory kill totals
use the interval’s maximum. For example, a 15-minute resolution covers the full 15 minutes rather
than sampling a single minute. VM disk read/write rates are available when telemetry is present,
but VM filesystem space (filesystem_usage_bytes) has no_data; it is not a disk-free-space check.
See Read Sandbox logs for active and deleted log filters, response entries, retention semantics, and CLI support.
Legacy v1
/api/v1 is a legacy one-Sandbox-per-workspace compatibility surface. New integrations should use v2, where a workspace can own multiple Sandbox instances.