what is recoverable, what is not, and what a container takes with it
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.
{ "deleted": 12 }What deletion looks like to a reader
A permanently deleted row answers 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.
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.