# Sandboxes (/docs/api/sandboxes)



A sandbox is an isolated live execution environment. You create one from a
template, run commands and work with files inside it, and it's destroyed
when its TTL expires or when you kill it.

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

| Field               | Type    | Description                                                                              |
| ------------------- | ------- | ---------------------------------------------------------------------------------------- |
| `id`                | string  | Sandbox ID, prefixed `sbx_`                                                              |
| `orgId`             | string  | Owning organization, prefixed `org_`                                                     |
| `projectId`         | string  | Owning project, prefixed `prj_`                                                          |
| `status`            | string  | `creating`, `starting`, `ready`, `running`, `idle`, `expiring`, `destroyed`, or `failed` |
| `templateId`        | string  | Template ID, prefixed `tpl_`                                                             |
| `templateSlug`      | string  | Template slug, such as `python-node`                                                     |
| `templateVersion`   | string  | Resolved template version                                                                |
| `templateVersionId` | string  | TemplateVersion ID, prefixed `tplv_`                                                     |
| `ttlMs`             | integer | Requested lifetime in milliseconds                                                       |
| `expiresAt`         | string  | ISO 8601 timestamp when the sandbox expires                                              |
| `metadata`          | object  | String labels set at creation                                                            |
| `networkPolicy`     | object  | Normalized create-time outbound access policy                                            |
| `createdAt`         | string  | ISO 8601 creation timestamp, when available                                              |
| `destroyedAt`       | string  | ISO 8601 destruction timestamp, once destroyed                                           |
| `destroyedReason`   | string  | `idle_expired`, `platform_cleanup`, `ttl_expired`, or `user_killed`                      |

## Create a sandbox [#create-a-sandbox]

`POST /v1/sandboxes` — requires scope `sandbox:create`. Accepts an
[`Idempotency-Key`](/docs/api/pagination-and-idempotency) header.

Creates a sandbox from a template and returns it once provisioning starts.
All body fields are optional:

| Field               | Type    | Required | Default         | Constraints                                                                |
| ------------------- | ------- | -------- | --------------- | -------------------------------------------------------------------------- |
| `projectId`         | string  | No       | Default project | A `prj_` ID your key can access                                            |
| `template`          | string  | No       | `python-node`   | `python-node` or `node`; discover the current catalogue through Templates  |
| `templateVersionId` | string  | No       | —               | A `tplv_` ID; pins an exact immutable environment                          |
| `ttlMs`             | integer | No       | 3600000         | Positive integer, max 3600000 (1 hour). Use the TTL endpoint later.        |
| `metadata`          | object  | No       | —               | String map; max 16 keys, keys ≤ 64 chars, values ≤ 512 chars, ≤ 4 KB total |
| `networkPolicy`     | object  | No       | `unrestricted`  | Create-time outbound mode and, for list modes, up to 64 reviewed hosts     |

```bash title="Terminal"
curl -X POST https://api.crownest.dev/v1/sandboxes \
  -H "Authorization: Bearer $CROWNEST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f3d9c2a-create-01" \
  -d '{
    "template": "python-node",
    "ttlMs": 1800000,
    "metadata": { "run": "nightly-eval" }
  }'
```

Returns `201` with the created sandbox:

```json
{
  "sandbox": {
    "id": "sbx_abc123",
    "orgId": "org_abc123",
    "projectId": "prj_abc123",
    "status": "creating",
    "templateId": "tpl_abc123",
    "templateSlug": "python-node",
    "templateVersion": "2026-06-17",
    "templateVersionId": "tplv_python_node_2026_06_17",
    "ttlMs": 1800000,
    "expiresAt": "2026-06-11T13:30:00.000Z",
    "metadata": { "run": "nightly-eval" },
    "networkPolicy": { "mode": "deny-all" },
    "createdAt": "2026-06-11T13:00:00.000Z"
  }
}
```

## Control outbound access [#control-outbound-access]

`networkPolicy` is immutable for the lifetime of a Sandbox. Use one of these
provider-neutral forms:

```json
{ "mode": "unrestricted" }
{ "mode": "deny-all" }
{ "mode": "allowlist", "hosts": ["api.example.com", "*.example.org"] }
{ "mode": "denylist", "hosts": ["telemetry.example.com"] }
```

`deny-all` blocks internet traffic except Cloudflare's documented container DNS
path. An allowlist denies internet by default and permits matching HTTP or HTTPS
request hosts. A denylist keeps default internet access but blocks matching HTTP
or HTTPS request hosts. Redirect destinations are checked again.

Hosts are lowercase ASCII names, exact IPv4 literals, or a leftmost `*.` suffix
pattern. The suffix matches nested subdomains but not the apex. URLs, ports,
CIDR ranges, IPv6 literals, Unicode names, and other glob forms are rejected.
This surface doesn't claim post-resolution CIDR filtering or universal
DNS-rebinding protection.

Restrictive configuration completes before the Sandbox becomes ready. If the
runtime can't install it, creation fails instead of returning an unrestricted
Sandbox. HTTPS interception uses the curated image's trust configuration;
certificate-pinned or private-trust-store clients can fail.

```bash title="Terminal"
crownest sandboxes create \
  --network-policy allowlist \
  --network-hosts api.example.com,github.com
```

Errors: `invalid_request`, `forbidden`, `not_found`, `quota_exceeded`,
`sandbox_ttl_exceeded`.

## List sandboxes [#list-sandboxes]

`GET /v1/sandboxes` — requires scope `sandbox:read`.

Returns a list of live sandboxes, newest first, in the standard
[`{ data, hasMore }`](/docs/api/pagination-and-idempotency) envelope.
Destroyed and failed sandboxes are excluded unless you ask for them:

| Parameter        | Type   | Description                                                                |
| ---------------- | ------ | -------------------------------------------------------------------------- |
| `projectId`      | string | Filter to one project                                                      |
| `status`         | string | Filter by sandbox status                                                   |
| `include`        | string | `historical` to include destroyed and failed sandboxes                     |
| `metadata.<key>` | string | Filter by an exact metadata label, for example `metadata.run=nightly-eval` |

```bash title="Terminal"
curl "https://api.crownest.dev/v1/sandboxes?include=historical" \
  -H "Authorization: Bearer $CROWNEST_API_KEY"
```

```json
{
  "data": [
    {
      "id": "sbx_abc123",
      "status": "ready",
      "templateSlug": "python-node",
      "expiresAt": "2026-06-11T13:30:00.000Z"
    }
  ],
  "hasMore": false
}
```

## Get a sandbox [#get-a-sandbox]

`GET /v1/sandboxes/{sandboxId}` — requires scope `sandbox:read`.

Returns one sandbox by ID. Poll this endpoint to watch `status` move from
`creating` to `ready`.

```bash title="Terminal"
curl https://api.crownest.dev/v1/sandboxes/sbx_abc123 \
  -H "Authorization: Bearer $CROWNEST_API_KEY"
```

Returns `200` with `{ "sandbox": { ... } }`. Errors: `forbidden`,
`not_found`.

## Set a sandbox TTL [#set-a-sandbox-ttl]

`POST /v1/sandboxes/{sandboxId}/ttl` — requires scope
`sandbox:extend`. Requires an [`Idempotency-Key`](/docs/api/pagination-and-idempotency)
header.

This endpoint resets a live sandbox's TTL countdown from the time of the
request. The requested `ttlMs` must
move `expiresAt` later than its current value. A sandbox's total session is
capped at 24 hours from creation.

| Field   | Type    | Required | Description                                                                   |
| ------- | ------- | -------- | ----------------------------------------------------------------------------- |
| `ttlMs` | integer | Yes      | New lifetime in milliseconds. Max 86400000 (24 hours), counted from creation. |

```bash title="Terminal"
curl -X POST https://api.crownest.dev/v1/sandboxes/sbx_abc123/ttl \
  -H "Authorization: Bearer $CROWNEST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f3d9c2a-extend-01" \
  -d '{ "ttlMs": 3600000 }'
```

Returns `200` with `{ "sandbox": { ... } }`, including the refreshed
`expiresAt`.

Errors: `invalid_request`, `forbidden`, `not_found`,
`sandbox_destroyed`, `sandbox_ttl_exceeded`.

### Deprecated alias [#deprecated-alias]

`POST /v1/sandboxes/{sandboxId}/extend` remains available until July 10, 2027. Its responses include `Deprecation`, `Sunset`, and `Link` headers that
identify `POST /v1/sandboxes/{sandboxId}/ttl` as the canonical endpoint.

## Kill a sandbox [#kill-a-sandbox]

`DELETE /v1/sandboxes/{sandboxId}` — requires scope `sandbox:kill`.

Destroys the sandbox immediately. The workspace filesystem is lost; only
[artifacts](/docs/api/artifacts) you exported survive. The operation is
idempotent by state — deleting an already-destroyed sandbox returns the
destroyed record rather than an error.

```bash title="Terminal"
curl -X DELETE https://api.crownest.dev/v1/sandboxes/sbx_abc123 \
  -H "Authorization: Bearer $CROWNEST_API_KEY"
```

```json
{
  "sandbox": {
    "id": "sbx_abc123",
    "status": "destroyed",
    "destroyedAt": "2026-06-11T13:05:00.000Z",
    "destroyedReason": "user_killed"
  }
}
```

Errors: `forbidden`, `not_found`.

## Next steps [#next-steps]

* Run processes in [Commands](/docs/api/commands), or interpreter snippets in
  [Code](/docs/api/code).
* Move data in and out in [Files](/docs/api/files).
* Keep outputs after destruction in [Artifacts](/docs/api/artifacts).
