22 routes
/notes
22 routes.
A note is a page of Markdown you keep and read: a release history, a set of measurements, the paragraph you will need again in March. It has no checkbox, no dates and no priority, so it never appears in a date view. A note sits in at most one FOLDER (folderId, see /note-folders), can be in any number of areas, projects and subprojects (placeIds, see /notes/{id}/places), is LINKED to any number of to-dos (links) and of other notes (noteLinks), and carries tags (tags); all of these are independent. Its body can embed its attachments, other notes, to-dos and projects ([title](note:ID) and friends). It can be pinned, it keeps a version history (/notes/{id}/versions), it can hold attachments of its own (parentType: "note"), and it needs a title or a body. A note in the inbox is still a capture row until you file it.
- GET /v1/notes
- POST /v1/notes
- GET /v1/notes/{id}
- PATCH /v1/notes/{id}
- POST /v1/notes/{id}/move
- POST /v1/notes/{id}/trash
- POST /v1/notes/{id}/restore
- DELETE /v1/notes/{id}
- GET /v1/notes/{id}/links
- POST /v1/notes/{id}/links
- DELETE /v1/notes/{id}/links/{todoId}
- GET /v1/notes/{id}/places
- POST /v1/notes/{id}/places
- DELETE /v1/notes/{id}/places/{containerId}
- GET /v1/notes/{id}/note-links
- POST /v1/notes/{id}/note-links
- DELETE /v1/notes/{id}/note-links/{noteId}
- GET /v1/notes/{id}/versions
- GET /v1/notes/{id}/versions/{versionId}
- POST /v1/notes/{id}/versions/{versionId}/restore
- GET /v1/notes/{id}/export
- GET /v1/notes/export
GET /v1/notes
List notes
Ordered by sortKey like /todos, or with ?order=edited the way the apps draw notes: pinned first, then editedAt descending. Filters, combined with AND: ?parent_id= (notes with that area, project or subproject among their places, or the literal null for notes with no place), ?folder_id= (a folder id, or null for no folder), ?linked_todo_id=, ?linked_note_id=, ?tag= (a tag name), ?pinned=true|false, ?placement=folder|attached|orphan (where the apps show a live note) and ?q= (title, body, tag names and folder name; an embed is matched by its name, never its id). Trashed notes are excluded unless you pass ?include_trashed=true.
Scopes: notes:read
Query
| parameter | type | notes | |
|---|---|---|---|
limit |
integer | optional | min 1, max 200 |
cursor |
string | optional | max 4096 chars |
updated_since |
string (RFC 3339) | optional | max 24 chars. An RFC 3339 instant, e.g. 2026-09-16T08:30:00.000Z. |
parent_id |
string | optional | max 64 chars |
include_trashed |
true, false |
optional | |
folder_id |
string | optional | max 64 chars. A folder id, or the literal null for notes in no folder. |
linked_todo_id |
string | optional | max 64 chars. Only notes LINKED to this to-do. |
pinned |
true, false |
optional | |
placement |
folder, attached, orphan |
optional | Where the apps show the note: folder (in a live folder), attached (no folder; attached to a live project/area or linked to a live to-do: the apps’ “attached notes”), orphan (none of those, so it shows in the inbox). Live notes only. |
q |
string | optional | max 200 chars, min 1. Case-insensitive substring over the title, the body (an embed by its name, never its id), the tag names and the folder’s name. |
tag |
string | optional | max 64 chars, min 1. Only notes with this tag (a name; case and spacing are normalized). |
linked_note_id |
string | optional | max 64 chars. Only notes LINKED with this note. |
order |
sort_key, edited |
optional | sort_key (the default) or edited: pinned first, then editedAt descending, the order the apps draw. The cursor follows the order you asked for. |
Response
- 200 NoteList. One page of notes.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * account_too_large * validation_failed * rate_limited * internal
POST /v1/notes
Create a note
A note needs a title or a body. parentId is its first place and may be an area, a project or a subproject; placeIds adds more of them. None may be in the trash. A to-do is refused, because to-dos are LINKED (POST /notes/{id}/links). folderId puts it in a note folder, independently of its places. tags are names; a tag that does not exist yet is created. pinned: true creates it pinned. The new note is placed at the END of its first place’s ITEM BAND, which is that container’s to-dos and notes together in one ordering. Embeds in the body connect as they do in the apps: [title](note:ID) links that note, [title](task:ID) links that to-do and [name](container:ID) adds that place. The note’s history starts with its first version (GET /notes/{id}/versions).
Scopes: notes:write Idempotent. Send Idempotency-Key and a retry replays the first answer instead of writing twice.
Request body
| field | type | notes | |
|---|---|---|---|
body |
string | optional | or null, max 100000 chars. The note itself, Markdown, up to 100,000 characters. Stored and returned VERBATIM: the API neither renders it nor checks the syntax. May be empty when the note has a title: a note needs a title or a body. |
title |
string | optional | or null, max 512 chars. An optional heading. Without one, the apps show the first line of the body. |
folderId |
string | optional | or null, max 64 chars. A LIVE note folder (/note-folders), or null for no folder. Independent of parentId. |
pinned |
boolean | optional | Create it pinned. |
colorHex |
string | optional | or null, 6-digit hex, with or without the #, max 16 chars. A 6-digit hex colour, with or without the leading #: #3A7BD5 or 3A7BD5. Three-digit shorthand is refused. |
parentId |
string | optional | or null, max 64 chars. The note’s FIRST place: an area, a project or a subproject (not in the trash). Omitted or null: no place. A to-do id is refused: to-dos are LINKED (POST /notes/{id}/links), and a to-do carries its own free text in description. |
placeIds |
array of string | optional | max 64 items. More places after parentId, in order: a note can be in several projects and areas at once. |
tags |
array of string | optional | max 64 items. Tag names. Normalized (lowercase, single spaces); a tag that does not exist yet is created. |
id |
string (UPPERCASE UUID) | optional | max 64 chars. An uppercase UUID, all 36 characters, hex digits in capitals: 8E4F2A1B-7C3D-4E5F-9A0B-1C2D3E4F5A6B. Supplying one is how a create is made idempotent; a repeat is 409 duplicate_id. Lower case is refused, and so is a short id. |
Response
- 201 Note. The note as stored.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * conflict * duplicate_id * payload_too_large * unsupported_media_type * account_too_large * validation_failed * rate_limited * internal
GET /v1/notes/{id}
One note
Scopes: notes:read
Response
- 200 Note. The note.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
PATCH /v1/notes/{id}
Change a note’s title, body, folder, pin, tags or places
Send "title": null to clear the title; the note must keep a title or a body. folderId (a live folder, or null) moves it between folders, pinned pins or unpins it, tags sets its tags EXACTLY and placeIds sets its places EXACTLY (to add or remove one, use /notes/{id}/places). parentId is refused here: moving a note in a list reissues its ordering key, which is /move. A change to the title or body is recorded in the note’s history as the apps record an editing stretch: the text before it is kept, and a version from this credential that is under 10 minutes old is updated rather than repeated. Embeds new in the body connect (a note link, a to-do link or a place); an att: embed must name one of the note’s own attachments.
Scopes: notes:write
Request body
| field | type | notes | |
|---|---|---|---|
title |
string | optional | or null, which clears it, max 512 chars. An optional heading. Without one, the apps show the first line of the body. |
body |
string | optional | or null, which clears it, max 100000 chars. The note itself, Markdown, up to 100,000 characters. Stored and returned VERBATIM: the API neither renders it nor checks the syntax. May be empty when the note has a title: a note needs a title or a body. |
folderId |
string | optional | or null, which clears it, max 64 chars. A LIVE note folder (/note-folders), or null for no folder. Independent of parentId. |
pinned |
boolean | optional | true pins, false unpins. |
colorHex |
string | optional | or null, which clears it, 6-digit hex, with or without the #, max 16 chars. A 6-digit hex colour, with or without the leading #: #3A7BD5 or 3A7BD5. Three-digit shorthand is refused. |
tags |
array of string | optional | or null, which clears it, max 64 items. The note’s tags, EXACTLY (names; [] or null clears them). A tag that does not exist yet is created. |
placeIds |
array of string | optional | max 64 items. The note’s places, EXACTLY: areas, projects and subprojects, in order ([] removes every place). Places you keep keep their age; to add or remove one, use /notes/{id}/places. It does not move the note in a list: that is /move. |
Response
- 200 Note. The note after the change.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * payload_too_large * unsupported_media_type * account_too_large * validation_failed * rate_limited * internal
POST /v1/notes/{id}/move
Move a note to another container, or reorder it
Two moves in one route, as in the apps. To the note’s FIRST place (its parentId), it is a reorder: only its position changes and every place is kept. To another area, project or subproject, that container becomes the note’s ONLY place (the apps’ move to…). parentId: null removes every place. With keepOtherPlaces: true, the note leaves only its first place for parentId and keeps the others: the apps’ drag from one list to another. after and before may name a TO-DO or another NOTE of the destination: the band holds both, in one order, which is the same run of rows a person sees in the app. Without either, the note goes to the end. To ADD a place without moving the note, use POST /notes/{id}/places.
Scopes: notes:write
Request body
| field | type | notes | |
|---|---|---|---|
parentId |
string | required | or null, max 64 chars |
after |
string | optional | max 64 chars |
before |
string | optional | max 64 chars |
keepOtherPlaces |
boolean | optional | true moves the note OUT of its first place and INTO parentId, keeping every other place (the apps’ drag from one list to another). Without it, parentId becomes the note’s only place (the apps’ move to…). |
Response
- 200 Note. The note in its new place.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * payload_too_large * unsupported_media_type * account_too_large * validation_failed * rate_limited * internal
POST /v1/notes/{id}/trash
Move a note to trash
Reversible. It leaves every view and appears in /trash, where it is purged after 30 days.
Scopes: notes:write
Response
- 200 Note. The note, with
trashedAtstamped.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * payload_too_large * unsupported_media_type * account_too_large * rate_limited * internal
POST /v1/notes/{id}/restore
Restore a trashed note
Scopes: notes:write
Response
- 200 Note. The note, with
trashedAtcleared.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * payload_too_large * unsupported_media_type * account_too_large * rate_limited * internal
DELETE /v1/notes/{id}
Delete permanently
Irreversible. Its links (to to-dos and to other notes), its places, its version history go with it, and its attachments are released (one with no other place is deleted with its stored file); a linked to-do or note never does. ⚠ It works on ANY note, trashed or not: trash it first if you want a way back.
Scopes: notes:write
Response
- 204 No body. Deleted. Irreversible.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
GET /v1/notes/{id}/links
The to-dos a note is linked to
The same links as the note’s links member, as NoteLink objects. A link to a trashed to-do is hidden.
Scopes: notes:read
Response
- 200 NoteLinkList. Every live link of the note, oldest first.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
POST /v1/notes/{id}/links
Link a note to a to-do
Many-to-many: a note links to any number of to-dos, a to-do to any number of notes. Linking a pair that is already linked returns the existing link with 200 instead of 201, so the call is safe to retry. The to-do must exist and not be in the trash. Fires note.linked.
Scopes: notes:write Idempotent. Send Idempotency-Key and a retry replays the first answer instead of writing twice.
Request body
| field | type | notes | |
|---|---|---|---|
todoId |
string | required | max 64 chars, min 1. The to-do to link. It must exist and not be in the trash. |
id |
string (UPPERCASE UUID) | optional | max 64 chars. An uppercase UUID, all 36 characters, hex digits in capitals: 8E4F2A1B-7C3D-4E5F-9A0B-1C2D3E4F5A6B. Supplying one is how a create is made idempotent; a repeat is 409 duplicate_id. Lower case is refused, and so is a short id. |
Response
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * conflict * duplicate_id * payload_too_large * unsupported_media_type * account_too_large * validation_failed * rate_limited * internal
DELETE /v1/notes/{id}/links/{todoId}
Unlink a note from a to-do
Deletes the link (a hard delete, there is no trash for links). Neither the note nor the to-do changes. not_found when the two were not linked. Fires note.unlinked.
Scopes: notes:write
Response
- 204 No body. Unlinked.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
GET /v1/notes/{id}/places
The projects and areas a note is in
Oldest first; the first is the note’s parentId. live: false marks a place whose container is in the trash or archived: the apps hide it until the container comes back.
Scopes: notes:read
Response
- 200 NotePlaceList. Every place of the note.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
POST /v1/notes/{id}/places
Add a project or area to a note
The apps’ attach to project…: the note now ALSO shows in that area, project or subproject. It never moves the note (that is /move). Adding a place the note already has returns it with 200 instead of 201, so the call is safe to retry. Fires note.place_added.
Scopes: notes:write Idempotent. Send Idempotency-Key and a retry replays the first answer instead of writing twice.
Request body
| field | type | notes | |
|---|---|---|---|
containerId |
string | required | max 64 chars, min 1. The area, project or subproject to add. It must exist and not be in the trash. |
id |
string (UPPERCASE UUID) | optional | max 64 chars. An uppercase UUID, all 36 characters, hex digits in capitals: 8E4F2A1B-7C3D-4E5F-9A0B-1C2D3E4F5A6B. Supplying one is how a create is made idempotent; a repeat is 409 duplicate_id. Lower case is refused, and so is a short id. |
Response
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * conflict * duplicate_id * payload_too_large * unsupported_media_type * account_too_large * validation_failed * rate_limited * internal
DELETE /v1/notes/{id}/places/{containerId}
Take a note out of a project or area
The apps’ detach: the note leaves that container and stays everywhere else (a note with no folder, no place and no link shows in the inbox). not_found when the note was not there. Fires note.place_removed.
Scopes: notes:write
Response
- 204 No body. Removed.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
GET /v1/notes/{id}/note-links
The notes a note is linked with
The same links as the note’s noteLinks member, as NoteNoteLink objects, oldest first. A link to a note in the trash is hidden.
Scopes: notes:read
Response
- 200 NoteNoteLinkList. Every live link of the note.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
POST /v1/notes/{id}/note-links
Link two notes
Two-way: each note lists the other in its notes section. A note cannot link to itself. Linking a pair that is already linked returns the existing link with 200 instead of 201. Writing [title](note:ID) in a body links the two as well; taking it out of the text keeps the link. Fires note.note_linked.
Scopes: notes:write Idempotent. Send Idempotency-Key and a retry replays the first answer instead of writing twice.
Request body
| field | type | notes | |
|---|---|---|---|
noteId |
string | required | max 64 chars, min 1. The other note. It must exist, not be in the trash, and not be this note. |
id |
string (UPPERCASE UUID) | optional | max 64 chars. An uppercase UUID, all 36 characters, hex digits in capitals: 8E4F2A1B-7C3D-4E5F-9A0B-1C2D3E4F5A6B. Supplying one is how a create is made idempotent; a repeat is 409 duplicate_id. Lower case is refused, and so is a short id. |
Response
- 201 NoteNoteLink. The new link.
- 200 NoteNoteLink. The pair was already linked: the existing link.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * conflict * duplicate_id * payload_too_large * unsupported_media_type * account_too_large * validation_failed * rate_limited * internal
DELETE /v1/notes/{id}/note-links/{noteId}
Unlink two notes
Deletes the link (there is no trash for links); neither note changes, and an embed of the other note in the text stays. not_found when the two were not linked. Fires note.note_unlinked.
Scopes: notes:write
Response
- 204 No body. Unlinked.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
GET /v1/notes/{id}/versions
A note’s version history
The note’s text as it was at the end of each editing stretch, the apps’ history: the version holding the current text first (current: true), then newest first. Titles and bodies are left out; read one version for its text. Versions are kept for 30 days, then thinned by the apps to the newest per day; the first one and the newest are always kept.
Scopes: notes:read
Response
- 200 NoteVersionList. Every version of the note.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
GET /v1/notes/{id}/versions/{versionId}
One version of a note, with its text
The title and body as they were. ?diff=true adds the word diff against the version before it: the added and removed words the apps mark.
Scopes: notes:read
Query
| parameter | type | notes | |
|---|---|---|---|
diff |
true, false |
optional | true adds the word diff against the version before it. |
Response
- 200 NoteVersionDetail. The version.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * validation_failed * rate_limited * internal
POST /v1/notes/{id}/versions/{versionId}/restore
Restore an older version
As in the apps, nothing is lost: the current text is kept as a restore version first, then the note takes that version’s title and body (which is recorded as a version of its own). Restoring the text the note already has changes nothing. A note in the trash is conflict: restore the note first.
Scopes: notes:write Idempotent. Send Idempotency-Key and a retry replays the first answer instead of writing twice.
Response
- 200 Note. The note with the restored text.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * conflict * payload_too_large * unsupported_media_type * account_too_large * rate_limited * internal
GET /v1/notes/{id}/export
Export a note as Markdown or text
The apps’ export…, as files in their layout: <folder>/<title>.md (or .txt), its files under <folder>/<title>/. Markdown carries front matter and rewrites every embed to standard Markdown pointing at the exported files; text is the title and the body with every mark taken out. The response lists the files to write and the attachments to fetch (GET /attachments/{id}/file). PDF is made by the apps only.
Scopes: notes:read
Query
| parameter | type | notes | |
|---|---|---|---|
format |
markdown, text |
optional | markdown (the default: front matter, then the body with every embed rewritten to standard Markdown) or text (the title, then the body with every mark taken out). PDF is made by the apps only. |
attachments |
include, leave_out |
optional | include (the default) lists the note’s files and pictures under attachments, at the paths the Markdown points to; leave_out writes their names as plain text instead. |
Response
- 200 NoteExport. The files of the export.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * validation_failed * rate_limited * internal
GET /v1/notes/export
Export every note
Settings › data › notes export in the apps: every live note, in its folder (<folder>/<subfolder>/<title>.md), notes in no folder under attached notes/ (with a place or a link) or inbox/. Clashing names get (2), (3). Links between notes become relative links between the files.
Scopes: notes:read
Query
| parameter | type | notes | |
|---|---|---|---|
format |
markdown, text |
optional | markdown (the default: front matter, then the body with every embed rewritten to standard Markdown) or text (the title, then the body with every mark taken out). PDF is made by the apps only. |
attachments |
include, leave_out |
optional | include (the default) lists the note’s files and pictures under attachments, at the paths the Markdown points to; leave_out writes their names as plain text instead. |
Response
- 200 NoteExport. The files of the export.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * account_too_large * validation_failed * rate_limited * internal