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.

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]
}
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": "[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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close