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.
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/execute |
sandbox:execute |
{ "result": ExecutionResult } |
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: "small" is 0.5 vCPU / 1,024 MiB and "large" is 1.5 vCPU / 3,072 MiB; both
default to a 30 GiB thin-provisioned workspace. 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. 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. Retained legacy runtimes
and files remain available for recovery, but an unavailable legacy runtime cannot be recreated
through start or execution. 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 Sandbox-count or workspace-storage limit. Stopped Sandboxes count toward both.
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:
{}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.
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.
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": "[email protected]", "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",
"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 or large) for the
recorded compute profile; and
lastUsedAt can be null. 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.
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 is reported as absent
series, not as numeric zeros, and the response never exposes provider or Kubernetes identifiers.
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.