10 routes
/inbox
10 routes.
Capture rows (text, a link, or something shared from another app) waiting to be sorted. A row leaves the inbox by MOVING (becoming a to-do), ATTACHING (becoming an attachment on something), being ARCHIVED or DELETED (archived, with its file deleted), or being parked in someday.
- GET /v1/inbox
- POST /v1/inbox
- GET /v1/inbox/{id}
- PATCH /v1/inbox/{id}
- POST /v1/inbox/{id}/move
- POST /v1/inbox/{id}/archive
- POST /v1/inbox/{id}/someday
- POST /v1/inbox/upload
- GET /v1/inbox/{id}/file
- POST /v1/inbox/{id}/delete
GET /v1/inbox
List inbox items
Capture rows, in (sortKey, id) order. ?status=open is the untriaged inbox.
Scopes: inbox: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. |
status |
open, archived, someday, moved |
optional | |
kind |
text, url, note, file, image |
optional |
Response
- 200 InboxList. One page of capture rows.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * account_too_large * validation_failed * rate_limited * internal
POST /v1/inbox
Capture text, a link or a note (the API fetches a link’s title)
For kind: "url" the API fetches the page title itself, behind the outbound-fetch guard. A page that is unreachable, hostile or slow yields title: null and a perfectly good capture; it never fails the request, and no log line carries the URL.
Scopes: inbox:write Idempotent. Send Idempotency-Key and a retry replays the first answer instead of writing twice.
Request body
| field | type | notes | |
|---|---|---|---|
kind |
text, url, note |
required | |
text |
string | optional | max 100000 chars |
url |
string (uri) | optional | max 2048 chars, min 1. An absolute http:// or https:// URL. Other schemes (javascript:, file:, data:) are refused. |
title |
string | optional | max 512 chars. An optional heading. Without one, the apps show the first line of the body. |
whenDate |
string (yyyy-MM-dd) | optional | or null, max 10 chars. A calendar date, yyyy-MM-dd. No time and no zone, so it is the same day everywhere. |
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 InboxItem. The capture row.
titlemay still benullif the fetch has not landed.
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/inbox/{id}
One inbox item
Scopes: inbox:read
Response
- 200 InboxItem. The capture row.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
PATCH /v1/inbox/{id}
Edit a capture row’s text, url or title
The content columns only, plus whenDate. kind is not editable, because it decides which of these columns means anything, and status belongs to the actions below. after or before reorders the capture among the waiting ones (the inbox’s drag). To delete a capture as the apps do, use /inbox/{id}/delete.
Scopes: inbox:write
Request body
| field | type | notes | |
|---|---|---|---|
text |
string | optional | max 100000 chars, min 1. The body of a text capture or a note. Cannot be cleared; archive the item instead. |
url |
string (uri) | optional | max 2048 chars, min 1. The link of a url capture, an absolute http(s) URL. Cannot be cleared. |
title |
string | optional | or null, which clears it, max 512 chars. The page title of a url capture, or the filename/caption of an image or file. null clears it. |
whenDate |
string (yyyy-MM-dd) | optional | or null, which clears it, max 10 chars. The date quick-add parsed out of the capture; it rides onto the to-do a /move creates. null clears it. |
after |
string | optional | max 64 chars. Reorder: put the capture right after this OPEN capture of the inbox. |
before |
string | optional | max 64 chars. Reorder: put the capture right before this OPEN capture of the inbox. |
Response
- 200 InboxItem. The capture row 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/inbox/{id}/move
File an item: as a to-do, as a note, or onto something as an attachment
Three outcomes, decided by the item’s kind. A text capture BECOMES a to-do in the container named by parentId, or a standalone undated one when parentId is null or omitted. A note capture becomes a NOTE, under that container or standalone; it needs notes:write as well, and it never becomes a to-do. A url, image or file capture ATTACHES to the container instead, so no to-do is created and the response carries todo: null with the attachment’s id. "as": "attachment" files a TEXT capture as a text attachment of the container instead of a to-do, and todoId in place of parentId attaches a capture (any kind but a note) to a TO-DO. The container may be an area, a project or a subproject. ⚠ Only those last three need a target: they have nothing to attach to without one, and asking is validation_failed with a pointer at /parentId and a detail naming the kind. An image or file capture that has not been uploaded yet (pending: true: the bytes are still on the device that shared it) is conflict: only that device can file it, and the same call succeeds once its upload lands (an inbox.updated event). One that was already filed or deleted no longer holds its file and is validation_failed. Any other capture that was already filed (moved/attached) or archived is conflict: a capture is filed once, so a retried call never makes a duplicate; read the capture to see what it became. A parentId that names no container is validation_failed at /parentId. A link capture’s stored site icon moves to the attachment with it.
Scopes: inbox:write, todos:write
Request body
| field | type | notes | |
|---|---|---|---|
parentId |
string | optional | or null, max 64 chars. The container (an area, a project or a subproject) to sort the item onto. null or omitted means ANYTIME: a sorted to-do with no container and no date, which only a text capture can become. |
todoId |
string | optional | max 64 chars. Attach the capture to this TO-DO instead of a container (attach to…); a text capture becomes a text attachment. A NOTE capture becomes a note LINKED to this to-do (its where is the task). Not together with parentId. |
folderId |
string | optional | or null, max 64 chars. NOTE captures only: a live note folder to file the new note in. Combines with a where (parentId or todoId) or stands alone. Any other capture is refused. |
as |
todo, attachment |
optional | What a TEXT capture becomes under parentId: todo (the default, a to-do in that container) or attachment (a text attachment of the container: the move to… of the apps). A link, image or file capture always attaches; a note capture always becomes a note. |
Response
- 200 InboxMoved. The to-do that was created, or the note, or
todo: nullwith the attachment that was made.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * conflict * payload_too_large * unsupported_media_type * account_too_large * validation_failed * rate_limited * internal
POST /v1/inbox/{id}/archive
Archive an item
It leaves the inbox and stays readable at ?status=archived. A file or image it holds is kept; /inbox/{id}/delete also deletes the file.
Scopes: inbox:write
Response
- 200 InboxItem. The archived capture row.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * payload_too_large * unsupported_media_type * account_too_large * rate_limited * internal
POST /v1/inbox/{id}/someday
Park an item
Scopes: inbox:write
Response
- 200 InboxItem. The parked capture row, with
whenDatecleared.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * payload_too_large * unsupported_media_type * account_too_large * rate_limited * internal
POST /v1/inbox/upload
Capture a file or a picture into the inbox
What sharing a file or a picture to init.Tasks does: a capture of kind image or file that holds the file. Same two forms as POST /attachments/upload: the raw file as the body with name in the query (up to 100 MB), or JSON with the bytes base64 in content (about 3 MB). It goes at the end of the inbox. File it with /inbox/{id}/move like any capture: the file moves to the attachment it becomes.
Scopes: inbox:write Idempotent. Send Idempotency-Key and a retry replays the first answer instead of writing twice.
Request body
| field | type | notes | |
|---|---|---|---|
name |
string | required | max 512 chars, min 1. The filename, WITH its extension: the capture’s title. |
mime |
string | optional | max 128 chars. The media type. Omitted: taken from the extension. An image/* type makes a picture capture (kind: "image"), anything else a file. |
content |
string | required | pattern ^[A-Za-z0-9+/]*={0,2}$. The file’s bytes, base64 (a JSON body is capped at 4 MB: about 3 MB of file). Send a bigger one (up to 100 MB) as the raw body instead. |
thumbnail |
string | optional | pattern ^[A-Za-z0-9+/]*={0,2}$. Optional: a JPEG preview of at most 256 px and 64 KB, base64. |
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. |
Or a raw body: The file itself, as the whole body, with its Content-Length. Any Content-Type but application/json: the file’s own type, or application/octet-stream. Up to 100 MB. The query below says what the file is.
Query
| parameter | type | notes | |
|---|---|---|---|
name |
string | optional | max 512 chars, min 1. Raw body only (required there): the filename, WITH its extension. |
mime |
string | optional | max 128 chars. Raw body only: the media type. Omitted: the request’s Content-Type unless that is application/octet-stream, else the extension. |
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 InboxItem. The capture, holding its file: its bytes read back at
/inbox/{id}/file.
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/inbox/{id}/file
The decrypted bytes of a captured file
An image or file capture’s bytes, decrypted on your behalf, exactly as GET /attachments/{id}/file serves an attachment’s. 404 while it is pending (still on the device that shared it), once it was filed (the attachment holds it now) or deleted.
Scopes: inbox:read
Response
- 200 application/octet-stream. The decrypted bytes, served as a download with the capture’s own
mime. CarriesContent-Type,Content-Disposition,Content-Length,X-Content-Type-Options.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * account_too_large * rate_limited * internal
POST /v1/inbox/{id}/delete
Delete a capture (archive it and delete its file)
The apps’ delete on a capture: it is archived (out of the inbox, readable at ?status=archived), and a file or image it still holds is deleted from storage. A capture that was already filed is conflict: delete what it became instead.
Scopes: inbox:write
Response
- 200 InboxItem. The archived capture, holding no file.
Errors: grant_expired * grant_revoked * unauthorized * forbidden_scope * not_found * conflict * payload_too_large * unsupported_media_type * account_too_large * rate_limited * internal