<!-- section: Files API · status: building · source: docs/files-api/06-restoring.md -->

> Deletes are reversible for 30 days — how to see what is recoverable and bring it back.


# Restoring a deleted file

Deleting stops a file serving immediately. It does not destroy it.

For **30 days** the stored copy is kept and the record survives alongside it, so
the file can be brought back exactly as it was — same id, same URL, same entity
attachment. After that the copy expires and the record is removed.

This is not a feature you should need. It exists because the alternative was a
system where one mistyped call was permanent, and
`DELETE /v1/files?role=gallery` is a legal request that removes every gallery
image in a project.

## GET /v1/files/deleted

What is recoverable, most recently deleted first.

```json Response
{
  "files": [
    {
      "id": "E7iVnWA1LiEpu3u8dRTVK",
      "originalFilename": "eso1242a (2).tif",
      "contentType": "image/tiff",
      "size": 35233254,
      "deletedAt": "2026-08-17T09:12:44.001Z",
      "restorableUntil": "2026-09-16T09:12:44.001Z"
    }
  ]
}
```

Deliberately **not** part of `GET /v1/files`, and there is no flag to include
them. A deleted file is not a file: it does not serve, it has no working URL,
and a caller who forgot a filter should never find one in a gallery.

For the same reason no `url` or `variants` come back here. The object is behind
a delete marker and any link would 404 — offering one would be offering
something that cannot work.

## POST /v1/files/{id}/restore

```bash Request
curl -X POST "$BASE/v1/files/E7iVnWA1LiEpu3u8dRTVK/restore" \
  -H "authorization: Bearer $KEY"
```

```json Response
{ "file": { "…": "the file, readable again, with fresh URLs" } }
```

Needs `files:write` — it changes what the project serves.

Everything comes back with it: the id, the filename, the entity it was attached
to, who uploaded it. **Smaller versions do not**, and do not need to: they are
derived, and generation is on demand, so each rebuilds the first time it is
requested. Storing them through a deletion would be paying to keep what a
request can recreate.

## Errors

```json 400 — it was not deleted
{ "error": "That file was not deleted" }
```

```json 410 — the window closed
{ "error": "The stored copy is gone. Deleted files are recoverable for 30 days." }
```

A `410` is final. The record may still be listed for a moment before the daily
sweep removes it, but the bytes are gone and nothing will bring them back.

## What deleting actually does

Worth knowing, because it explains the edges:

1. The stored object is deleted, which is what stops it serving.
2. Storage keeps the previous copy for 30 days rather than destroying it.
3. The record stays, marked deleted — because the bytes alone are not enough.
   What a file was attached to, and who uploaded it, exist only in the record.
4. Smaller versions are removed outright and rebuilt on demand if it comes back.

**Already-cached copies can still be served briefly.** A public file's URL is
permanent and cached at the edge, so deleting it stops it being *distributed*
rather than making it instantly unreachable everywhere. If something must be
unreachable now, that is a different problem from deletion.
