# 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:

```text
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**: `parentId` names a project or subproject. To-dos cannot hang off an area.
- **Standalone**: `parentId` is 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](/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](/errors/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](/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](/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](/errors-and-retries).

Webhook subscriptions, events and deliveries use prefixed ids instead: `wh_`, `evt_` and `whd_`.

## Next

- [Dates and time zones](/dates)
- [Trash and deletion](/trash)
- [REST and MCP](/rest-and-mcp)
