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 | asks to confirm | |
|---|---|---|
| trash | yes, until it purges | no |
| permanent delete | no | on MCP (confirm: true); REST does not ask |
Trash is the safety net, so trashing takes no confirmation. Permanent delete is the cascade: the apps and the MCP tools ask first, and a REST call is taken as meant.
Trashing
POST /v1/todos/{id}/trash, POST /v1/notes/{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, POST /v1/notes/{id}/restore and POST /v1/containers/{id}/restore clear the stamp. A restored container brings its subtree with it.
Tags and saved filters go to the trash too: POST /v1/tags/{id}/trash and POST /v1/filters/{id}/trash, with /restore to bring them back. Nothing else is written: the to-dos, notes and attachments that carry a tag keep its name while it is in the trash, so a restore is complete. A tag in the trash is hidden in the apps and a filter criterion naming it matches nothing; a filter in the trash matches nothing. See tags.
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.
Trashed notes are listed as well, for a credential that also holds notes:read, and emptying a trash that holds one needs notes:write.
Trashed note folders are listed too (notes:read), and only the folder: like a project, it is the only row stamped, and its subfolders and notes come back when it is restored. See notes.
Tags and filters in the trash are listed for a credential that also holds tags:read / filters:read, and emptying a trash that holds one needs tags:write / filters:write.
Every entry carries a type, one of todo, container, note, note_folder, tag or filter. Branch on it. Do not infer the kind from which other fields are present: a title does not mean a to-do, because a note has one too.
It is type here and not kind, because kind already means area, project or subproject on a container. One member cannot mean two things in one list.
Seeing trashed rows in a list
GET /v1/todos?include_trashed=true, GET /v1/notes?include_trashed=true, GET /v1/tags?include_trashed=true and GET /v1/filters?include_trashed=true put trashed rows back into an ordinary list, and GET /v1/search?include_trashed=true adds the trashed hits, each marked trashed: true. The parameter is the string "true" or "false", not a boolean.
Those lists hide a row trashed itself. A to-do or note inside a trashed container is not stamped, so /v1/todos and /v1/notes still list it; the views and /v1/search leave it out.
⚠ GET /v1/containers has no switch and lists trashed containers too, with trashedAt set. Filter on it.
The purge
A trashed row is purged automatically 30 days after it was stamped.
Every entry of GET /v1/trash says when: purgesAt is trashedAt plus 30 days, and daysLeft is the whole days until then, rounded up and never below 0. The apps show it as "28 days left", in red at 5 or fewer. MCP list_trash carries both.
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}, DELETE /v1/notes/{id}, DELETE /v1/containers/{id}, DELETE /v1/tags/{id} and DELETE /v1/filters/{id} delete a row for good. The apps only offer this from the trash, but the API does not check: ⚠ it deletes a live row just as readily, with no confirmation. Trash first if you want a way back, and pass an id you got from GET /v1/trash. The MCP tools are stricter: delete_todo, delete_container, delete_tag and delete_filter move to the trash like trash_*, and permanent_delete refuses a row that is not in the trash and asks for confirm: true. An assistant asked to delete something for good trashes it first, then purges it.
For a container this is a cascade: the row, its child containers, their to-dos and its notes. Their attachments are released: one that is also somewhere else stays there (if this was its first place, its next place becomes the first), and one that has no other place is deleted, with the file it stored. The same holds when you delete a to-do or a note for good. See attachments. There is no partial form and nothing is recoverable afterwards. Deleting a note releases its own attachments the same way and deletes its links to to-dos, never the to-dos. Deleting a container deletes the notes whose every place is in it, unless they are also in a folder; any other note only loses that place. Deleting a note also deletes its places, its links to other notes and its version history. DELETE /v1/note-folders/{id} works only on a trashed folder.
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.
Deleting a capture archives it
The inbox has no trash, and no call removes a capture row. Deleting one, in the apps or with POST /v1/inbox/{id}/delete (MCP delete_inbox_item), archives it and deletes the file or picture it holds from storage. POST /v1/inbox/{id}/archive archives it and keeps the file. Either way the row stays readable through GET /v1/inbox?status=archived. See the data model.