> ## Documentation Index
> Fetch the complete documentation index at: https://boxrouter.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a Sandbox

> Create a sandbox with the size and lifetime your job needs, reuse it by ID from any process, and destroy it.

A sandbox is one machine on one provider account. It has its own files and processes. Create it
once, keep its ID, and send it work from any process until you destroy it. By default,
BoxRouter destroys it at or after the end of its lifetime; you can request a pause instead. The
[lifecycle guide](https://boxrouter.ai/docs/lifecycle.md#lifetime) explains the timing.

**Before You Begin:** `BOXROUTER_API_KEY` set and a client installed, as in the [quickstart](https://boxrouter.ai/docs/quickstart.md).

## Create It

Ask for what the job needs. This sandbox needs at least 2 CPUs and 2 GiB of memory and may run
for 15 minutes. Its label lets you filter on it later.

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    box = client.sandboxes.create(
        resources={"cpu_min": 2, "memory_min_mib": 2048},
        lifetime_seconds=900,
        labels={"job": "nightly-report"},
    )
    print(box.id)
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const box = await client.sandboxes.create({
    resources: { cpu_min: 2, memory_min_mib: 2048 },
    lifetime_seconds: 900,
    labels: { job: 'nightly-report' },
  });
  console.log(box.id);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter create \
  --resources '{"cpu_min": 2, "memory_min_mib": 2048}' \
  --lifetime-seconds 900 \
  --labels '{"job": "nightly-report"}'
```

```bash HTTP theme={"theme":"css-variables"}
API=${BOXROUTER_BASE_URL:-https://api.boxrouter.ai/api/v1}
AUTH="Authorization: Bearer $BOXROUTER_API_KEY"

curl -sSf -X POST "$API/sandboxes" -H "$AUTH" -H "Content-Type: application/json" -d '{
  "resources": {"cpu_min": 2, "memory_min_mib": 2048},
  "lifetime_seconds": 900,
  "labels": {"job": "nightly-report"}
}' | jq -er '.id | strings | select(length > 0)'
```

Output

```text theme={"theme":"css-variables"}
34209922-dbe0-4119-bf8e-a1e20bef55c6
```

Every BoxRouter call takes this ID. The provider's own ID for the same machine is a different
value.

## What You Ask for and What BoxRouter Decides

Every field is optional. An empty request gets the standard image, the baseline capabilities
(lifecycle, buffered commands and files) and the default lifetime. A capability is something a route may not offer, such as pausing. The fields you set say what
the sandbox must have:

| Field | What It Sets |
| - | - |
| `resources` | Minimum CPU, memory and disk, and a region. |
| `lifetime_seconds`, `at_end` | How long the sandbox may run (300 seconds when unset), and whether it is destroyed or paused when its lifetime ends. See [lifecycle](https://boxrouter.ai/docs/lifecycle.md). |
| `requirements` | Capabilities beyond the baseline, by name, such as `pause_resume` or `previews`. |
| `image`, `env` | The image the sandbox starts from and its environment variables. See [images](https://boxrouter.ai/docs/images.md). |
| `labels` | Your own name-value strings. They come back on the sandbox and work as a list filter. |
| `preferences` | Providers to prefer or exclude. [Routing](https://boxrouter.ai/docs/routing.md) explains them. |

[Create a sandbox](https://boxrouter.ai/docs/reference/create.md) in the API reference lists every field and its limits.

Name a capability that needs no settings in a list, as in `requirements=["pause_resume"]`. Give one with settings as an object,
as in `{"previews": {"access": "public", "ports": [3000]}}`.

BoxRouter decides the rest and records it on the sandbox:

| Field | What It Tells You |
| - | - |
| `route` | The provider and account BoxRouter chose, and the routes it tried first. |
| `contract` | What you were granted: capabilities under `gates`, the actual size under `resources`. |
| `lifetime` | The earliest time the sandbox can end (`ends_at`) and the longest lifetime its route allows (`max_seconds`). The lifetime is 300 seconds unless you set `lifetime_seconds`. |

A resource minimum is the smallest size you accept, so you can get more. Read `contract.resources` for the size you got.

Credits can pay only for a sandbox of a known size. If you pay with Credits and name no size on a route with no default size, the sandbox gets 2 CPUs and 4 GiB.

BoxRouter never relaxes what you asked for to find a route. If no route can meet the request, the
create request fails with `no_eligible_route` and nothing is created. Its `fix` names the nearest change
that would work, and `details.nearest` lists each such change with the routes it would admit.

A sandbox can use only the capabilities its `requirements` named. Calling `pause` on a sandbox created without
`pause_resume` fails with `not_named` and leaves the sandbox unchanged. The error's
`details.requirements` is the line to add when you create the next one.

A sandbox stays on the provider and account it was created on. Creating the next sandbox
elsewhere does not move this one or its files; a [Volume](https://boxrouter.ai/docs/volumes.md) carries files between
sandboxes.

## Reuse It from Another Process

Any process with an API key from the same workspace can pick the sandbox up by ID. Enter the
printed ID when prompted; this exports `SANDBOX_ID` for the examples below.

```bash theme={"theme":"css-variables"}
printf 'Sandbox ID: '; read -r SANDBOX_ID && export SANDBOX_ID
```

The CLI and HTTP examples use `jq`; the HTTP calls reuse `API` and `AUTH` from the step that creates the sandbox.

```python Python theme={"theme":"css-variables"}
import os
from boxrouter import BoxRouter

with BoxRouter() as client:
    box = client.sandboxes.get(os.environ["SANDBOX_ID"])
    print(box.state)
    print(box.exec("nproc").stdout, end="")
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const box = await client.sandboxes.get(process.env.SANDBOX_ID!);
  console.log(box.state);
  process.stdout.write((await box.exec('nproc')).stdout);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  boxrouter get "$SANDBOX_ID" | jq -er '.state | strings | select(length > 0)'
  boxrouter exec "$SANDBOX_ID" nproc
)
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -euo pipefail
  API=${BOXROUTER_BASE_URL:-https://api.boxrouter.ai/api/v1}
  AUTH="Authorization: Bearer $BOXROUTER_API_KEY"
  curl -sSf "$API/sandboxes/$SANDBOX_ID" -H "$AUTH" | jq -er '.state | strings | select(length > 0)'
  curl -sSf "$API/sandboxes/$SANDBOX_ID/commands" -H "$AUTH" \
    -H "Content-Type: application/json" -d '{"command": "nproc"}' |
    jq -ej '
      if .exit_code == 0 and (.stdout | type == "string") then .stdout
      else error("Command failed or returned invalid output")
      end
    '
)
```

Output

```text theme={"theme":"css-variables"}
running
2
```

`get` reads the sandbox's state without resuming it or extending its lifetime; [state timestamps](https://boxrouter.ai/docs/lifecycle.md#states)
explain the response. The handle shows the sandbox's fields as properties, such as `box.state`, `box.lifetime`,
and `box.cost`. They hold the last response; `box.refresh()` reads the sandbox again.

A command on an ended sandbox fails with `sandbox_ended`. A second destroy of a destroyed sandbox returns success.

The [sandbox list](https://boxrouter.ai/docs/reference/list-sandboxes.md) filters by `label`, state, and provider. It shows
the last recorded state without asking the provider. Fetch one sandbox to request a current observation.

## Destroy It

These examples use the same `SANDBOX_ID`.

```python Python theme={"theme":"css-variables"}
import os
from boxrouter import BoxRouter

with BoxRouter() as client:
    box = client.sandboxes.get(os.environ["SANDBOX_ID"])
    box.destroy()
    print(box.state)
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const box = await client.sandboxes.get(process.env.SANDBOX_ID!);
  await box.destroy();
  console.log(box.state);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  boxrouter destroy "$SANDBOX_ID" | jq -er '.state | strings | select(length > 0)'
)
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -euo pipefail
  API=${BOXROUTER_BASE_URL:-https://api.boxrouter.ai/api/v1}
  AUTH="Authorization: Bearer $BOXROUTER_API_KEY"
  curl -sSf -X DELETE "$API/sandboxes/$SANDBOX_ID" -H "$AUTH" | jq -er '.state | strings | select(length > 0)'
)
```

Output

```text theme={"theme":"css-variables"}
destroyed
```

Destroying ends the sandbox's compute. An attached [Volume](https://boxrouter.ai/docs/volumes.md) survives, so keep files you need
there. Volume storage is free during launch, up to the [workspace limit](https://boxrouter.ai/docs/volumes.md#what-it-holds).

`DELETE` always answers `202` with the sandbox. Its `state` is `destroyed` when the provider confirmed
within the request. It is `destroying`, with an `operation_id`, while work remains; BoxRouter finishes the
destroy without you. A repeat `DELETE` answers `202` with the same sandbox and does not call the
provider a second time.

For a sandbox that lives inside one block of code, let the client destroy it. Use a Python `with`
block or a TypeScript `finally`, as in the [quickstart](https://boxrouter.ai/docs/quickstart.md). Leaving the block sends
the destroy and updates the handle, so `box.state` reads the result; it does not prove the destroy finished. If cleanup raises, keep the sandbox ID from
the error and follow [Recover from Errors](https://boxrouter.ai/docs/errors.md).

## Next Steps

> [card] [How a Sandbox Runs and Ends](https://boxrouter.ai/docs/lifecycle.md)
>
> Every state, what it keeps and bills, and which clock stops what.

> [card] [Run Commands](https://boxrouter.ai/docs/commands.md)
>
> Exit codes, timeouts, and output limits.

> [card] [How Routing Works](https://boxrouter.ai/docs/routing.md)
>
> How BoxRouter picks the provider.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.