Base URL & Authentication
OpenComputer exposes two API surfaces:
| Surface | Base URL | Auth Header | Use |
|---|
| Control plane | https://app.opencomputer.dev/api | X-API-Key: <key> | Create/list/kill sandboxes, templates, hibernation |
| Worker direct | connectURL from create/get response | Authorization: Bearer <jwt> | Exec, files, agents, PTY, timeout |
The connectURL and token fields are returned when you create or get a sandbox. Worker-direct routes have no /api prefix — for example, POST {connectURL}/sandboxes/:id/exec/run.
SDK users only need the API key. The SDK resolves connectURL and manages the JWT automatically.
Route Availability
Most operational routes are available on both surfaces. Exceptions:
| Route | Control Plane | Worker |
|---|
POST /api/sandboxes (create) | Yes | — |
GET /api/sandboxes (list all) | Yes | — |
DELETE /api/sandboxes/:id (kill) | Yes | — |
POST .../hibernate, .../wake | Yes | — |
| Checkpoint routes | Yes | — |
| Preview URL routes | Yes | — |
| Template routes | Yes | — |
POST .../token/refresh | — | Yes |
POST .../timeout | — | Yes |
| Exec, filesystem, agent, PTY routes | Yes | Yes |
Token Refresh
Worker JWTs are short-lived. To refresh:
curl -X POST {connectURL}/sandboxes/{id}/token/refresh \
-H "Authorization: Bearer $TOKEN"
Returns a new JWT. The SDKs handle this automatically.
Sandbox Lifecycle
POST /api/sandboxes — Create
Create a new sandbox.
Request body:
| Field | Type | Required | Description |
|---|
templateID | string | No | Template name (default: "base") |
timeout | int | No | Idle timeout in seconds (default: 300) |
cpuCount | int | No | CPU cores, 1–4 (default: 1) |
memoryMB | int | No | Memory in MB, up to 2048 (default: 512) |
envs | object | No | Environment variables |
metadata | object | No | Arbitrary key-value pairs |
Response 201:
{
"sandboxID": "sb-abc123",
"templateID": "base",
"status": "running",
"startedAt": "2025-01-15T10:30:00Z",
"endAt": "2025-01-15T10:35:00Z",
"cpuCount": 1,
"memoryMB": 512,
"metadata": {},
"connectURL": "https://worker-xyz.opencomputer.dev",
"token": "eyJhbG..."
}
curl -X POST https://app.opencomputer.dev/api/sandboxes \
-H "X-API-Key: $OPENCOMPUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"templateID": "base", "timeout": 600}'
GET /api/sandboxes — List
List all running sandboxes.
Response 200: Array of sandbox objects.
GET /api/sandboxes/:id — Get
Get sandbox details by ID. Available on both control plane and worker.
Response 200: Sandbox object (same shape as create response).
DELETE /api/sandboxes/:id — Kill
Terminate and remove a sandbox.
Response 204: No content.
POST /api/sandboxes/:id/timeout — Set Timeout
Update the idle timeout. Worker-direct only — must be sent to the worker via connectURL with a Bearer token. The control plane will reject this request.
Request body:
| Field | Type | Required | Description |
|---|
timeout | int | Yes | New timeout in seconds (must be > 0) |
Response 204: No content.
POST /api/sandboxes/:id/hibernate — Hibernate
Snapshot VM state and stop the sandbox. Control plane only.
Response 200:
{
"sandboxID": "sb-abc123",
"hibernationKey": "checkpoints/sb-abc123/1234567890.tar.zst",
"sizeBytes": 134217728,
"status": "hibernated"
}
POST /api/sandboxes/:id/wake — Wake
Resume a hibernated sandbox. Control plane only.
Request body:
| Field | Type | Required | Description |
|---|
timeout | int | No | Idle timeout after wake (default: 300) |
Response 200: Sandbox object with updated status.
Commands (Exec)
POST /api/sandboxes/:id/exec/run — Run Command
Execute a command synchronously and return the result. Available on both surfaces.
Request body:
| Field | Type | Required | Description |
|---|
cmd | string | Yes | Command to execute |
args | string[] | No | Command arguments |
envs | object | No | Environment variables |
cwd | string | No | Working directory |
timeout | int | No | Timeout in seconds (default: 60) |
Response 200:
{
"exitCode": 0,
"stdout": "Hello, World!\n",
"stderr": ""
}
curl -X POST {connectURL}/sandboxes/{id}/exec/run \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"cmd": "echo", "args": ["Hello, World!"]}'
POST /api/sandboxes/:id/exec — Create Exec Session
Start a long-running command as a session. Attach via WebSocket to stream I/O.
Request body:
| Field | Type | Required | Description |
|---|
cmd | string | Yes | Command to execute |
args | string[] | No | Command arguments |
envs | object | No | Environment variables |
cwd | string | No | Working directory |
timeout | int | No | Timeout in seconds |
maxRunAfterDisconnect | int | No | Seconds to keep running after all clients disconnect |
Response 201:
{
"sessionID": "es-abc123",
"sandboxID": "sb-abc123",
"command": "node",
"args": ["server.js"],
"running": true,
"exitCode": null,
"startedAt": "2025-01-15T10:30:00Z",
"attachedClients": 0
}
GET /api/sandboxes/:id/exec — List Sessions
Response 200: Array of ExecSessionInfo objects (same shape as create response).
GET /api/sandboxes/:id/exec/:sessionID — WebSocket Attach
Upgrade to WebSocket. Streams I/O using the binary protocol.
Pass auth via query parameter: ?token=<jwt>
On connect, the server replays the scrollback buffer, sends a 0x04 end marker, then streams live output.
POST /api/sandboxes/:id/exec/:sessionID/kill — Kill Session
Request body:
| Field | Type | Required | Description |
|---|
signal | int | No | Signal number (default: 9 / SIGKILL) |
Response 204: No content.
Agent Sessions
Agent sessions run the Claude Agent SDK inside the sandbox. Events stream over the exec session WebSocket transport — there is no separate agent WebSocket endpoint.
POST /api/sandboxes/:id/agent — Create
Request body:
| Field | Type | Required | Description |
|---|
prompt | string | No | Initial prompt |
model | string | No | Claude model name |
systemPrompt | string | No | System prompt |
allowedTools | string[] | No | Restrict available tools |
permissionMode | string | No | Permission mode |
maxTurns | int | No | Maximum turns |
cwd | string | No | Working directory |
mcpServers | object | No | MCP server configuration |
resume | string | No | Session ID to resume |
Response 201:
{
"sessionID": "as-abc123",
"sandboxID": "sb-abc123",
"running": true,
"startedAt": "2025-01-15T10:30:00Z",
"claudeSessionID": ""
}
curl -X POST {connectURL}/sandboxes/{id}/agent \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prompt": "Create a hello world app", "model": "claude-sonnet-4-20250514"}'
GET /api/sandboxes/:id/agent — List
Response 200: Array of AgentSessionInfo objects.
POST /api/sandboxes/:id/agent/:sid/prompt — Send Follow-up
Request body:
| Field | Type | Required | Description |
|---|
text | string | Yes | Follow-up prompt text |
Response 200: Empty object.
POST /api/sandboxes/:id/agent/:sid/interrupt — Interrupt
Stop the agent’s current turn.
Response 200: Empty object.
POST /api/sandboxes/:id/agent/:sid/kill — Kill
Terminate the agent session.
Response 200: Empty object.
Filesystem
GET /api/sandboxes/:id/files?path=... — Read File
Returns file content as plain text (not JSON). The path query parameter is required.
curl {connectURL}/sandboxes/{id}/files?path=/app/index.js \
-H "Authorization: Bearer $TOKEN"
Response 200: Raw file content with Content-Type: text/plain.
PUT /api/sandboxes/:id/files?path=... — Write File
The request body is written as raw file content (not JSON). The path query parameter specifies the destination.
curl -X PUT {connectURL}/sandboxes/{id}/files?path=/app/hello.txt \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: text/plain" \
-d "Hello, World!"
Response 204: No content.
GET /api/sandboxes/:id/files/list?path=... — List Directory
Query parameter: path (default: /)
Response 200:
[
{ "name": "app", "isDir": true, "size": 4096, "path": "/app" },
{ "name": "README.md", "isDir": false, "size": 1234, "path": "/README.md" }
]
Each entry has: name (string), isDir (boolean), size (int64), path (string).
POST /api/sandboxes/:id/files/mkdir?path=... — Create Directory
The path query parameter specifies the directory to create.
Response 204: No content.
DELETE /api/sandboxes/:id/files?path=... — Remove
Delete a file or directory. The path query parameter is required.
Response 204: No content.
Checkpoints
Maximum 10 checkpoints per sandbox.
POST /api/sandboxes/:id/checkpoints — Create
Request body:
| Field | Type | Required | Description |
|---|
name | string | Yes | Checkpoint name (unique per sandbox) |
Response 201:
{
"id": "cp-abc123",
"sandboxID": "sb-abc123",
"name": "before-migration",
"status": "processing",
"sizeBytes": 0,
"createdAt": "2025-01-15T10:30:00Z"
}
Status transitions: processing → ready or failed.
GET /api/sandboxes/:id/checkpoints — List
Response 200: Array of CheckpointInfo objects.
POST /api/sandboxes/:id/checkpoints/:checkpointId/restore — Restore
Revert the sandbox in-place to a checkpoint. All changes since the checkpoint are lost.
Response 200: Empty object.
POST /api/sandboxes/from-checkpoint/:checkpointId — Fork
Create a new sandbox from a checkpoint.
Request body:
| Field | Type | Required | Description |
|---|
timeout | int | No | Idle timeout for the new sandbox (default: 300) |
Response 201: Sandbox object (same shape as create).
curl -X POST https://app.opencomputer.dev/api/sandboxes/from-checkpoint/cp-abc123 \
-H "X-API-Key: $OPENCOMPUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"timeout": 600}'
DELETE /api/sandboxes/:id/checkpoints/:checkpointId — Delete
Response 204: No content.
Checkpoint Patches
Patches are scripts that run when a sandbox is spawned from a checkpoint. They apply in sequence order.
POST /api/sandboxes/checkpoints/:checkpointId/patches — Create
Request body:
| Field | Type | Required | Description |
|---|
script | string | Yes | Bash script to execute on spawn |
description | string | No | Human-readable description |
Response 201:
{
"patch": {
"id": "pa-abc123",
"checkpointId": "cp-abc123",
"script": "apt install -y curl",
"description": "Install curl",
"strategy": "on_wake",
"sequence": 1,
"createdAt": "2025-01-15T10:30:00Z"
}
}
GET /api/sandboxes/checkpoints/:checkpointId/patches — List
Response 200: Array of PatchInfo objects.
DELETE /api/sandboxes/checkpoints/:checkpointId/patches/:patchId — Delete
Response 204: No content.
Preview URLs
POST /api/sandboxes/:id/preview — Create
Request body:
| Field | Type | Required | Description |
|---|
port | int | Yes | Container port (1–65535) |
domain | string | No | Custom domain |
authConfig | object | No | Authentication configuration |
Response 201:
{
"id": "pv-abc123",
"sandboxId": "sb-abc123",
"hostname": "sb-abc123-p3000.preview.opencomputer.dev",
"port": 3000,
"sslStatus": "active",
"createdAt": "2025-01-15T10:30:00Z"
}
GET /api/sandboxes/:id/preview — List
Response 200: Array of preview URL objects.
DELETE /api/sandboxes/:id/preview/:port — Delete
Response 204: No content.
Templates
Templates are built from Dockerfiles. The Docker build produces a filesystem image that Firecracker boots from directly.
POST /api/templates — Build
Request body:
| Field | Type | Required | Description |
|---|
name | string | Yes | Template name |
dockerfile | string | Yes | Dockerfile content |
tag | string | No | Image tag (default: "latest") |
Response 201:
{
"id": "tpl-abc123",
"name": "my-template",
"tag": "latest",
"status": "ready",
"createdAt": "2025-01-15T10:30:00Z"
}
Dockerfile templates return status: "ready" immediately. Sandbox-snapshot templates transition processing → ready.
GET /api/templates — List
Response 200: Array of template objects.
GET /api/templates/:name — Get
Response 200: Template object.
DELETE /api/templates/:name — Delete
Response 204: No content.
PTY
Terminal sessions for interactive shell access.
POST /api/sandboxes/:id/pty — Create
Request body:
| Field | Type | Required | Description |
|---|
cols | int | No | Terminal columns (default: 80) |
rows | int | No | Terminal rows (default: 24) |
shell | string | No | Shell path (default: /bin/bash) |
Response 201:
{
"sessionID": "ps-abc123",
"sandboxID": "sb-abc123"
}
GET /api/sandboxes/:id/pty/:sessionID — WebSocket
Upgrade to WebSocket. Raw binary I/O — data from the PTY is sent as-is, data received is written to PTY stdin.
Pass auth via query parameter: ?token=<jwt>
POST /api/sandboxes/:id/pty/:sessionID/resize — Resize
Request body:
| Field | Type | Required | Description |
|---|
cols | int | Yes | New column count |
rows | int | Yes | New row count |
Response 200: Empty object.
PTY resize is HTTP-only — not exposed in the TypeScript or Python SDKs. The SDKs handle resize automatically for interactive sessions.
DELETE /api/sandboxes/:id/pty/:sessionID — Kill
Terminate a PTY session.
Response 204: No content.
WebSocket Binary Protocol
Exec and PTY WebSocket sessions use binary frames with a 1-byte stream prefix:
| Byte | Direction | Meaning |
|---|
0x00 | Client → Server | stdin data |
0x01 | Server → Client | stdout data |
0x02 | Server → Client | stderr data |
0x03 | Server → Client | Exit code (4-byte big-endian int32) |
0x04 | Server → Client | Scrollback end marker |
Connection flow:
- Client opens WebSocket with
?token=<jwt>
- Server replays scrollback buffer (historical output)
- Server sends
0x04 to mark end of scrollback
- Live output streams as
0x01/0x02 frames
- When the process exits, server sends
0x03 with the exit code
- Server closes the connection
Sending input: Prefix your data with 0x00 and send as a binary frame.
PTY sessions use the same binary framing but without stream prefixes — raw bidirectional terminal data.
All errors use a consistent envelope:
{
"error": "descriptive error message"
}
| Status Code | Meaning |
|---|
400 | Invalid request (missing fields, bad values) |
401 | Missing or invalid authentication |
403 | Insufficient permissions |
404 | Resource not found |
409 | Conflict (duplicate resource, e.g. checkpoint name) |
429 | Quota exceeded |
500 | Internal server error |
503 | Feature unavailable in current deployment mode |