> ## 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.

# Keep Files Across Sandboxes

> Write files in one sandbox, destroy it, and read them in the next, on the same provider or another.

A Volume keeps one folder, `/workspace` unless you attach it elsewhere, after its sandbox is gone. Processes, memory, connections, and everything outside that folder are lost. Volumes can be switched off for a deployment or workspace. Creating a volume, or a sandbox with a volume, then fails with `capability_unavailable` (409); the `fix` says Volumes are not enabled. Nothing starts.

If the first `create` below succeeds, Volumes are on. A privacy-mode workspace fails with `privacy_persistence_conflict` instead.

![Files on a Local Disk: your code runs a sandbox on Provider A, and its files sit on Provider A's local disk. Files in a Volume: your code calls BoxRouter, which runs the sandbox on Provider A or Provider B, and both mount the same Volume.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/shared-volume-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=07051fe0beaa6ac48a34be1fd576078f)

![Files on a Local Disk: your code runs a sandbox on Provider A, and its files sit on Provider A's local disk. Files in a Volume: your code calls BoxRouter, which runs the sandbox on Provider A or Provider B, and both mount the same Volume.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/shared-volume-mobile-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=97ddf0094c44e8f49e0b06870ff4d103)

A local disk ends with its sandbox; a Volume outlives it.

The volume saves that folder as checkpoints. Destroying a `write` sandbox saves one; call `save_volume` to save one on demand, as [History](#history) shows.

By default, a new sandbox starts from the volume's **head** checkpoint.

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

![Sandbox 1, attached with access write, writes /workspace into the volume; destroying it saves a checkpoint. Sandbox 2 saves another with save\_volume. A branch sandbox starts from a checkpoint with files of its own, and the head stays unchanged. A second writer is refused with volume\_busy until the first sandbox releases the volume.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/volume-checkpoints-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=b73ac2f638304fa3349206157084b87c)

![Sandbox 1, attached with access write, writes /workspace into the volume; destroying it saves a checkpoint. Sandbox 2 saves another with save\_volume. A branch sandbox starts from a checkpoint with files of its own, and the head stays unchanged. A second writer is refused with volume\_busy until the first sandbox releases the volume.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/volume-checkpoints-mobile-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=3f847001da18d5ea6be4d5bbe5440181)

One writer at a time adds checkpoints to the volume; a branch starts from a checkpoint and leaves the head alone. Processes, memory and everything outside the folder are lost.

| Section | What It Covers |
| - | - |
| [Write in one sandbox, read in the next](#write-in-one-sandbox-read-in-the-next) | Create a volume, attach it, and read its files from a later sandbox. |
| [Continue on another provider](#continue-on-another-provider) | Read the same files from a sandbox on a different provider. |
| [What to expect](#what-to-expect) | Credentials, one writer, and the wait between writers. |
| [What it holds](#what-it-holds) | The storage limit and what happens past it. |
| [Move files in and out](#move-files-in-and-out) | Upload and download without a sandbox. |
| [History](#history) | Save a checkpoint on demand, start a sandbox from one, and choose `read`, `write`, `shared`, or `branch` access. |
| [Reading many small files](#reading-many-small-files) | Why a large scan is slow, and the fix. |
| [Delete a volume](#delete-a-volume) | Erase the files and every checkpoint. |

## Write in One Sandbox, Read in the Next

Create a sandbox with only the volume, write your files, then destroy the sandbox. Destruction flushes the folder and saves a checkpoint. Creating the next sandbox on the same volume waits for the first one to release it, so the next call needs no polling.

Use create for the first sandbox: it can prepare an image, while matching a volume requires an image that is already ready.

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

with BoxRouter() as client:
    volume = client.volumes.create(name="project-files")
    with client.sandboxes.create(volume={"id": volume.id}) as first:
        first.exec("echo 'written in the first sandbox' > /workspace/notes.txt")
    with client.sandboxes.create(volume={"id": volume.id}) as second:
        print(second.exec("cat /workspace/notes.txt").stdout, end="")
    client.volumes.delete(volume.id)
```

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

const client = new BoxRouter();
try {
  const volume = await client.volumes.create({ name: 'project-files' });
  const first = await client.sandboxes.create({ volume: { id: volume.id } });
  await first.exec("echo 'written in the first sandbox' > /workspace/notes.txt");
  await first.destroy();
  const second = await client.sandboxes.create({ volume: { id: volume.id } });
  process.stdout.write((await second.exec('cat /workspace/notes.txt')).stdout);
  await second.destroy();
  await client.volumes.delete(volume.id);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
VOLUME_ID=$(boxrouter volumes create project-files | jq -r .id)
FIRST=$(boxrouter create --volume "$VOLUME_ID")
boxrouter exec "$FIRST" "echo 'written in the first sandbox' > /workspace/notes.txt"
boxrouter destroy "$FIRST"
SECOND=$(boxrouter create --volume "$VOLUME_ID")
boxrouter exec "$SECOND" "cat /workspace/notes.txt"
boxrouter destroy "$SECOND"
boxrouter volumes delete "$VOLUME_ID"
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -eo pipefail
  API="${BOXROUTER_BASE_URL:-https://api.boxrouter.ai/api/v1}"
  AUTH="Authorization: Bearer $BOXROUTER_API_KEY"
  JSON="Content-Type: application/json"
  VOLUME_ID=$(curl -sSf "$API/volumes" -H "$AUTH" -H "$JSON" -d '{"name":"project-files"}' | jq -r .id)
  FIRST_ID=$(curl -sSf "$API/sandboxes" -H "$AUTH" -H "$JSON" -d "{\"volume\":{\"id\":\"$VOLUME_ID\"}}" | jq -r .id)
  curl -sSf "$API/sandboxes/$FIRST_ID/commands" -H "$AUTH" -H "$JSON" \
    -d '{"command":"echo '"'"'written in the first sandbox'"'"' > /workspace/notes.txt"}' >/dev/null
  curl -sSf -o /dev/null -X DELETE "$API/sandboxes/$FIRST_ID" -H "$AUTH"
  SECOND_ID=$(curl -sSf "$API/sandboxes" -H "$AUTH" -H "$JSON" \
    -d "{\"volume\":{\"id\":\"$VOLUME_ID\"}}" | jq -r .id)
  curl -sSf "$API/sandboxes/$SECOND_ID/commands" -H "$AUTH" -H "$JSON" -d '{"command":"cat /workspace/notes.txt"}' | jq -j .stdout
  curl -sSf -o /dev/null -X DELETE "$API/sandboxes/$SECOND_ID" -H "$AUTH"
  curl -sSf -o /dev/null -X DELETE "$API/volumes/$VOLUME_ID" -H "$AUTH"
)
```

Output

```text theme={"theme":"css-variables"}
written in the first sandbox
```

## Continue on Another Provider

Any sandbox that can mount the volume can read it, so the next sandbox does not have to run where the last one did. This example asks the second sandbox to avoid the first one's provider.

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

with BoxRouter() as client:
    volume = client.volumes.create(name="project-files")
    with client.sandboxes.create(volume={"id": volume.id}) as first:
        first.exec("echo 'written on the first provider' > /workspace/notes.txt")
    with client.sandboxes.create(
        volume={"id": volume.id}, preferences={"ignore": [first.route.provider]}
    ) as second:
        print(second.exec("cat /workspace/notes.txt").stdout, end="")
    client.volumes.delete(volume.id)
```

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

const client = new BoxRouter();
try {
  const volume = await client.volumes.create({ name: 'project-files' });
  const first = await client.sandboxes.create({ volume: { id: volume.id } });
  await first.exec("echo 'written on the first provider' > /workspace/notes.txt");
  await first.destroy();
  const second = await client.sandboxes.create({
    volume: { id: volume.id }, preferences: { ignore: [first.route.provider] },
  });
  process.stdout.write((await second.exec('cat /workspace/notes.txt')).stdout);
  await second.destroy();
  await client.volumes.delete(volume.id);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
VOLUME_ID=$(boxrouter volumes create project-files | jq -r .id)
FIRST=$(boxrouter create --volume "$VOLUME_ID")
FIRST_PROVIDER=$(boxrouter api sandboxes get "$FIRST" | jq -er '.route.provider')
boxrouter exec "$FIRST" "echo 'written on the first provider' > /workspace/notes.txt"
boxrouter destroy "$FIRST"
SECOND=$(boxrouter create --volume "$VOLUME_ID" --preferences "{\"ignore\":[\"$FIRST_PROVIDER\"]}")
boxrouter exec "$SECOND" "cat /workspace/notes.txt"
boxrouter destroy "$SECOND"
boxrouter volumes delete "$VOLUME_ID"
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -eo pipefail
  API="${BOXROUTER_BASE_URL:-https://api.boxrouter.ai/api/v1}"
  AUTH="Authorization: Bearer $BOXROUTER_API_KEY"
  JSON="Content-Type: application/json"
  VOLUME_ID=$(curl -sSf "$API/volumes" -H "$AUTH" -H "$JSON" -d '{"name":"project-files"}' | jq -r .id)
  FIRST=$(curl -sSf "$API/sandboxes" -H "$AUTH" -H "$JSON" -d "{\"volume\":{\"id\":\"$VOLUME_ID\"}}")
  FIRST_ID=$(jq -r .id <<<"$FIRST")
  curl -sSf "$API/sandboxes/$FIRST_ID/commands" -H "$AUTH" -H "$JSON" \
    -d '{"command":"echo '"'"'written on the first provider'"'"' > /workspace/notes.txt"}' >/dev/null
  curl -sSf -o /dev/null -X DELETE "$API/sandboxes/$FIRST_ID" -H "$AUTH"
  SECOND_ID=$(curl -sSf "$API/sandboxes" -H "$AUTH" -H "$JSON" \
    -d "{\"volume\":{\"id\":\"$VOLUME_ID\"},\"preferences\":{\"ignore\":[$(jq '.route.provider' <<<"$FIRST")]}}" | jq -r .id)
  curl -sSf "$API/sandboxes/$SECOND_ID/commands" -H "$AUTH" -H "$JSON" -d '{"command":"cat /workspace/notes.txt"}' | jq -j .stdout
  curl -sSf -o /dev/null -X DELETE "$API/sandboxes/$SECOND_ID" -H "$AUTH"
  curl -sSf -o /dev/null -X DELETE "$API/volumes/$VOLUME_ID" -H "$AUTH"
)
```

Output

```text theme={"theme":"css-variables"}
written on the first provider
```

Not every route can mount a volume. Some routes block the network path to the volume's storage, and BoxRouter skips a route it has not proven end to end. If no remaining route can mount the volume, `match` and `create` fail with `volume_unavailable` (409). Sandboxes without a volume still start on those routes.

An explicit provider outside the list fails the same way, so leave `preferences.only` unset to let BoxRouter pick. [Provider support](https://boxrouter.ai/docs/provider-support.md) explains how routes differ.

## What to Expect

| Question | Answer |
| - | - |
| Who can write? | One `write` sandbox at a time. A second is refused with `volume_busy` (409), and `details.writer_sandbox_id` names the holder. |
| What does destroy do? | It returns once BoxRouter accepts it. BoxRouter finishes ending the sandbox and releasing the volume without you. |
| What does create do while the writer is ending? | It waits for the release, at most 120 seconds, then creates the sandbox. Deleting the volume waits the same way. Measured handoffs take 12 to 59 seconds. |
| What if the release takes longer? | `create` fails with `volume_busy` and outcome `not_started`, so nothing was created and you can send it again. |
| What if many creates wait on ending writers at once? | A server runs a limited number of waits at once. A further `create` or volume delete fails at once with `volume_busy` and outcome `not_started`, and its fix says to retry in a few seconds. |
| What if the writer is still running? | `create` fails at once with `volume_busy`. Destroy the writer sandbox, then create again. |
| Can a reader attach next to a writer? | No. `read`, `shared`, and `branch` attaches are refused with `volume_busy` while a `write` sandbox holds the volume, and a `write` attach is refused while a `read` or `shared` sandbox is attached. |
| What can a sandbox read and write? | Each sandbox's mount has its own credential. A `read` mount is read-only, and the credential is revoked when the sandbox releases the volume. |

## What It Holds

| Limit | Value | What Happens Past It |
| - | - | - |
| Storage per workspace | 10 GiB, free during launch | Past it, adding bytes is refused with `resource_limit` (413); reading is not. See the list below |
| Volumes per workspace | 20 | `resource_limit` (413) on create; delete a volume before creating more |
| One uploaded file | 256 GiB, and never more than the workspace storage left | `resource_limit` (413); with the 10 GiB storage limit, the storage limit is the one you reach |
| Saved checkpoints | `limits.max_checkpoints` on the volume | A save over the limit removes the oldest automatic (pause or destroy) checkpoint first, and the oldest saved one only when no automatic one is left; the head and a checkpoint a sandbox is attached from are kept |
| Writers | One `write` sandbox at a time | `volume_busy` |

Files are written to the volume as the sandbox writes them, and a later sandbox sees them without a save.

While a sandbox has the volume attached with `write` or `shared` access, the size shown for the volume and the storage you are billed for refresh about every few minutes. Past the storage limit, writes already in progress are not stopped.

A workspace past its storage limit can still read what it has, and nothing it wrote is deleted:

* **Allowed:** attaching a volume with `read` or `shared` access, resuming a sandbox that already holds a volume, and downloading or listing files. These add no bytes, so they do not check usage.
* **Refused with `resource_limit` (413):** attaching with `write` or `branch` access, saving a checkpoint, and starting an upload. A pause or destroy that has to save a checkpoint first is refused the same way, and the sandbox keeps running. Files already written stay on the volume. Delete volumes you no longer need and try again, or destroy with `discard_unsaved=true` to end the sandbox without a checkpoint.

If BoxRouter cannot read usage at that moment, the checkpoint that a pause or destroy takes retries the reading once. If it is still unavailable, the call fails with `capability_unavailable` (503) and the sandbox keeps running. Files already written stay on the volume; retry shortly. A save you request does not retry on its own; save again.

An abrupt kill loses writes the sandbox had not yet flushed. `fsync` files you cannot afford to lose before anything that might kill the sandbox, or save a checkpoint.

A workspace in transient privacy mode cannot create or attach volumes, because that mode forbids persistence. The request fails with `privacy_persistence_conflict`.

## Move Files in and Out

You can upload and download without a sandbox. A directory upload preserves its relative paths. A
download path ending in `/` selects everything under that path. Delete a file the same way, or everything
under a path ending in `/`, to free space without deleting the volume. Deleting a path that does not
exist succeeds, so a repeated request is safe.
After a delete, BoxRouter reads the volume's size once. The storage system can take a short while to report the freed space, so the size shown for the volume may still be the old one; it is read again at the next attach, save, or upload.

An upload session, and the part URLs it returns, stay valid for six hours plus about one hour for each 70 GiB of the file's declared size, up to 72 hours. `expires_at` in the response says when. That allows for a sustained 20 MiB/s; a slower link needs a smaller file or a faster connection. Expired sessions are aborted, and you can start a new upload.

A directory upload sends up to four files at once, and each file sends up to four parts at once. Set
`file_concurrency` (Python) or `fileConcurrency` (TypeScript) to change the number of files, and
`concurrency` to change the number of parts per file; both must be at least 1. The progress callback
receives the bytes sent and the total bytes of every file. After the first failure the SDK starts no
more files and aborts the uploads still sending, so files that finished before the failure stay on the
volume. The Python call returns the uploaded files in path order.

The SDKs can retry a Volume upload part after a network or server failure; resending replaces the
part. They can retry upload completion with the same idempotency key. If an upload still fails,
check its outcome and target before starting another.

You cannot upload or delete files while a sandbox is attached with `write`, a save is running, or an upload is
open, and you cannot attach a `write` sandbox while an upload is open. Each is refused with `volume_busy`.

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

with BoxRouter() as client:
    volume = client.volumes.create(name="uploads")
    client.volumes.upload(volume.id, "./report.csv", "incoming/report.csv")
    client.volumes.download(volume.id, "incoming/report.csv", "./report-copy.csv")
    for file in client.volumes.list_files(volume.id, prefix="incoming/"):
        print(file.path, file.size)
    client.volumes.delete_file(volume.id, "incoming/report.csv")
    client.volumes.delete(volume.id)
```

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

const client = new BoxRouter();
try {
  const volume = await client.volumes.create({ name: 'uploads' });
  await client.volumes.upload(volume.id, './report.csv', 'incoming/report.csv');
  await client.volumes.download(volume.id, 'incoming/report.csv', './report-copy.csv');
  for await (const file of client.volumes.listFiles(volume.id, { prefix: 'incoming/' })) {
    console.log(file.path, file.size);
  }
  await client.volumes.deleteFile(volume.id, 'incoming/report.csv');
  await client.volumes.delete(volume.id);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
VOLUME_ID=$(boxrouter volumes create uploads | jq -r .id)
boxrouter volumes put "$VOLUME_ID" ./report.csv incoming/report.csv
boxrouter volumes get "$VOLUME_ID" incoming/report.csv ./report-copy.csv
boxrouter volumes ls "$VOLUME_ID" incoming/
boxrouter volumes rm "$VOLUME_ID" incoming/report.csv
boxrouter volumes delete "$VOLUME_ID"
```

Each example uploads a local `report.csv`, downloads it again, lists the folder, deletes the file, and deletes its own volume. To keep the volume for a sandbox, leave out the last line.

Uploaded files appear under `/workspace/` in the next sandbox. An upload does not save a checkpoint.

## History

A volume saves its folder as checkpoints. Destroying a `write` sandbox saves one. To save one on demand, call `save_volume`; it returns the checkpoint number. Pass that number as `checkpoint` when you create a sandbox to start from that state. This does not roll back the volume itself.

A sandbox can attach a checkpoint only with the image and architecture the checkpoint was saved with. Any other image or architecture fails with `volume_incompatible` (409) before anything starts. The `fix` names the image and architecture to use; create the sandbox again with them.

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

with BoxRouter() as client:
    volume = client.volumes.create(name="project-files")
    with client.sandboxes.create(volume={"id": volume.id}) as box:
        saved = box.save_volume(message="Before dependency upgrade")
    with client.sandboxes.create(volume={"id": volume.id, "checkpoint": saved.checkpoint}):
        print(saved.checkpoint)
    client.volumes.delete(volume.id)
```

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

const client = new BoxRouter();
try {
  const volume = await client.volumes.create({ name: 'project-files' });
  const box = await client.sandboxes.create({ volume: { id: volume.id } });
  const saved = await box.saveVolume({ message: 'Before dependency upgrade' });
  await box.destroy();
  const restored = await client.sandboxes.create({ volume: { id: volume.id, checkpoint: saved.checkpoint } });
  await restored.destroy();
  await client.volumes.delete(volume.id);
  console.log(saved.checkpoint);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
VOLUME_ID=$(boxrouter volumes create project-files | jq -r .id)
SANDBOX_ID=$(boxrouter create --volume "$VOLUME_ID")
CHECKPOINT=$(boxrouter volumes save "$VOLUME_ID" \
  --body "{\"sandbox_id\":\"$SANDBOX_ID\",\"message\":\"Before dependency upgrade\"}" | jq -r .checkpoint)
boxrouter destroy "$SANDBOX_ID"
RESTORED=$(boxrouter create --volume "$VOLUME_ID@$CHECKPOINT")
boxrouter destroy "$RESTORED"
boxrouter volumes delete "$VOLUME_ID"
echo "$CHECKPOINT"
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -eo pipefail
  API="${BOXROUTER_BASE_URL:-https://api.boxrouter.ai/api/v1}"
  AUTH="Authorization: Bearer $BOXROUTER_API_KEY"
  JSON="Content-Type: application/json"
  VOLUME_ID=$(curl -sSf "$API/volumes" -H "$AUTH" -H "$JSON" -d '{"name":"project-files"}' | jq -r .id)
  SANDBOX_ID=$(curl -sSf "$API/sandboxes" -H "$AUTH" -H "$JSON" -d "{\"volume\":{\"id\":\"$VOLUME_ID\"}}" | jq -r .id)
  CHECKPOINT=$(curl -sSf "$API/volumes/$VOLUME_ID/checkpoints" -H "$AUTH" -H "$JSON" \
    -d "{\"sandbox_id\":\"$SANDBOX_ID\",\"message\":\"Before dependency upgrade\"}" | jq -r .checkpoint)
  curl -sSf -o /dev/null -X DELETE "$API/sandboxes/$SANDBOX_ID" -H "$AUTH"
  RESTORED=$(curl -sSf "$API/sandboxes" -H "$AUTH" -H "$JSON" \
    -d "{\"volume\":{\"id\":\"$VOLUME_ID\",\"checkpoint\":$CHECKPOINT}}" | jq -r .id)
  curl -sSf -o /dev/null -X DELETE "$API/sandboxes/$RESTORED" -H "$AUTH"
  curl -sSf -o /dev/null -X DELETE "$API/volumes/$VOLUME_ID" -H "$AUTH"
  echo "$CHECKPOINT"
)
```

Output

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

### Access Modes

Set `access` on the attachment to say what the sandbox may do with the volume:

* Use `write` (the default) to change the volume's files. Only one `write` sandbox can be attached at a time.
* Use `read` to read files without changing them.
* Use `shared` when several sandboxes need the volume at once.
* Use `branch` to work on files of your own without changing the head.

A new volume needs one `write` attachment or one [upload](#move-files-in-and-out) before it accepts `read` or `shared`. An empty volume has no head. Pass a checkpoint number as `checkpoint` to start from a saved state other than the head.

| Mode | Writes | Starts from |
| - | - | - |
| `write` (default) | This sandbox only | The head, or any checkpoint |
| `read` | Refused with `EROFS` | The head, or a saved branch checkpoint |
| `shared` | Several sandboxes at once; concurrent writes to one file can overwrite each other, and changes can take a moment to appear elsewhere | The head |
| `branch` | Separate files, saved on their own | The head, or any checkpoint |

`read` and `shared` refuse any other historical checkpoint with `capability_unavailable` (409).

On the CLI, `--volume` takes the volume ID alone, `ID:ACCESS` (for example `"$VOLUME_ID:read"`), `ID@CHECKPOINT`, or both as `ID@CHECKPOINT:ACCESS`. Use the JSON form, such as `--volume '{"id":"vol_...","path":"/data"}'`, to set `path` as well.

### Branches

A branch starts from a checkpoint and has its own files. Save it using the branch sandbox's ID; the resulting checkpoint contains only that branch's files.

The volume's head and the source files stay unchanged. Creating a sandbox without `checkpoint` still starts from the head.

Read the branch checkpoint with `access: read` and its number, on any provider that can mount the volume.

> [!WARNING]
>
> Branches share one disk, so a sandbox in one branch can reach the files of the others. A branch is not a security boundary: do not use branches to isolate untrusted code.

## Reading Many Small Files

Reading many small files is slower from a volume than from the sandbox's own disk, because each file is a separate network read. Scans such as `grep -r` or `git status` over a large tree are slow in the same way.

Copy a working set you read heavily onto the sandbox's own disk first.

## Delete a Volume

Delete erases the files and every checkpoint. If the last writer is still ending, delete waits for its release, at most 120 seconds. It is refused with `volume_busy` while a sandbox is still running on the volume, so destroy the sandboxes that use it first.

If storage removal does not finish at once, the volume stays in the list with state `deleting` and keeps counting toward the 20-volume limit. BoxRouter keeps retrying, and you can send the same delete again; it returns `204` once the volume is gone.

The dashboard's Storage page lists and inspects volumes, and Delete is its only action. **In Use By** shows each attached sandbox. Checkpoints and branches are code-only.

See the [Volumes API reference](https://boxrouter.ai/docs/reference/list-volumes.md) for every field.

## Next Steps

> [card] [Choose a Sandbox Image](https://boxrouter.ai/docs/images.md)
>
> Fix software in the image instead.

> [card] [Pause, Resume, and Extend](https://boxrouter.ai/docs/pause-resume.md)
>
> Keep the whole sandbox, memory included.

> [card] [Work with Files](https://boxrouter.ai/docs/files.md)
>
> Move files in and out of one sandbox.


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