Sign in

Sandbox API

Every sandbox route: create, list, pause, resume, fork, resize, and delete on the control plane; run commands and move files on the edge; with the fields, states, and error codes.

On this page16

What a sandbox is, and how its lifecycle works, is on Sandboxes. The routes for boxes and the workspace are on API reference.

Two hosts, one key

WhatControl planeEdge
Base URLhttps://api.prized.dev/api/v1The sandbox's toolboxBase
Routes/sandboxes, /sandbox-templates/process/execute, /files/upload, /files/download
Errors{"error": "code"}, sometimes with reason, field, or message{"error": "code"}, sometimes with detail or message

Read toolboxBase from the sandbox object rather than building it; it is the same for the sandbox's whole life.

export PRIZED_API_KEY=dcp_…
API=https://api.prized.dev/api/v1
AUTH="Authorization: Bearer $PRIZED_API_KEY"

Authentication

Send Authorization: Bearer dcp_… on every request, with an API key from Dashboard → Settings → API keys (API keys). Expiry, revocation, and rotation work as on the rest of the API (Authentication).

KeyReaches
Full accessEvery route below
Custom, Sandboxes: ReadGET routes: templates, the list, one sandbox
Custom, Sandboxes: WriteEvery route below, the edge included
Limited to some boxes, or an agent sign-in keyNone: 403 key_access_denied

A key acts as the member who made it: an owner's or member's key sees every sandbox in the workspace, and a contractor's gets 404 not_found on every sandbox route. On the free credit alone, a workspace works within the free tier's limits.

Conventions

TopicRule
JSONSend Content-Type: application/json on the control plane. A field the route does not know is 422 unknown_field with field, never ignored.
IdsSandboxes are sbx_…, forks frk_…. An unknown, malformed, deleted, or foreign id is 404 not_found.
202 means in progressPause, resume, fork, resize, and delete answer 202 with the sandbox as written; its state moves within seconds. Poll GET /sandboxes/{id}.
IdempotencyPOST /sandboxes and POST /sandboxes/{id}/fork take an Idempotency-Key header (1 to 128 printable characters): a retry with the same key and body answers 200 with the first result, and makes nothing. The same key with another body is 409 idempotency_conflict.
UnitsmemoryMb is MiB, diskGb is GiB, timers are whole minutes, timestamps are RFC 3339 UTC.
StabilityFields are added without notice; ignore the ones you do not know.

Templates

RouteAnswer
GET /api/v1/sandbox-templates200 {templates: [...]}, each {id, resources: {defaults, min, max}, durability, defaultMaxRuntimeMin}
{
  "templates": [
    {
      "id": "prized-base-v1",
      "resources": {
        "defaults": {"vcpu": 4, "memoryMb": 8192, "diskGb": 10},
        "min": {"vcpu": 1, "memoryMb": 1024, "diskGb": 10},
        "max": {"vcpu": 8, "memoryMb": 16384, "diskGb": 100}
      },
      "durability": "ephemeral",
      "defaultMaxRuntimeMin": 1440
    }
  ]
}

What each template holds is on Sandboxes: Templates.

Create

POST /api/v1/sandboxes answers 201 {sandbox} with state: "starting"; read it until running.

curl -s -X POST "$API/sandboxes" -H "$AUTH" -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" -d '{"template":"prized-agent-v1","resources":{"vcpu":2,"memoryMb":4096},"envVars":{"ANTHROPIC_API_KEY":"sk-ant-…"},"labels":{"run":"pr-1874"},"autoPauseMin":10,"autoDeleteMin":60,"maxRuntimeMin":120}'
FieldDefaultRule
templateRequiredA template id from GET /sandbox-templates
resourcesThe template's defaults (capped at 2 vCPU and 4 GB on the free tier){vcpu, memoryMb, diskGb}, any of them, each within the template's min and max (and the free tier's caps)
envVarsNoneUp to 32 names (shell identifiers of up to 64 characters), each value up to 16 KiB; set for every command, stored encrypted, never returned (Environment variables)
labelsNoneUp to 32 string pairs (keys up to 64 characters, values up to 256), yours to filter on
autoPauseMinOffPause after this many idle minutes; 0 is off, up to 40,320
autoDeleteMinOffDelete after this many minutes paused; 0 is off, up to 40,320
maxRuntimeMinThe template's defaultMaxRuntimeMin (1,440); 60 on the free tierDelete this many minutes after the create; up to the workspace's maximum runtime (1,440, or 60 on the free tier)

The sandbox object

{
  "sandbox": {
    "id": "sbx_3f9c0a1b2d4e5f60",
    "state": "running",
    "template": "prized-agent-v1",
    "resources": {"vcpu": 2, "memoryMb": 4096, "diskGb": 10},
    "durability": "ephemeral",
    "labels": {"run": "pr-1874"},
    "autoPauseMin": 10,
    "autoDeleteMin": 60,
    "expiresAt": "2026-10-14T19:02:11Z",
    "errorReason": null,
    "toolboxBase": "https://…/v1/sandbox/sbx_3f9c0a1b2d4e5f60",
    "createdAt": "2026-10-14T17:02:11Z",
    "lastActiveAt": "2026-10-14T17:05:40Z"
  }
}
FieldMeaning
statestarting, running, pausing, paused, resizing, deleting, deleted, or error (States)
durabilityAlways ephemeral
expiresAtWhen maxRuntimeMin deletes it
errorReasonWhy it is in error; otherwise null
autoPauseMin, autoDeleteMinnull when off
toolboxBaseThe edge address for this sandbox's commands and files
lastActiveAtThe last activity autoPauseMin counts from

Read

RouteAnswer
GET /api/v1/sandboxes200 {sandboxes: [...]}: every sandbox not deleted, newest first
GET /api/v1/sandboxes/{id}200 {sandbox}, or 404 not_found once it is deleted

Lifecycle

RouteBodyAnswer
POST /api/v1/sandboxes/{id}/pause202 {sandbox}, state: "pausing"
POST /api/v1/sandboxes/{id}/resume202 {sandbox}, state: "starting". Checks the balance and the quota like a create
POST /api/v1/sandboxes/{id}/refresh204: counts as activity for autoPauseMin
POST /api/v1/sandboxes/{id}/fork{count, labels?}202 {fork, sandboxes}, below
POST /api/v1/sandboxes/{id}/resize{resources}202 {sandbox, operation}, below
DELETE /api/v1/sandboxes/{id}202 {sandbox}, state: "deleting"; again on a deleting sandbox is a no-op

Fork

count is 1 to 256; labels replaces the parent's on every child. The answer lists the children, each starting; poll each until running.

{
  "fork": {
    "id": "frk_9a1c44e0b7d2f318",
    "parentId": "sbx_3f9c0a1b2d4e5f60",
    "count": 2,
    "status": "active",
    "sandboxIds": ["sbx_51d0c2aa9e8b7f64", "sbx_c8e4f1b20a3d9e75"],
    "createdAt": "2026-10-14T17:06:02Z"
  },
  "sandboxes": [{"id": "sbx_51d0c2aa9e8b7f64", "state": "starting", "…": "…"}]
}

What a child inherits, and the limits, are on Sandboxes: Fork.

Resize

A paused sandbox only. resources names any of vcpu, memoryMb, diskGb, within the template's bounds; disk never shrinks. The sandbox shows resizing, then paused again; its next resume boots fresh from the resized disk, without the paused memory.

{
  "sandbox": {"id": "sbx_3f9c0a1b2d4e5f60", "state": "resizing", "…": "…"},
  "operation": {
    "id": "…",
    "status": "pending",
    "resources": {"vcpu": 4, "memoryMb": 8192, "diskGb": 20},
    "coldRestart": true,
    "pausedMemoryDiscarded": true,
    "createdAt": "2026-10-14T17:10:00Z",
    "completedAt": null
  }
}

Read the sandbox until it leaves resizing: paused with the new resources when the resize succeeded, paused with the old ones when it was turned down before it began, or error (with errorReason) when it failed partway, after which the sandbox cannot resume and should be deleted. To read the operation itself, send the resize with an Idempotency-Key and send the same request again: the replay answers 200 with the operation as it is now, its status moving through pending and applying to succeeded, failed (with failureReason), or cancelled.

The edge

Routes below are relative to the sandbox's toolboxBase, with the same bearer. They need a running sandbox: a paused one answers 409 sandbox_not_running, and nothing on the edge wakes it.

Run a command

RouteBodyAnswer
POST /process/execute{command, cwd?, env?, timeout?}200 {stdout, stderr, exitCode, durationMs, timedOut?}
curl -s -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"npm test","cwd":"/home/prized/work/app","env":{"CI":"1"},"timeout":600}'
# {"stdout":"…","stderr":"","exitCode":0,"durationMs":8124}
  • How it runs. bash -c as the user prized, in cwd (absolute; /home/prized/work by default), with the create's envVars and then this request's env on top.
  • The answer is the verdict. A command that fails still answers 200; read exitCode.
  • timeout is in seconds, 300 by default and 3,600 at most. At the limit the command and everything it started are killed: exitCode: -1, timedOut: true.
  • Output. Each stream keeps its first 4 MiB; the rest is dropped. For more, write to a file and download it.
  • One run per request. An exec whose answer was lost is never retried for you, since it may already have run; retry it yourself only if the command is safe to run twice.

Files

RouteBody or queryAnswer
POST /files/uploadquery path; the raw file as the body200 {path, size}
GET /files/downloadquery pathThe file as application/octet-stream with Content-Length
curl -s -X POST "$TOOLBOX/files/upload?path=/home/prized/work/app.py" -H "$AUTH" --data-binary @app.py
curl -s "$TOOLBOX/files/download?path=/home/prized/work/out.json" -H "$AUTH" -o out.json

Paths are absolute and inside /home/prized; a path through a symlink is refused. An upload creates missing directories and replaces the file whole, never half-written. Downloads are regular files only; list or archive a directory with a command (tar czf /home/prized/out.tgz work) and download that. Each transfer is up to 100 MiB and 10 minutes.

Edge limits

48 concurrent requests and 60 a second (bursts of 120) per key, and 8 concurrent and 20 a second (bursts of 40) per sandbox; past either, 429 rate_limited with Retry-After. The request document of an exec is at most 1 MiB.

Errors

The control plane:

HTTPerrorMeaning
400bad_requestThe body is not a JSON object, or a field has the wrong type or is out of range; for envVars, with field and a message stating the rules
400invalid_idempotency_keyNot 1 to 128 printable characters
401unauthorizedNo key, or a revoked one
401token_expiredThe key has expired, with expiresAt
403key_access_deniedThe key lacks Sandboxes: Read or Write, or is limited to some boxes; message names what it needs
404not_foundNo such sandbox in this workspace, a contractor's key, or a create, fork, or resize while sandboxes are switched off for this workspace (email )
404template_not_foundtemplate is missing or names no template
409paid_balance_requiredFree-tier access is switched off for your workspace, and it has only free credit, on a create, resume, or fork: add a plan or a top-up
409insufficient_balanceThe balance is empty
409workspace_heldThe workspace is on hold (Not allowed), on a create, resume, or fork; message says why. The hold paused its running sandboxes; pausing and deleting still work. Email for a review
409workspace_closingThe workspace is being closed, on a create, resume, or fork; the owner can reopen it (Close or erase the workspace)
409template_inactiveThe template is retired; pick another
409idempotency_conflictThe key was used for another request
409durability_incompatibledurability: "persistent"; every sandbox is ephemeral
409parent_not_runningFork of a sandbox that is not running, with its state
409parent_not_forkableThis sandbox cannot be forked
409fork_capacityThe parent's machine has room for only fits more copies; fork that many or fewer
409fork_unsupported_hostThis sandbox's machine cannot fork; create a fresh sandbox and fork that
409resize_rejectedWith reason: not_stopped (pause it first), resize_in_progress, lifecycle_in_progress, unchanged, or host_capacity
422unknown_fieldA field the route does not take, named in field
422invalid_resourcesA shape outside the template's bounds, or over the free tier's 2 vCPU and 4 GB (then with "tier": "free" and limit, in vCPU or MB), named in field
422disk_shrink_unsupportedA resize to a smaller disk
422invalid_max_runtimemaxRuntimeMin is not a positive whole number
422invalid_countA fork count outside 1 to max (256)
422invalid_labelsFork labels that are not up to 32 string pairs
429sandbox_quota_exceededA quota, or a free-tier limit (then with "tier": "free" and limit, in the reason's unit), would be passed, named in reason: sandbox_limit, running_vcpu, running_memory, retained_disk, or max_runtime; on a resume, customer_disabled means sandboxes are switched off for this workspace (email ). A fork adds admitted, how many children would have fit
503sandbox_capacityNo sandbox capacity is free right now. Nothing was created or billed; retry after Retry-After (also retryAfterSec in the body)
503sandboxes_disabled, sandbox_create_disabled, sandbox_fork_disabledSandboxes, or creating or forking them, are paused for maintenance; retry later

The edge:

HTTPerrorMeaning
400bad_json, missing_command, cwd_not_absolute, bad_timeout, bad_envThe exec document is not the documented shape; detail says more where it can
400spawn_failedThe command could not start (a cwd that does not exist); detail has the system's words
400missing_path, path_not_absolute, symlink_not_allowed, not_regular_fileThe path is missing, relative, through a symlink, or not a regular file
401unauthorized, token_expiredAs on the control plane
403key_access_deniedThe key lacks Sandboxes: Write
403path_outside_root, permission_deniedThe path is outside /home/prized, or prized cannot touch it
404not_foundNo such sandbox for this key, or no such file
409sandbox_not_runningThe sandbox is not running: resume it and wait
413body_too_large, file_too_largeOver 1 MiB for an exec document, or 100 MiB for a file
429rate_limitedOver the edge limits; Retry-After says when
502sandbox_unreachableThe sandbox did not answer. An exec may or may not have run
502resolve_failed, sandbox_credentials_unavailableThe edge could not look the sandbox up; retry
500control_plane_unavailableThe edge could not reach the control plane to check the key

Not yet

Not yetInstead
Streaming output, or a terminalWrite output to a file and read it with a later exec or a download
Listing, moving, or deleting files through the edgels, mv, and rm through exec
WebhooksPoll GET /api/v1/sandboxes/{id}
An OpenAPI document, or TypeScript and Python SDKsThe routes above with curl or any HTTP client