# Sandboxes API

> Create and manage managed sandboxes: VMs on platform engines that run belt remote start and connect back as an ordinary remote. Use a sandbox's remoteid with the Remotes and Exec runs APIs the same wa...

**URL:** https://inference.sh/docs/api/rest/sandboxes
**Last updated:** 2026-10-31

---

Create and manage **managed sandboxes**: VMs on platform engines that run `belt remote start` and connect back as an ordinary [remote](/docs/api/rest/remotes). Use a sandbox's `remote_id` with the Remotes and Exec runs APIs the same way as a machine you registered yourself.

Requires API key scopes `sandboxes:read` (list, get) and `sandboxes:write` (create, start, stop, delete). The `standard` preset includes both. The key a box holds has the `remote` preset's scopes only, so a box cannot create sandboxes.

Sandboxes are open to every workspace. Create and start are refused only when the workspace's `feature:sandboxes` [entitlement](/docs/api/rest/entitlements) is set to `enabled: false`: `403` `feature_not_available`, detail `sandboxes are turned off for this workspace`.

→ [Remotes API](/docs/api/rest/remotes) · [Exec runs API](/docs/api/rest/exec-runs) · [Managed sandboxes](/docs/extend/managed-sandboxes)

---

## Concepts

| Term | Description |
|------|-------------|
| **Sandbox** | A workspace record for one managed VM. The API places it on a free worker of a platform engine, mints a key for the box, and handles start, stop, restarts and billing. |
| **Remote** | Created with the sandbox and kept across restarts. The box registers as this remote. |
| **Generation** | Goes up on every start, including automatic restarts. Each generation boots on a fresh disk with a new box key; the remote id stays the same. |

Sandboxes run only on platform engines that report the `sandboxes` [engine capability](/docs/api/rest/engines#engine-capabilities), never on your private engines.

Inside the box, `INFERENCE_API_URL` points at the API. The box key reaches the box as a bound secret for the API's host only: the box holds a placeholder, and the engine's egress gateway puts the key on requests to the API.

---

## Lifecycle

| `status` | Meaning |
|----------|---------|
| `pending` | Wants to run; waiting for worker capacity, credit, or a restart backoff |
| `starting` | Sent to the engine |
| `running` | The VM is up (its remote may still be connecting) |
| `stopping` | Stop sent |
| `stopped` | Box stopped; the record and remote remain |
| `failed` | Gave up; see `status_reason` |

| `desired_state` | Set by |
|-----------------|--------|
| `running` | Create, `POST /sandboxes/{id}/start`, or an automatic restart |
| `stopped` | `POST /sandboxes/{id}/stop` |
| `deleted` | `DELETE /sandboxes/{id}` |

`status_reason` values include `no capacity`, `out of credit` and `the engine went away`. A box that ends on its own, or whose remote stays disconnected for 3 minutes, restarts after 5 s, 30 s, then 2 min, at most 3 times in 10 minutes; after that it is `failed`.

---

## Billing

A running box is charged to the owner's balance every minute, at the leased worker's price, for the time it is healthy: its remote connected and heartbeating. Time the remote is away (no heartbeat for a minute) or disconnected is not billed. With no balance, a new generation does not start (`status_reason` `out of credit`).

---

## Create sandbox

`POST /sandboxes`

Requires `sandboxes:write`. Creates the sandbox and its remote, with `desired_state` `running`. A server with no box image configured answers `409` `sandboxes are not set up on this server (no SANDBOX_IMAGE)`.

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Display name |
| `vcpus` | integer | Default 2, max 8 |
| `memory_mib` | integer | Default 4096, max 16384 |
| `disk_mib` | integer | Default 10240, max 51200 |

```bash
curl -X POST https://api.inference.sh/sandboxes \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"dev-box"}'
```

Returns the [sandbox](#sandbox-fields).

---

## List sandboxes

`GET /sandboxes` or `POST /sandboxes/list`

Requires `sandboxes:read`. Cursor-paginated like other list routes.

---

## Get sandbox

`GET /sandboxes/{id}`

Requires `sandboxes:read`.

---

## Start sandbox

`POST /sandboxes/{id}/start`

Requires `sandboxes:write`. Sets `desired_state` to `running`. A stopped or failed sandbox goes back to `pending` and boots on a new disk as the same remote. A start asked for while `stopping` runs when the stop finishes.

---

## Stop sandbox

`POST /sandboxes/{id}/stop`

Requires `sandboxes:write`. Stops the VM and drops its disk. The sandbox and its remote stay, so you can start it again.

---

## Delete sandbox

`DELETE /sandboxes/{id}`

Requires `sandboxes:write`. Stops a live box, then removes the sandbox and its remote.

---

## Sandbox fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Sandbox id |
| `user_id`, `team_id` | string | Owner |
| `name` | string | Display name |
| `image` | string | Box image the platform boots |
| `vcpus`, `memory_mib`, `disk_mib` | integer | Size |
| `desired_state` | string | `running`, `stopped` or `deleted` |
| `status` | string | See [Lifecycle](#lifecycle) |
| `status_reason` | string | Why it is `pending` or `failed` |
| `generation` | integer | Starts so far, restarts included |
| `engine_id`, `worker_id` | string | Placement of the current generation; empty while pending or stopped |
| `remote_id` | string | The remote the box connects as |
| `started_at`, `stopped_at` | string | Timestamps, when set |

---

## SDKs

Neither SDK has a `client.sandboxes` namespace; call the routes above. `SandboxDTO`, `CreateSandboxRequest`, the `SandboxStatus` and `SandboxDesiredState` values, `EngineCapabilitySandboxes` and `ResourceFeatureSandboxes` ship in the next JavaScript and Python SDK releases.
