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

URL: https://prized.dev/docs/sandboxes-api

What a sandbox is, and how its lifecycle works, is on [Sandboxes](https://prized.dev/docs/sandboxes). The routes for boxes and the workspace are on [API reference](https://prized.dev/docs/api).

## Two hosts, one key [#two-hosts-one-key]

| What     | Control plane                                                       | Edge                                                      |
| -------- | ------------------------------------------------------------------- | --------------------------------------------------------- |
| Base URL | `https://api.prized.dev/api/v1`                                     | The 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.

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

## Authentication [#authentication]

Send `Authorization: Bearer dcp_…` on every request, with an API key from **Dashboard → Settings → API keys** ([API keys](https://prized.dev/docs/workspaces#api-keys)). Expiry, revocation, and rotation work as on the rest of the API ([Authentication](https://prized.dev/docs/api#authentication)).

| Key                                                                   | Reaches                                        |
| --------------------------------------------------------------------- | ---------------------------------------------- |
| Full access                                                           | Every route below                              |
| Custom, **Sandboxes: Read**                                           | `GET` routes: templates, the list, one sandbox |
| Custom, **Sandboxes: Write**                                          | Every route below, the edge included           |
| Limited to some boxes, or an [agent sign-in](https://prized.dev/docs/agent-sign-in) key | None: `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](https://prized.dev/docs/teams#roles)'s gets `404 not_found` on every sandbox route. On the free credit alone, a workspace works within the [free tier](https://prized.dev/docs/sandboxes#free-tier)'s limits.

## Conventions [#conventions]

| Topic                 | Rule                                                                                                                                                                                                                                                                         |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| JSON                  | Send `Content-Type: application/json` on the control plane. A field the route does not know is `422 unknown_field` with `field`, never ignored.                                                                                                                              |
| Ids                   | Sandboxes are `sbx_…`, forks `frk_…`. An unknown, malformed, deleted, or foreign id is `404 not_found`.                                                                                                                                                                      |
| 202 means in progress | Pause, resume, fork, resize, and delete answer `202` with the sandbox as written; its `state` moves within seconds. Poll `GET /sandboxes/{id}`.                                                                                                                              |
| Idempotency           | `POST /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`. |
| Units                 | `memoryMb` is MiB, `diskGb` is GiB, timers are whole minutes, timestamps are RFC 3339 UTC.                                                                                                                                                                                   |
| Stability             | Fields are added without notice; ignore the ones you do not know.                                                                                                                                                                                                            |

## Templates [#templates]

| Route                           | Answer                                                                                                   |
| ------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `GET /api/v1/sandbox-templates` | `200 {templates: [...]}`, each `{id, resources: {defaults, min, max}, durability, defaultMaxRuntimeMin}` |

```json
{
  "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](https://prized.dev/docs/sandboxes#templates).

## Create [#create]

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

```bash
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}'
```

| Field           | Default                                                              | Rule                                                                                                                                                                                                         |
| --------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `template`      | Required                                                             | A template id from `GET /sandbox-templates`                                                                                                                                                                  |
| `resources`     | The 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)                                                                                               |
| `envVars`       | None                                                                 | Up 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](https://prized.dev/docs/sandboxes#environment-variables)) |
| `labels`        | None                                                                 | Up to 32 string pairs (keys up to 64 characters, values up to 256), yours to filter on                                                                                                                       |
| `autoPauseMin`  | Off                                                                  | Pause after this many idle minutes; `0` is off, up to 40,320                                                                                                                                                 |
| `autoDeleteMin` | Off                                                                  | Delete after this many minutes paused; `0` is off, up to 40,320                                                                                                                                              |
| `maxRuntimeMin` | The template's `defaultMaxRuntimeMin` (1,440); 60 on the free tier   | Delete this many minutes after the create; up to the workspace's maximum runtime (1,440, or 60 on the free tier)                                                                                             |

## The sandbox object [#the-sandbox-object]

```json
{
  "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"
  }
}
```

| Field                           | Meaning                                                                                                                      |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `state`                         | `starting`, `running`, `pausing`, `paused`, `resizing`, `deleting`, `deleted`, or `error` ([States](https://prized.dev/docs/sandboxes#states)) |
| `durability`                    | Always `ephemeral`                                                                                                           |
| `expiresAt`                     | When `maxRuntimeMin` deletes it                                                                                              |
| `errorReason`                   | Why it is in `error`; otherwise `null`                                                                                       |
| `autoPauseMin`, `autoDeleteMin` | `null` when off                                                                                                              |
| `toolboxBase`                   | The edge address for this sandbox's commands and files                                                                       |
| `lastActiveAt`                  | The last activity `autoPauseMin` counts from                                                                                 |

## Read [#read]

| Route                        | Answer                                                            |
| ---------------------------- | ----------------------------------------------------------------- |
| `GET /api/v1/sandboxes`      | `200 {sandboxes: [...]}`: every sandbox not deleted, newest first |
| `GET /api/v1/sandboxes/{id}` | `200 {sandbox}`, or `404 not_found` once it is deleted            |

## Lifecycle [#lifecycle]

| Route                                 | Body               | Answer                                                                               |
| ------------------------------------- | ------------------ | ------------------------------------------------------------------------------------ |
| `POST /api/v1/sandboxes/{id}/pause`   |                    | `202 {sandbox}`, `state: "pausing"`                                                  |
| `POST /api/v1/sandboxes/{id}/resume`  |                    | `202 {sandbox}`, `state: "starting"`. Checks the balance and the quota like a create |
| `POST /api/v1/sandboxes/{id}/refresh` |                    | `204`: 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 [#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`.

```json
{
  "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](https://prized.dev/docs/sandboxes#fork).

### Resize [#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.

```json
{
  "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 [#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 [#run-a-command]

| Route                   | Body                              | Answer                                                  |
| ----------------------- | --------------------------------- | ------------------------------------------------------- |
| `POST /process/execute` | `{command, cwd?, env?, timeout?}` | `200 {stdout, stderr, exitCode, durationMs, timedOut?}` |

```bash
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 [#files]

| Route                 | Body or query                          | Answer                                                       |
| --------------------- | -------------------------------------- | ------------------------------------------------------------ |
| `POST /files/upload`  | query `path`; the raw file as the body | `200 {path, size}`                                           |
| `GET /files/download` | query `path`                           | The file as `application/octet-stream` with `Content-Length` |

```bash
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 [#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 [#errors]

The control plane:

| HTTP | `error`                                                                  | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ---- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400  | `bad_request`                                                            | The 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                                                                                                                                                                                                                                                                                                         |
| 400  | `invalid_idempotency_key`                                                | Not 1 to 128 printable characters                                                                                                                                                                                                                                                                                                                                                                                                                      |
| 401  | `unauthorized`                                                           | No key, or a revoked one                                                                                                                                                                                                                                                                                                                                                                                                                               |
| 401  | `token_expired`                                                          | The key has expired, with `expiresAt`                                                                                                                                                                                                                                                                                                                                                                                                                  |
| 403  | `key_access_denied`                                                      | The key lacks Sandboxes: Read or Write, or is limited to some boxes; `message` names what it needs                                                                                                                                                                                                                                                                                                                                                     |
| 404  | `not_found`                                                              | No such sandbox in this workspace, a contractor's key, or a create, fork, or resize while sandboxes are switched off for this workspace (email the support address shown on https://prized.dev/docs/troubleshooting)                                                                                                                                                                                                                                                                                       |
| 404  | `template_not_found`                                                     | `template` is missing or names no template                                                                                                                                                                                                                                                                                                                                                                                                             |
| 409  | `paid_balance_required`                                                  | Free-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](https://prized.dev/docs/billing#topping-up)                                                                                                                                                                                                                                                                                     |
| 409  | `insufficient_balance`                                                   | The balance is empty                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| 409  | `workspace_held`                                                         | The workspace is on hold ([Not allowed](https://prized.dev/docs/limits#not-allowed)), on a create, resume, or fork; `message` says why. The hold paused its running sandboxes; pausing and deleting still work. Email the support address shown on https://prized.dev/docs/troubleshooting for a review                                                                                                                                                                                                                      |
| 409  | `workspace_closing`                                                      | The workspace is being closed, on a create, resume, or fork; the owner can reopen it ([Close or erase the workspace](https://prized.dev/docs/workspaces#close-or-erase-the-workspace))                                                                                                                                                                                                                                                                                   |
| 409  | `template_inactive`                                                      | The template is retired; pick another                                                                                                                                                                                                                                                                                                                                                                                                                  |
| 409  | `idempotency_conflict`                                                   | The key was used for another request                                                                                                                                                                                                                                                                                                                                                                                                                   |
| 409  | `durability_incompatible`                                                | `durability: "persistent"`; every sandbox is `ephemeral`                                                                                                                                                                                                                                                                                                                                                                                               |
| 409  | `parent_not_running`                                                     | Fork of a sandbox that is not `running`, with its `state`                                                                                                                                                                                                                                                                                                                                                                                              |
| 409  | `parent_not_forkable`                                                    | This sandbox cannot be forked                                                                                                                                                                                                                                                                                                                                                                                                                          |
| 409  | `fork_capacity`                                                          | The parent's machine has room for only `fits` more copies; fork that many or fewer                                                                                                                                                                                                                                                                                                                                                                     |
| 409  | `fork_unsupported_host`                                                  | This sandbox's machine cannot fork; create a fresh sandbox and fork that                                                                                                                                                                                                                                                                                                                                                                               |
| 409  | `resize_rejected`                                                        | With `reason`: `not_stopped` (pause it first), `resize_in_progress`, `lifecycle_in_progress`, `unchanged`, or `host_capacity`                                                                                                                                                                                                                                                                                                                          |
| 422  | `unknown_field`                                                          | A field the route does not take, named in `field`                                                                                                                                                                                                                                                                                                                                                                                                      |
| 422  | `invalid_resources`                                                      | A shape outside the template's bounds, or over the [free tier](https://prized.dev/docs/sandboxes#free-tier)'s 2 vCPU and 4 GB (then with `"tier": "free"` and `limit`, in vCPU or MB), named in `field`                                                                                                                                                                                                                                                                  |
| 422  | `disk_shrink_unsupported`                                                | A resize to a smaller disk                                                                                                                                                                                                                                                                                                                                                                                                                             |
| 422  | `invalid_max_runtime`                                                    | `maxRuntimeMin` is not a positive whole number                                                                                                                                                                                                                                                                                                                                                                                                         |
| 422  | `invalid_count`                                                          | A fork `count` outside 1 to `max` (256)                                                                                                                                                                                                                                                                                                                                                                                                                |
| 422  | `invalid_labels`                                                         | Fork `labels` that are not up to 32 string pairs                                                                                                                                                                                                                                                                                                                                                                                                       |
| 429  | `sandbox_quota_exceeded`                                                 | A [quota](https://prized.dev/docs/limits#sandboxes), or a [free-tier](https://prized.dev/docs/sandboxes#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 the support address shown on https://prized.dev/docs/troubleshooting). A fork adds `admitted`, how many children would have fit |
| 503  | `sandbox_capacity`                                                       | No sandbox capacity is free right now. Nothing was created or billed; retry after `Retry-After` (also `retryAfterSec` in the body)                                                                                                                                                                                                                                                                                                                     |
| 503  | `sandboxes_disabled`, `sandbox_create_disabled`, `sandbox_fork_disabled` | Sandboxes, or creating or forking them, are paused for maintenance; retry later                                                                                                                                                                                                                                                                                                                                                                        |

The edge:

| HTTP | `error`                                                                        | Meaning                                                                                    |
| ---- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| 400  | `bad_json`, `missing_command`, `cwd_not_absolute`, `bad_timeout`, `bad_env`    | The exec document is not the documented shape; `detail` says more where it can             |
| 400  | `spawn_failed`                                                                 | The command could not start (a `cwd` that does not exist); `detail` has the system's words |
| 400  | `missing_path`, `path_not_absolute`, `symlink_not_allowed`, `not_regular_file` | The `path` is missing, relative, through a symlink, or not a regular file                  |
| 401  | `unauthorized`, `token_expired`                                                | As on the control plane                                                                    |
| 403  | `key_access_denied`                                                            | The key lacks Sandboxes: Write                                                             |
| 403  | `path_outside_root`, `permission_denied`                                       | The path is outside `/home/prized`, or `prized` cannot touch it                            |
| 404  | `not_found`                                                                    | No such sandbox for this key, or no such file                                              |
| 409  | `sandbox_not_running`                                                          | The sandbox is not `running`: resume it and wait                                           |
| 413  | `body_too_large`, `file_too_large`                                             | Over 1 MiB for an exec document, or 100 MiB for a file                                     |
| 429  | `rate_limited`                                                                 | Over the [edge limits](#edge-limits); `Retry-After` says when                              |
| 502  | `sandbox_unreachable`                                                          | The sandbox did not answer. An exec may or may not have run                                |
| 502  | `resolve_failed`, `sandbox_credentials_unavailable`                            | The edge could not look the sandbox up; retry                                              |
| 500  | `control_plane_unavailable`                                                    | The edge could not reach the control plane to check the key                                |

## Not yet [#not-yet]

| Not yet                                             | Instead                                                            |
| --------------------------------------------------- | ------------------------------------------------------------------ |
| Streaming output, or a terminal                     | Write output to a file and read it with a later exec or a download |
| Listing, moving, or deleting files through the edge | `ls`, `mv`, and `rm` through exec                                  |
| Webhooks                                            | Poll `GET /api/v1/sandboxes/{id}`                                  |
| An OpenAPI document, or TypeScript and Python SDKs  | The routes above with `curl` or any HTTP client                    |
