# Sandboxes

A sandbox is a persistent cloud computer for one agent: a full Firecracker microVM with its own kernel, disk, IP address and network namespace. It keeps its state between sessions and costs nothing for compute while paused.

> `TaskCloud` is in private beta. Latency numbers on this page are targets, not guarantees.

## What is inside

| Property | Value |
| --- | --- |
| OS | Ubuntu 24.04 LTS, x86_64, own Linux kernel |
| Users | `user` (home `/home/user`, passwordless `sudo`); `root` is allowed |
| Init | real systemd: services you enable come back after pause/resume and fork |
| Containers | Docker inside the VM |
| Disk | persistent, survives pause, resume and host restarts |
| GPU | none (software rendering only); GPU is on the roadmap |
| Nested virtualization (`/dev/kvm`) | not available; on the roadmap |
| Region | EU only. `auto` (default) places the sandbox on any EU host; `eu-de` and `eu-fi` pin it to a location |

Isolation: each sandbox runs in its own Firecracker jailer, cgroup and network namespace. Sandboxes cannot see each other or the host API. See [Security](../security.md).

## Sizes

| `size` | vCPU | RAM | Included disk | Price per active hour |
| --- | --- | --- | --- | --- |
| `small` | 2 | 4 GB | 20 GB | $0.015 |
| `default` | 4 | 8 GB | 50 GB | $0.030 |
| `large` | 8 | 16 GB | 100 GB | $0.060 |

Billing is per second while the VM runs. Paused sandboxes cost $0 for compute. Set `disk_gb` (20–1000) for more disk; disk above the included amount is billed per GB-month. Details: [Billing](../billing.md).

The desktop (`desktop` template) needs size `default` or larger.

## Create a sandbox

A sandbox starts from exactly one source:

- a **template** (`template`, default `base`), see [Templates](templates.md);
- a **snapshot** (`snapshot_id`), which restores RAM and disk, see [Snapshots and fork](snapshots-and-fork.md);
- an **OCI image** (`image.ref`).

With `wait: true` (the default) the call returns `201` once the sandbox is `running` (target p95 < 500 ms from a template). With `wait: false` it returns `202` in state `creating`.

```ts
import { Client } from "@taskcloud/sdk";

const client = new Client(); // reads TASKCLOUD_API_KEY

const sandbox = await client.sandboxes.create({
  template: "base",
  size: "default",
  name: "user-42",
  labels: { customer: "acme", user: "42" },
  env: { NODE_ENV: "development" },
  auto_pause: { enabled: true, idle_timeout_seconds: 300 },
});
console.log(sandbox.id, sandbox.state); // sbx_2fqxnt4bkq7m running
```

```python
from taskcloud import Client

client = Client()  # reads TASKCLOUD_API_KEY

sandbox = client.sandboxes.create(
    template="base",
    size="default",
    name="user-42",
    labels={"customer": "acme", "user": "42"},
    env={"NODE_ENV": "development"},
    auto_pause={"enabled": True, "idle_timeout_seconds": 300},
)
print(sandbox.id, sandbox.state)
```

```bash
curl -sS -X POST https://api.task.cloud/v1/sandboxes \
  -H "Authorization: Bearer $TASKCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-user-42" \
  -d '{"template":"base","size":"default","name":"user-42","labels":{"customer":"acme"}}'
```

```bash
taskcloud create --template base --size default --name user-42 --label customer=acme
```

### Create options

| Field | Default | Notes |
| --- | --- | --- |
| `template` | `base` | name, ID, or `name:version` |
| `snapshot_id` | – | start from a named snapshot |
| `image` | – | `{ "ref": "ghcr.io/acme/env:1.4", "registry_auth": {...} }` |
| `size` | `default` | `small`, `default`, `large` |
| `name` | – | up to 120 chars |
| `labels` | – | up to 32 key/value pairs; filter with `?label=key=value` |
| `env` | – | up to 100 variables, 64 KiB; write-only, never returned |
| `auto_pause` | `{enabled: true, idle_timeout_seconds: 300}` | see [Lifecycle](lifecycle.md) |
| `always_on` | `false` | never auto-pause; `default` size only, monthly price |
| `priority` | `standard` | `batch` = −50%, may wait in `queued` |
| `region` | `auto` | `eu-de`, `eu-fi` |
| `ttl_seconds` | `null` | delete automatically after N seconds |
| `disk_gb` | size's included disk | 20–1000 |
| `ports` | – | up to 20 ports to expose at creation |
| `metadata` | – | free-form JSON up to 16 KiB, returned as-is |
| `wait` | `true` | ignored for `priority: "batch"` |

Send an `Idempotency-Key` header to make retries safe: replaying the same key with the same body returns the original sandbox.

## Inspect, list, update, delete

```ts
const sbx = await client.sandboxes.get("sbx_2fqxnt4bkq7m"); // does not wake a paused sandbox

for await (const s of client.sandboxes.list({ state: ["running", "paused"], label: ["customer=acme"] })) {
  console.log(s.id, s.state, s.last_activity_at);
}

await sbx.update({ name: "user-42-main", labels: { customer: "acme", tier: "pro" } });
await sbx.delete();
```

```python
sbx = client.sandboxes.get("sbx_2fqxnt4bkq7m")
for s in client.sandboxes.list(state=["running", "paused"], label=["customer=acme"]):
    print(s.id, s.state)
sbx.update(name="user-42-main")
sbx.delete()
```

- `GET /sandboxes` is paginated (`limit`, `cursor`) and filters by `state`, `label`, `name_prefix` and `template_id`.
- `PATCH /sandboxes/{sandbox_id}` changes `name`, `labels`, `auto_pause`, `always_on`, `ttl_seconds` and `metadata`.
- `DELETE /sandboxes/{sandbox_id}` returns `202`. The sandbox moves to `deleting`, then `deleted`. Its disk and automatic pause snapshot are removed. Named snapshots are kept.

## The sandbox object

Key fields of `Sandbox` (full schema in the [API reference](../api-reference.md)):

| Field | Meaning |
| --- | --- |
| `id` | `sbx_...` |
| `state` | `queued`, `creating`, `running`, `pausing`, `paused`, `resuming`, `deleting`, `deleted`, `error` |
| `size`, `vcpus`, `memory_mb`, `disk_gb` | resources |
| `template_id`, `snapshot_id`, `image`, `forked_from` | where it came from |
| `auto_pause`, `always_on`, `priority`, `ttl_seconds`, `expires_at` | lifecycle settings |
| `url_template` | `https://a2b3c4d5e6f7-{port}.taskhost.cloud` |
| `ports` | exposed ports |
| `desktop` | `true` if the sandbox has a desktop |
| `error` | `{code, message}` when `state` is `error` |
| `created_at`, `started_at`, `paused_at`, `last_activity_at` | timestamps |

## One sandbox per end user

The most common pattern for agent platforms: give each of your users their own sandbox, label it with your user ID, and let auto-pause stop billing while the user is away. Create a restricted API key per user with `restrict.labels` so that a leaked key only reaches that user's sandboxes. See [Security](../security.md).

## Related

- [Lifecycle](lifecycle.md): states, auto-pause, wake on request, billing per state
- [Snapshots and fork](snapshots-and-fork.md)
- [Exec and files](../guides/exec-and-files.md)
- [Networking](../guides/networking.md)
- [Limits](../limits.md)
