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

# Sandboxes

> Every /sandboxes route -- lifecycle, filesystem ops, exec-stream, snapshots, preview, and local tunnel.

## Sandboxes

| Route | What it does |
| - | - |
| `POST /sandboxes` | Boot a sandbox. Body: `name?`, `tags?`, `vcpu_count?`, `mem_size_mib?`, `image_id?`, `drives?`, `rate_limit?` (`{bandwidth_bytes_per_sec?, ops_per_sec?}`, Firecracker's own token-bucket limiter), `egress?` (`{mode: "allow_all"\|"deny_all", allow_cidrs?, deny_cidrs?}` — see [Networking & isolation](/concepts/networking)). A request with no `drives`/`rate_limit` matching a configured pool's image/resources transparently resumes a warm snapshot instead of cold-booting — see [Startup latency & the pre-warmed pool](/architecture/startup-latency); `egress` doesn't disqualify a pool match, it's applied fresh after either way. |
| `GET /sandboxes` | List sandboxes. `?tag.<key>=<value>` filters (repeatable, all must match). |
| `GET /sandboxes/history` | Durable history of every sandbox that ever existed and how it ended (`sqlite`-backed, survives a daemon restart, unlike this list itself). `?live_only=true`, `?limit=<n>`. |
| `GET /sandboxes/by-name/:name` | Resolve a name to a *live* sandbox's id. `409` if the name currently belongs to a held snapshot instead. |
| `POST /sandboxes/get-or-create` | Resolve-or-create by name in one call. Body: `name` (required), `tags?`, `vcpu_count?`, `mem_size_mib?`, `image_id?`, `rate_limit?`, `drives?`, `egress?` (the last five used only if a fresh sandbox is created — resuming an existing snapshot always uses what was recorded on it). |
| `DELETE /sandboxes/:id` | Stop a sandbox. Preserves state as a snapshot by default (`200`, `{"kept": true, "snapshot_id": "..."}`); `?keep=false` fully destroys instead (`204`). See [Sandbox lifecycle](/concepts/sandbox-lifecycle). |
| `POST /sandboxes/:id/exec` | Run a command. Body: `{"command": "...", "args": [...]}`. Returns `stdout`/`stderr`/`exit_code`. |
| `POST /sandboxes/:id/read-file` | Body: `{"path": "..."}`. Returns `{"content_base64": "..."}`. |
| `POST /sandboxes/:id/write-file` | Body: `{"path": "...", "content_base64": "..."}`. |
| `POST /sandboxes/:id/chmod` | Body: `{"path": "...", "mode": <octal-as-int>}`. |
| `POST /sandboxes/:id/chown` | Body: `{"path": "...", "uid": <n>, "gid": <n>}`. |
| `POST /sandboxes/:id/mkdir` | Body: `{"path": "...", "parents"?: <bool>}`. |
| `POST /sandboxes/:id/rename` | Body: `{"from": "...", "to": "..."}`. |
| `POST /sandboxes/:id/copy` | Body: `{"from": "...", "to": "..."}`. |
| `POST /sandboxes/:id/symlink` | Body: `{"target": "...", "link_path": "..."}`. |
| `POST /sandboxes/:id/readlink` | Body: `{"path": "..."}`. Returns `{"target": "..."}`. |
| `POST /sandboxes/:id/truncate` | Body: `{"path": "...", "size": <n>}`. |
| `POST /sandboxes/:id/list-dir` | Body: `{"path": "..."}`. Returns real per-entry metadata: type, permission bits, size, mtime. |
| `GET /sandboxes/:id/pty[?cols=&rows=]` | Upgrades to a WebSocket: a live, bidirectional shell session, distinct from `exec`'s request/response shape. Accepts the token as `?token=` as well as a header, since neither browsers' nor Node's native `WebSocket` constructor can set a custom header. A per-sandbox concurrent-session cap (64) is enforced. |
| `POST /sandboxes/:id/exec-stream` | Starts a command in the background, returns `{"id": "..."}` immediately — the command itself may not even be spawned yet. Attach to the `logs` route below to actually see its output. Not `exec`'s request/response shape: this is for a long-running command you want to watch live, not wait on. |
| `GET /sandboxes/:id/exec-stream` | Lists every streamed session tracked against this sandbox, running or finished: `{"sessions": [{"id", "command", "args", "started_at_unix", "exit_code"}, ...]}` — `exit_code` is `null` while still running. |
| `GET /sandboxes/:id/exec-stream/:session_id/logs` | Upgrades to a WebSocket: replays everything captured so far, then live-tails anything new, until the process exits (one final `"[process exited with code N]"` text message, then the socket closes) or the client disconnects. Callable again any number of times for the same session, including long after it finished — it's just a replay with nothing to tail. Buffered replay frames arrive as binary; the final exit notice arrives as text — a client needs to handle both frame types. Sessions aren't carried across `resume()`/`fork()` and don't survive a daemon restart. |

## Snapshots

| Route | What it does |
| - | - |
| `POST /sandboxes/:id/snapshot` | Pause, snapshot, stop. Returns a snapshot id. |
| `GET /snapshots` | List snapshots. `?source_sandbox_id=<id>` narrows to the one taken from that sandbox; `?parent_snapshot_id=<id>` narrows to whatever was forked/resumed from that snapshot (can be more than one, over time). Each result includes `parent_snapshot_id` — see [Snapshots, resume, and fork](/concepts/snapshots#lineage--what-a-snapshot-came-from-and-what-came-from-it). |
| `POST /snapshots/:id/resume` | Boot a new sandbox from the snapshot, consuming the record. Retires the underlying checkpoint into `GET /snapshots/history` by default rather than deleting it — see [Time-travel restore](/concepts/snapshots#time-travel-restore--going-back-to-an-earlier-checkpoint). `?retain_history=false` opts back into the original, fully-destructive behavior. |
| `POST /snapshots/:id/fork` | Boot a new sandbox from the snapshot **without** consuming it. `409` while an earlier fork is still live. |
| `DELETE /snapshots/:id` | Delete a snapshot outright. `409` while a fork of it is still live. |

### Snapshot history (time-travel restore)

| Route | What it does |
| - | - |
| `GET /snapshots/history` | List retired checkpoints — anything a resume retained instead of deleting. Same `?source_sandbox_id=`/`?parent_snapshot_id=` filters as `GET /snapshots`. |
| `POST /snapshots/history/:id/restore` | Boot a new sandbox from a retired checkpoint. Does **not** consume it — restorable again later. `409` (naming the current holder) while anything live or held shares that checkpoint's frozen network identity. |
| `DELETE /snapshots/history/:id` | Delete a retired checkpoint outright, reclaiming its disk usage (a full guest-memory dump plus a private rootfs copy) — there's no automatic expiry yet. |

## Preview

| Route | What it does |
| - | - |
| `GET/POST/... /sandboxes/:id/preview/:port[/*path]` | Reverse-proxies a full HTTP request to that port inside the sandbox. Accepts the token as `?token=` as well as a header. |

## Local tunnel

The reverse of preview: exposes a service running on the *caller's* machine to code inside the sandbox. See [Local tunnel](/concepts/local-tunnel) and [local tunnel: the mechanism](/internals/local-tunnel).

| Route | What it does |
| - | - |
| `POST /sandboxes/:id/tunnel` | Body: `{"guest_port": <n>}`. Tells the guest agent to start listening on `guest_port`. Returns `{"tunnel_id": "...", "guest_port": <n>}`. |
| `GET /sandboxes/:id/tunnel/:tunnel_id/ws` | Upgrades to a WebSocket: the long-lived relay the caller holds open for the tunnel's whole life, multiplexing every guest-accepted connection over it with a small binary frame. Accepts the token as `?token=` as well as a header, same reason as `/pty` above. |
| `DELETE /sandboxes/:id/tunnel/:tunnel_id` | Tells the guest agent to stop listening and tears down the relay. |

## Example

Writing a file into a sandbox, then reading it back — the [HTTP API overview](/api-reference/http-api/overview) already covers create/exec/stop, so this shows the other half of the Sandboxes surface instead:

```bash theme={null}
curl -si -X POST http://127.0.0.1:7777/sandboxes/12b9dd16-5787-44fa-aceb-5c9bb404b854/write-file \
  -H 'content-type: application/json' \
  -d '{"path":"/tmp/config.json","content_base64":"eyJvayI6dHJ1ZX0="}'
HTTP/1.1 204 No Content

curl -s -X POST http://127.0.0.1:7777/sandboxes/12b9dd16-5787-44fa-aceb-5c9bb404b854/read-file \
  -H 'content-type: application/json' \
  -d '{"path":"/tmp/config.json"}'
{"content_base64":"eyJvayI6dHJ1ZX0="}
```

Snapshotting that sandbox, then resuming it into a new one — `resume` consumes the snapshot, so a second resume of the same id `404`s:

```bash theme={null}
curl -s -X POST http://127.0.0.1:7777/sandboxes/12b9dd16-5787-44fa-aceb-5c9bb404b854/snapshot
{"snapshot_id":"a1e9c3d2-2f0a-4b1e-9c3a-7d5e6f8b9c0d"}

curl -s -X POST http://127.0.0.1:7777/snapshots/a1e9c3d2-2f0a-4b1e-9c3a-7d5e6f8b9c0d/resume
{"id":"f3b2a1c0-9e8d-4c7b-a6f5-e4d3c2b1a0f9"}

curl -si -X POST http://127.0.0.1:7777/snapshots/a1e9c3d2-2f0a-4b1e-9c3a-7d5e6f8b9c0d/resume
HTTP/1.1 404 Not Found
content-type: application/json

{"error":"sandbox not found: a1e9c3d2-2f0a-4b1e-9c3a-7d5e6f8b9c0d"}
```


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