# Snapshots, restore and undo

> How moatbox keeps a history of each space's disk, and how you restore, reset, undo, share and fork from it.

A snapshot is a copy of a space's disk at one moment. moatbox keeps them on the server that runs the space. You can browse a snapshot's files, compare a file with the space now, restore the whole space to it, start a new space from it, or share it with another team.

A space has three kinds of snapshot:

| Kind | Who takes it | How long it stays |
| --- | --- | --- |
| Automatic | moatbox, while the space is awake | Thinned over time, gone after your plan's automatic history days |
| Saved | An owner or admin, with a name | Until you delete it |
| Taken at delete | moatbox, when you delete the space | While the space sits in the trash |

The **Snapshots** page of a space lists its history. The **Team snapshots** page lists every space, the team's saved snapshots, share codes and spaces in the trash.

## Automatic history

While a space is awake, moatbox checks it every 15 seconds and takes a snapshot if the disk changed since the last one. It takes one more when the space goes to sleep. A sleeping space takes none. Spaces that run CI jobs, and spaces of a team with no active plan, skip the 15-second checks but still get the snapshot at sleep.

moatbox thins the history as it ages. It keeps:

- every snapshot from the last 10 minutes
- one a minute for the last hour
- one an hour for the last day
- one a day after that, up to your plan's automatic history days

If a space's snapshots hold more than 100 GiB of disk, moatbox drops its oldest automatic snapshot each time it thins the history and finds nothing old enough to drop. This size rule stops at the newest automatic snapshot and leaves saved ones alone, so a space can stay above 100 GiB. The age rules above still apply to the newest one.

| Plan | Automatic history | Saved snapshots per team | Undo window for restores and resets |
| --- | --- | --- | --- |
| Solo | 7 days | 5 | 30 days |
| Pro | 14 days | 20 | 90 days |
| Team | 30 days | 50 | 365 days |

## Saved snapshots

Owners and admins save snapshots. Press **Save now** on a space's Snapshots page, or open an automatic snapshot and press **Save with a name**.

- A name is 1 to 64 ASCII letters, digits, spaces, dots, dashes or underscores, and starts with a letter or digit.
- Names are unique across the team.
- The limit counts saved snapshots across all of the team's spaces.
- A saved snapshot has no expiry.

**Unsave** turns a saved snapshot back into an automatic one. From then on, moatbox thins it like any other automatic snapshot.

You cannot save or share while snapshots are off for the space or the team. See [Privacy](https://docs.moatbox.dev/privacy.md).

## Snapshot taken at delete

When an owner or admin deletes a space, moatbox takes a final snapshot and moves the space to the trash for 72 hours. You cannot rename or delete that snapshot. If you restore the space from the trash, the final snapshot joins its history as an automatic one. After 72 hours, moatbox deletes the space and every snapshot it has. Trash and delete are covered in [Spaces](https://docs.moatbox.dev/spaces.md).

With snapshots off, moatbox takes no final snapshot. With zero data retention on, deleting a space also skips the trash.

## Browse, download and compare files

Any team member can browse a snapshot. Open it from the space's Snapshots page with **Browse files**. moatbox reads files from the snapshot on the server, so browsing works while the space sleeps.

Browsing shows the home folders and the workspace. The rest of the disk, such as `/root` and `/etc`, holds the space's own state and stays hidden: restore the snapshot or start a new space from it to reach those files. moatbox refuses a path that goes through a link.

Pick a file to:

- **Download** it. Files up to 64 MiB download. moatbox records each download in the audit log.
- **Compare with now**. moatbox shows a line diff between the snapshot and the file in the space today. Both sides must be text and at most 512 KiB, and the space must be awake.

For a bigger file, restore the snapshot or start a new space from it.

## Restore a snapshot

Restoring puts the space's whole disk back to a snapshot. Owners and admins restore, and the space must be awake.

1. Open the snapshot on the space's Snapshots page.
2. Press **Restore** and confirm.

The space stops, its disk goes back to the snapshot, and it starts again. Running agents and SSH sessions end. moatbox keeps the state from just before the restore as an undo point. Every snapshot in the history stays.

To go back, press **Undo restore**. Undo drops every change made since the restore. The undo point lasts for your plan's undo window (see the table above).

## Reset to a fresh box, and undo it

**Reset** on a space's page gives it a fresh disk. You lose everything in `/workspace` and `/home/agent`, the agent timeline, integration logins and hosted databases. The network mode and allowed hosts stay. Owners and admins reset, and the space must be awake.

Tick **Keep logins** to carry these files over:

| Tool | Files kept |
| --- | --- |
| Claude Code | `~/.claude/.credentials.json`, and the account in `~/.claude.json` |
| Codex | `~/.codex/auth.json` |
| opencode | `~/.local/share/opencode/auth.json` |
| GitHub CLI | `~/.config/gh/hosts.yml` |

moatbox skips a login file over 1 MiB. If it cannot write a kept login back, it undoes the reset.

Before the reset, moatbox keeps the old disk as an undo point. **Undo reset** puts it back and drops every change made since. The undo point lasts for your plan's undo window.

### Limits on undo

- A space keeps at most 3 undo points from resets and restores together. At 3, undo one or wait for the oldest to expire before you reset or restore again.
- Undo always goes back to the newest undo point.
- If you forked the space after the reset or restore, delete those copies before you undo.
- Undo fails if it would make the team host more apps than the plan allows. Remove an app first.

## Start a new space from a snapshot

On a snapshot, press **New space from it**. When you create a space, you can also pick **A saved snapshot** or **A share code from another team** as the start. Owners and admins create spaces, and the team needs an active plan.

moatbox makes the new space on the server that holds the snapshot. If that server has no room, try again later.

## Share codes

A share code lets another team start a space from a copy of one of your saved snapshots.

1. Save the snapshot with a name.
2. Open it and press **Share code**.
3. Copy the code. It starts with `mbs_` and is 47 characters long. moatbox stores only a hash of it, so you see the full code once.

Owners and admins create and revoke codes. A code lasts 7 days unless you pick another length, from 1 to 30 days. A team holds at most 5 live codes at a time; revoke one to make another.

Before the code works, moatbox checks a copy of the snapshot and removes from it:

- moatbox's own state in the space, and Tailscale state
- sign-in and key files in `/root` and each home folder: `.claude/.credentials.json`, `.claude.json`, `.codex/auth.json`, `.local/share/opencode/auth.json`, `.config/gh/hosts.yml`, `.git-credentials`, `.netrc`, `.npmrc`, `.pypirc`, `.docker/config.json`, `.aws/credentials`, `.aws/config`, `.config/gcloud`, `.kube/config`, `.ssh`
- any file that holds one of your team's integration credential values, the space's own key, its AI token, or a secret variable, agent key or API key of its environment (values of 8 characters or more)

The Team snapshots page lists what moatbox removed. If a credential value stays somewhere moatbox cannot strip, such as file system metadata or the space's data pool, the code shows **Blocked** and never works.

The other team pays for the space it makes. Your Team snapshots page shows who used each code, and your audit log records each use. Revoke a code to stop new spaces from it; spaces already made from it keep working. The page lists codes from the last 90 days.

## Fork a running space

A fork makes one or more copies of a space as it runs, memory included. moatbox pauses the space for a moment, copies its memory and disk, and resumes it. Each copy starts from that point on the same server.

Owners and admins fork. The space must be awake and not read-only. Each copy needs its own slug: 3 to 32 characters of `a-z`, `0-9` and `-`, starting and ending with a letter or digit.

Copies inherit the parent's network settings, environment, monitoring, snapshot and retention settings, and idle and awake limits. **Always on** starts off in a copy. Each copy counts toward your plan's limit on running boxes. moatbox refuses the fork if there aren't enough free boxes, if the server has no room for the copies, or if their hosted apps would pass your plan's limit.

```bash
curl -X POST https://app.moatbox.dev/api/v1/spaces/my-space/fork \
  -H "Authorization: Bearer $MOATBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"slugs": ["my-space-a", "my-space-b"]}'
```

## Delete snapshots

Owners and admins delete snapshots. **Delete** removes one saved or automatic snapshot. **Delete history** removes every automatic snapshot of the space; you confirm by typing the space's name. Deleted snapshots cannot come back. Saved snapshots stay until you delete them one by one.

## API

All routes live under `https://app.moatbox.dev/api/v1`. Send an API key as `Authorization: Bearer <key>`. Keys need `snapshot.read` or `snapshot.write`; forks need `space.fork`. Saving, unsaving, deleting, restoring, resetting and undoing also need one of `run`, `exec` or `file.write`. See [API keys](https://docs.moatbox.dev/api-keys.md).

| Method and path | Does |
| --- | --- |
| `GET /spaces/{space}/snapshots` | List the history, disk use, plan limits and the last restore |
| `POST /spaces/{space}/snapshots` | Save now. Body: `{"name": "..."}` |
| `PATCH /spaces/{space}/snapshots/{id}` | Save with a name (`{"name": "..."}`) or unsave (`{"name": null}`) |
| `DELETE /spaces/{space}/snapshots/{id}` | Delete one snapshot |
| `DELETE /spaces/{space}/snapshots` | Delete the automatic history. Body: `{"confirm": "<space>"}` |
| `POST /spaces/{space}/snapshots/{id}/restore` | Restore |
| `GET /spaces/{space}/snapshots/{id}/files?path=/` | List a folder |
| `GET /spaces/{space}/snapshots/{id}/files/content?path=...` | Download a file |
| `GET /spaces/{space}/snapshots/{id}/files/diff?path=...` | Compare a file with now |
| `POST /spaces/{space}/snapshots/{id}/shares` | Create a share code. Body: `{"days": 7}` |
| `DELETE /teams/{team}/shares/{id}` | Revoke a share code |
| `POST /spaces/{space}/reset` | Reset. Body: `{"keep_logins": true}` |
| `POST /spaces/{space}/reset/undo` | Undo the newest reset or restore |
| `GET /spaces/{space}/resets` | List undo points and when they expire |
| `POST /spaces/{space}/fork` | Fork. Body: `{"slugs": ["..."]}` |
| `GET /teams/{team}/snapshots` | Team view: spaces, saved snapshots, share codes, trash |

To start a space from a snapshot, send `from` to `POST /teams/{team}/spaces`:

```json
{"slug": "from-snap", "from": {"snapshot": 1234}}
```

or with a share code:

```json
{"slug": "from-code", "from": {"code": "mbs_..."}}
```
