# Snapshots and environments

> Copy a sandbox, save its disk, share it with another team, and start with saved variables

Four ways to start a sandbox that is ready to work, instead of setting it up every time.

## Copy a sandbox

`fork` starts a new sandbox with a copy of the source's disk. The source keeps running. The copy keeps its size, timeout, name and metadata, and is billed like any start: $0.01, then machine time.

```typescript
const { sandbox: copy } = await zavu.sandboxes.fork(sandboxId);
console.log(copy.id, copy.forkedFromSandboxId);
```

```bash
curl -X POST https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/fork \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

The source must be running or stopped, and not ephemeral.

## Save a snapshot

A snapshot freezes a sandbox's disk under a name, so you can start any number of sandboxes from it later. A running sandbox is captured as it is now; a stopped one as it was when it stopped.

```typescript
const { snapshot } = await zavu.sandboxes.snapshots.create({
  sandboxId,
  name: "node-22-with-deps",
});

// The save runs in the background.
let current = snapshot;
while (current.status === "saving") {
  await new Promise((r) => setTimeout(r, 5000));
  ({ snapshot: current } = await zavu.sandboxes.snapshots.retrieve(snapshot.id));
}

// Start from it. The sandbox takes the snapshot's size.
const { sandbox } = await zavu.sandboxes.create({ from: snapshot.id });
```

```bash
curl -X POST https://api.zavu.dev/v1/sandbox-snapshots \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sandboxId": "'$SANDBOX_ID'", "name": "node-22-with-deps"}'

curl https://api.zavu.dev/v1/sandbox-snapshots/$SNAPSHOT_ID \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"

curl -X POST https://api.zavu.dev/v1/sandboxes \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "'$SNAPSHOT_ID'"}'
```

- A snapshot costs $0.07 per started UTC day while you keep it. The first day is charged when you save, and returned if the save fails.
- A team keeps at most 20. Delete one with `zavu.sandboxes.snapshots.delete(id)`; sandboxes already started from it keep their disk.
- Starting from a snapshot that is still saving answers `409 snapshot_not_ready`. An ephemeral sandbox cannot be saved.

## Share a snapshot

A share code lets another team start from your snapshot. They pay their own start and machine time; you keep paying for the snapshot.

```typescript
const { snapshot } = await zavu.sandboxes.snapshots.share(snapshotId);
console.log(snapshot.shareCode); // zsh_k3m4n5p6q7r2s3t4u5v6w7x2y3z4a5b6

// The other team:
await zavu.sandboxes.create({ from: "zsh_k3m4n5p6q7r2s3t4u5v6w7x2y3z4a5b6" });

// Stop sharing:
await zavu.sandboxes.snapshots.revokeShare(snapshotId);
```

```bash
curl -X POST https://api.zavu.dev/v1/sandbox-snapshots/$SNAPSHOT_ID/share \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"

curl -X DELETE https://api.zavu.dev/v1/sandbox-snapshots/$SNAPSHOT_ID/share \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

> **Warning:**
> Whoever holds the code can copy everything on that disk. Share it only with people who may see what is on it, and remove secrets before you save.

Asking again returns the same code until it is revoked. A revoked code, or one of a deleted snapshot, answers `404 not_found` on create, and nothing is charged. Sandboxes already started from it keep their disk.

## Environments

An environment is a named set of variables your project keeps, so you do not send secrets on every create. Values are stored encrypted and never returned: reads show each variable's name and its last four characters.

```typescript
const { environment } = await zavu.sandboxes.environments.create({
  name: "staging",
  env: { DATABASE_URL: "postgres://user:pass@db/app", API_TOKEN: "tok_123" },
});

// Start with it. Variables in `env` win on the same name.
await zavu.sandboxes.create({ environment: "staging", env: { DEBUG: "1" } });

// Add or replace some variables, remove others. The rest keep their values.
await zavu.sandboxes.environments.update(environment.id, {
  env: { FEATURE_FLAG: "1" },
  unset: ["API_TOKEN"],
});
```

```bash
curl -X POST https://api.zavu.dev/v1/sandbox-environments \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "staging", "env": {"DATABASE_URL": "postgres://user:pass@db/app"}}'

curl -X POST https://api.zavu.dev/v1/sandboxes \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"environment": "staging", "env": {"DEBUG": "1"}}'
```

- A name is 1 to 64 letters, digits, dots, dashes or underscores, unique in the project. A project keeps at most 50.
- Variables follow the `env` rules: names match `[A-Za-z_][A-Za-z0-9_]{0,127}`, at most 100 per sandbox and 64 KB in total, counting `env` and the environment together.
- Changing or deleting an environment does not touch sandboxes already started with it.
- An unknown name on create answers `404 not_found`, and nothing is charged.
