# Trash and deletion

Two operations, and only one of them is reversible.

| | reversible | confirm needed |
|---|---|---|
| **trash** | yes, until it purges | no |
| **permanent delete** | no | yes |

Trash is the safety net, so trashing takes no confirmation. Permanent delete is the cascade, so it does.

## Trashing

`POST /v1/todos/{id}/trash` and `POST /v1/containers/{id}/trash` stamp the row.

For a container, **only the root row is stamped**. Its subtree is hidden by exclusion rather than marked, which is what lets a restore bring the whole thing back untouched.

`POST /v1/todos/{id}/restore` and `POST /v1/containers/{id}/restore` clear the stamp. A restored container brings its subtree with it.

## Reading the trash

`GET /v1/trash` lists **trashed roots only**, newest first: the order is `(trashedAt desc, id desc)`, and it is what the cursor resumes in.

A row trashed on its own inside a container that was later trashed does not appear here. It is inside the container's subtree, and it still purges on its own stamp. So the list is what a user can act on, not everything that carries a stamp.

`/v1/trash` needs `todos:read`, and `DELETE /v1/trash` needs `todos:write`, whatever kind of row is involved.

## Seeing trashed to-dos in a list

`GET /v1/todos?include_trashed=true` puts trashed to-dos back into an ordinary list. The parameter is the string `"true"` or `"false"`, not a boolean.

Every other list route excludes trashed rows and has no equivalent switch.

## The purge

A trashed row is purged automatically **30 days** after it was stamped.

The purge runs on its own stamp, so a to-do trashed three weeks before its parent container outlives the container's own window by a week.

// Do not treat the trash as storage you can read back later. If your integration needs a copy, take one before you trash.

## Permanent delete

`DELETE /v1/todos/{id}` and `DELETE /v1/containers/{id}` delete a **trashed** row for good. Pass an id you got from `GET /v1/trash`.

For a container this is a cascade: the row, its child containers, their to-dos, and every attachment on any of them. There is no partial form and nothing is recoverable afterwards.

`DELETE /v1/trash` does the same for everything in the trash at once, and answers with a count.

```json
{ "deleted": 12 }
```

## What deletion looks like to a reader

A permanently deleted row answers [not_found](/errors/not_found) on every route. A trashed row is still readable at its own URL and through `/v1/trash`.

There is no tombstone and no deleted-since feed. If you keep a copy, reconcile by walking the list rather than by asking what went away. See [keeping a copy in sync](/sync).

## The inbox has no delete

A capture row cannot be deleted, on either surface. Archiving is how the product deletes one, and an archived row stays readable through `GET /v1/inbox?status=archived`. See [the data model](/data-model).

## Next

- [Keeping a copy in sync](/sync)
- [Reference for /trash](/reference/trash)
