# RESTORE DATABASE to a `timestamp` (each table as of its last version committed at or before it) or

`POST /datanotes/operations/restore_database/` (operation `restore_database`)

RESTORE DATABASE to a `timestamp` (each table as of its last version committed at or before it) or
to `before_migration` (a `migration_id` or a `batch`: every table as it was just before that operation
ran — to rewind an agent's run). Every table that keeps a transaction log goes back, rows and
definition: rows added since are deleted, changed rows get their old content, removed rows come back;
a table created since is dropped with its rows moved to its archive folder (as drop_table with
mode "archive": nothing is hard-deleted), a table dropped since comes back. Tables already as
they were are left alone (`unchanged`, no new version). One operation: each restored table gets a
RESTORE version, all with one `migration_id`, and `undo_migration` on it reverts them all. Refused as a
whole, nothing written (rows listed, with the way out), when a row it leaves would break a CHECK that
follows links or a `ref_where` / `ref_by`, across tables, as the restored definitions have them.
Tables without a log (history off, or vacuumed before that time) cannot be rewound: they stay as they
are, listed in `skipped` with the reason (tell the user). Views, bases and the database settings are
not rewound (the logs keep tables), nor a rename or move of a table (undo_migration on it does that).
Returns `restored_to`, `tables` (per table: `action` restore / drop / recreate, `version`,
`creates`, `texts`, `deletes`, `archived`), `unchanged` and `skipped`. Run with `dry_run` first and show the user what changes; describe_history without `table` shows the moments.

The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Datanotes-Actor` | header | `string` | no | JSON: on whose behalf the write is made, recorded in the journal (`harness`, `model`, `session`). |

## Request body

`application/json`

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `timestamp` | `string \| null` | no | `null` |  |
| `before_migration` | `string \| null` | no | `null` |  |
| `dry_run` | `boolean` | no | `false` |  |

## Responses

| Status | Description | Body |
| --- | --- | --- |
| 200 | Done. | `RestoreDatabaseResult` (application/json) |
| 400 | Bad input: the body is not a JSON object, or an argument is unknown, missing or of the wrong type. | `Error` (application/json) |
| 401 | Missing or wrong bearer token. | `Error` (application/json) |
| 403 | A named token beyond its permissions (the engine's administration, tables outside its `write` patterns, schema changes without `ddl`). | `Error` (application/json) |
| 422 | Refused (validation failures, conflicts, an unmet `expect`): nothing written. Same body as 200, with `ok: false` and the reasons. | `RestoreDatabaseResult` (application/json) |
| 500 | Unexpected error (`{error}`), or a write that failed partway and could not be rolled back: `rolled_back: false`, `failed` lists the files left (undo_migration on `migration_id` retries the rollback). | `RestoreDatabaseResult \| Error` (application/json) |
| 503 | A write that failed partway (a file another program holds) and was rolled back: `rolled_back: true`, nothing written. Send it again later. Same body as 200, with `ok: false` and `failed`. | `RestoreDatabaseResult` (application/json) |

The schemas named here are in [openapi.json](https://datanotes.dev/openapi.json) (`components.schemas`).
