Skip to content

HTTP API reference

Public v2 routes, authentication, request bodies, and response shapes.

Updated View as Markdown

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_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 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]
}
JSON

The 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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close