# Database restores API

> Read the recovery window, start a point-in-time database restore, follow its progress and undo the latest completed restore.

Source: https://docs.fortly.com/api/restores

These routes recover a project's PostgreSQL database. They do not roll back its application release or restore files, KV data or account settings. Read [Backups and restore](https://docs.fortly.com/data/backups) before starting a recovery.

- `GET /projects/{name}/restores` — Recovery window, eligibility and the 20 newest restores
- `POST /projects/{name}/restores` — Queue a restore to a Unix timestamp
- `POST /projects/{name}/restores/{id}/undo` — Switch back to the database the latest restore replaced

Use an [account API token](https://docs.fortly.com/account/tokens). Project members can read status; only the owner or an admin can start or undo recovery. Use the full project `name` from [List projects](https://docs.fortly.com/api/projects#list-projects).

## Read the recovery window

```bash
curl https://api.fortly.com/projects/my-app-ada/restores \
  -H "Authorization: Bearer $FORTLY_TOKEN"
```

| Field | Meaning |
| --- | --- |
| `database` | Whether the project has a managed database. |
| `eligible` | Whether the owning account's plan or current Free offer permits restores. |
| `can_restore` | Whether the caller is the owner or an admin. Check this separately from eligibility. |
| `service_ok` | Whether the recovery service's catalogue is fresh. Do not start while false. |
| `retention_days` | The recovery retention, currently 7 days. |
| `window` | `{ "from": ..., "to": ... }` in Unix seconds, or null before a recovery window exists. Both bounds are inclusive. |
| `points` | Daily backup timestamps inside the window. Targets are not restricted to these timestamps. |
| `offer_until` | Free offer's exclusive cutoff, or null for paid accounts or an inactive offer. |
| `restores_left_today` | Remaining Free allowance in the preceding 24 hours, or null when no Free offer applies. The field name does not imply a midnight reset. |
| `restores` | Up to 20 restores, newest first. |
| `undoable` | The latest restore id that can still be undone, or null. |

The Free offer includes December 1, 2026, UTC: its cutoff is December 2 at 00:00 UTC (`1796169600`). It allows one non-failed restore per project in a rolling 24-hour window. Pro and Max remove that allowance. Undo does not consume an additional restore or refund the first one.

## Start a restore

Choose `target_at` from the server's current window. Convert the intended date and timezone to Unix seconds, review the project and target, then submit:

```bash
# Set TARGET_AT to the Unix timestamp you reviewed, within window.from and window.to.
curl -X POST https://api.fortly.com/projects/my-app-ada/restores \
  -H "Authorization: Bearer $FORTLY_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"target_at\":$TARGET_AT}"
```

Answers `202` with the restore record. This means accepted, not complete. Poll the GET route and find the returned `id`; the console checks every three seconds while a restore is active.

| State | What is happening |
| --- | --- |
| `queued` | Waiting for the recovery service. |
| `restoring` | Rebuilding the database from its backups. |
| `staged` | Rebuilt; waiting for the project to switch to it. |
| `done` | The project is using the restored database. |
| `failed` | Recovery failed; `detail` explains why. The current database remains in use. |
| `undone` | Undo switched the project back to its previous database. |

Records include `id`, `target_at`, `state`, `detail`, `created_at`, `started_at`, `finished_at` and `undone_at`. Nullable timestamps have not occurred yet. Internal database names and the requesting account's id are not returned.

> **Warning:**
>
> At switch-over, data written after the selected moment is replaced, including writes made while recovery was running. Pause writers first if necessary. A database restore does not change the deployed code; verify schema compatibility afterwards.

## Undo the latest restore

Read `undoable` again before submitting. Review the loss of access to writes made since the restore, then call:

```bash
curl -X POST https://api.fortly.com/projects/my-app-ada/restores/$RESTORE_ID/undo \
  -H "Authorization: Bearer $FORTLY_TOKEN"
```

The previous database is retained for seven days after completion. Undo normally returns `200` with the updated record; it can return `204` if the switch completed but the record could not be read back. Refresh status after either response. Undo is available for an eligible retained restore even after the Free offer expires; it does not require a new paid-plan entitlement.

Only the latest completed restore can be undone, and Undo itself is not a repeatable toggle. Check the current `undoable` value instead of retrying an old id blindly.

## Handle errors and retries

| Status | What to do |
| --- | --- |
| `400` | Send an integer `target_at` within the current server window. |
| `402` | Upgrade: the Free offer has ended for this account. |
| `403` | Ask the owner or an admin to perform the operation. |
| `404` | Verify project access and whether it has a database. |
| `409` | A restore is already active, no recovery window exists yet, or the requested Undo is no longer eligible. Refresh status. |
| `429` | The Free project's rolling 24-hour allowance is used. Wait or upgrade. |
| `503` | The recovery catalogue is unavailable or stale. Wait and refresh status. |

There is no idempotency key. If a POST response is lost, read the restore list before starting another request. A second active restore is refused, rather than put behind the first one in a queue.
