# Version history routes

> How Hyperclay Local stores backups in .hyperclay/versions/, prunes old versions, and serves the versions, version, and restore HTTP routes.

Source: https://hyperclaylocal.com/docs/version-history/

Every save in Hyperclay Local writes a backup copy of the document into `.hyperclay/versions/` inside the served folder, and the local server exposes HTTP routes to list, read, and restore versions. For a step-by-step guide to recovering a document, see [Recover from backups](https://hyperclaylocal.com/docs/guide/backups/).

## Backup storage in `.hyperclay/versions/`

Backups are stored under `<folder>/.hyperclay/versions/<document path without .html>/<timestamp>.html`. On the very first save of an existing file that has no prior backups, Hyperclay Local writes a copy both before and after the write:

```text
.hyperclay/versions/index/2026-08-27-22-09-56-277-0400.html
.hyperclay/versions/blog/post/2026-08-27-22-11-12-709-0400.html
```

- **Timestamp format**: Each filename is `YYYY-MM-DD-HH-MM-SS-mmm±ZZZZ`, using local wall-clock time to the millisecond plus the signed UTC offset in effect at that moment (for example, `2026-11-01-01-30-00-431-0400`). Including the UTC offset keeps filenames ordered accurately across daylight-saving time transitions.
- **Same-millisecond saves**: Bursts within the same millisecond receive a `-001` through `-999` suffix.
- **Opening the backups folder**: Choosing **Backups** in a folder card's `⋯` menu or system tray submenu opens `<folder>/.hyperclay/versions/` in your file manager (creating the folder first if it does not exist yet).
- **Never served as static files or synced**: Because `.hyperclay/` starts with a dot, version backups are never served as public static files and are never uploaded by cloud sync. The legacy `sites-versions` directory name also remains reserved and ignored by both the HTTP server and sync.

## Retention and pruning

Hyperclay Local retains the union of two rules for each document in `.hyperclay/versions/`:

- Every version newer than `60` days.
- Always the newest `20` versions per document, regardless of age.

A pruning pass runs once when the folder's server starts and at most once an hour after that, deleting only files in `.hyperclay/versions/` that are both older than `60` days and outside the newest `20` for that document.

## Recovered copies in `.hyperclay/recovered/`

When an external program on disk replaces or deletes a document while an open room has unsaved live edits, the file on disk wins and Hyperclay Local saves the unsaved live edits to `<folder>/.hyperclay/recovered/<document path without extension>/recovered-<timestamp>.html`.

- Recovered copies appear alongside normal backups in the version history routes, marked as `recovered`, and can be restored like any other version.
- The version pruner explicitly skips `.hyperclay/recovered/`, so recovered copies are never pruned automatically.

## Version history HTTP routes

The local server and the [workspace window](https://hyperclaylocal.com/docs/guide/workspace/) expose three routes for inspecting and restoring versions of a document:

- **`GET /_/versions`**: Lists the available versions for a document from `.hyperclay/versions/<document path>/` together with any `recovered-<timestamp>.html` copies from `.hyperclay/recovered/<document path>/`.
- **`GET /_/version`**: Returns the stored HTML bytes of a specific version or recovered copy.
- **`POST /_/restore`**: Restores a selected version over the current document through the folder's shared/exclusive write gate.

### How `POST /_/restore` protects the current document

Before overwriting a document, `POST /_/restore` runs a sequence of safety checks:

1. **Conditional check (`If-Match`)**: Restore accepts an optional `If-Match` header and checks it before writing the safety backup. If the document on disk has changed since it was loaded, restore refuses with `412` (`document changed since it was loaded`).
2. **Mandatory safety backup**: Restore writes a backup of the current file to `.hyperclay/versions/` before replacing it (skipping a rendered room backup only for non-page live documents). If the current file exists on disk and cannot be read, restore refuses with `500` (`current file cannot be read, so no safety backup is possible`). If a replaced page document has unsaved live edits that cannot be written as a version first, restore refuses with `unsaved-edits`.
3. **Version validation**: Restore refuses with:
   - `404` (`version not found`) when the requested version file does not exist.
   - `413` (`version is too large to restore`) when the version exceeds the document size limit.
   - `422` (`version is not a complete HTML document`, or `invalid-document` when a page document's room cannot render).
4. **Live rooms kept open**: When the restore succeeds, open live rooms stay connected and open windows receive the restored document.
