containers, to-dos, the inbox, and what the views mean
The data model
Five kinds of row: containers, to-dos, capture rows, tags and attachments. Everything else the API returns is a view over them.
Containers
A container is an area, a project or a subproject. One table, one word, three values of kind.
The hierarchy is closed and three deep:
area
project
subproject
(to-dos)- An area is always top level. It cannot be nested.
- A project sits under an area, or at the top level with no area.
- A subproject sits under a project. Nothing nests under a subproject.
A move can change the kind, the way dragging does in the app. Move a project onto another project and it becomes a subproject of it. Move a subproject to the top and it is promoted to a project. Illegal moves are refused with the reason, including any attempt to move a container into its own contents.
A container can be active or archived. Archiving hides it and its subtree from the views without trashing it.
To-dos
A to-do either has a parent container or it does not.
- Filed:
parentIdnames a project or subproject. To-dos cannot hang off an area. - Standalone:
parentIdis null. It lists after the projects on the overview, in its own band.
status is open, someday or done. There is no completed value.
Ordering inside a band comes from a stored sort key, not from a timestamp. Two rows can share a position only until the next write settles it.
The inbox
The inbox is the triage queue. It holds capture rows, and on the MCP surface it also shows unfiled to-dos first. See REST and MCP, because the two surfaces differ here.
An unfiled to-do has no parent and no date. A to-do leaves the inbox the moment it gets either one. There is no inbox flag.
A capture row is a scrap: a line of text or a URL, captured fast and sorted later. You can edit its text and its whenDate, archive it, park it in someday, or move it onto a container, where a text capture becomes a to-do carrying its captured date. Moving with no parentId means anytime, and only a text capture may do that: a note is refused with validation_failed naming the rule, and a url, image or file has nothing to attach to. There is no delete route. Archiving is how a capture row is deleted, on both surfaces.
What the views mean
| view | what it holds |
|---|---|
| overview | the container tree, the standalone band, counts |
| today | to-dos dated today, plus overdue, plus what was completed today |
| upcoming | a rolling window of today through today plus six, one bucket per day |
| anytime | open to-dos with no date that are filed under a project or subproject |
| someday | parked to-dos and parked capture rows, grouped by container |
| logbook | done to-dos grouped by the local day they were completed |
Three rules behind those that are not obvious:
A deadline alone does not schedule a to-do. whenDate puts a row in a date view. deadline does not. A to-do with a deadline and no whenDate still lists in anytime.
Parking strips dates. Moving a plain to-do to someday sets whenDate and deadline to null and keeps the parent, so a project idea parks with its project. A recurring to-do instead keeps its date and its rule, dormant, while parked.
Anytime excludes the unfiled. A to-do with no parent and no date is not in anytime. It is in the inbox.
Upcoming is always seven buckets. It is not bounded by the calendar week and does not read the week-start setting.
Tags
A tag's row id is derived from its name, so a tag cannot be renamed. PATCH /v1/tags/{id} accepts a colour and refuses a name. The working operation is create-new then delete-old. See encryption for why.
You pass tag names, not ids, when creating or patching a to-do. Unknown tags are created, so there is no vocabulary to register first.
Attachments
An attachment hangs off a to-do, a container or a capture row, named by parentType and parentId.
kind is url, text or note on create. file and image rows exist and are readable, but v1 has no upload route, so they cannot be created over the API. See scopes.
Ids
Every row id is an uppercase UUID, for example 9C3F1A72-8E4F-4B21-A0D5-3F7B2C1E6A94. That is also the shape you must use if you supply your own id on a create, which is a second way to make a create safe to repeat. See errors and retries.
Webhook subscriptions, events and deliveries use prefixed ids instead: wh_, evt_ and whd_.