Accent
The accent colour. Iris is the default.
iris | coral | jade | saffron | orchid | steel
AIModel
| Field | Type | Description |
id | ULID | A record's ID. |
ai_provider_id | ULID | A record's ID. |
identifier | string | |
name | string | |
is_default | boolean | At most one model is the default; with none, only tasks with their own model run. |
created_at | date-time | |
updated_at | date-time | |
AIModelResponse
| Field | Type | Description |
data | AIModel | |
AIProvider
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | |
kind | AIProviderKind | openai_compatible covers OpenAI, Ollama, LM Studio, llama.cpp and vLLM. mock needs no network and isn't available in production. |
base_url | string or null | |
api_key_set | boolean | Whether a key is stored. The key itself is never returned. |
last_succeeded_at | date-time or null | The last successful check, trial or AI call. |
last_failed_at | date-time or null | |
last_error | string or null | The last failure's message; cleared by a success. |
last_error_reason | string or null | last_error as a sentence fit to show, e.g. “Ollama rejected the API key.” for an HTTP 401 or 403 when a key is stored. Null when last_error is. |
created_at | date-time | |
updated_at | date-time | |
AIProviderKind
openai_compatible covers OpenAI, Ollama, LM Studio, llama.cpp and vLLM. mock needs no network and isn't available in production.
openai_compatible | anthropic | mock
AIProviderResponse
| Field | Type | Description |
data | AIProvider | |
AITask
| Field | Type | Description |
key | string | Stable, e.g. note_titles. |
name | string | |
description | string | |
enabled | boolean | Off until the owner turns it on. |
ai_model_id | ULID or null | The model the task uses; null for the default model. With neither, the task does nothing. |
reasoning | AITaskReasoning | How hard a reasoning model thinks before replying; none answers at once. Sent to OpenAI-compatible
providers as reasoning_effort; null (the default) sends nothing, leaving it to the model. Anthropic and
Mock models ignore it (the value is kept). Anything else is a 422. |
max_output_tokens | integer or null | The most tokens a reply may use (reasoning included); null for default_max_output_tokens. |
default_max_output_tokens | integer | The task's own reply budget, used while max_output_tokens is null (note titles: 2,048). |
backfill_pending | integer or null | How many records POST …/backfill would queue now; null for a task without a backfill. |
updated_at | date-time or null | The last change to its settings; null until first configured. |
AITaskReasoning
How hard a reasoning model thinks before replying; none answers at once. Sent to OpenAI-compatible
providers as reasoning_effort; null (the default) sends nothing, leaving it to the model. Anthropic and
Mock models ignore it (the value is kept). Anything else is a 422.
none | low | medium | high | null
AITaskResponse
| Field | Type | Description |
data | AITask | |
ApiToken
| Field | Type | Description |
id | integer | |
name | string | |
created_at | date-time | |
expires_at | date-time or null | |
last_used_at | date-time or null | |
Appearance
| Field | Type | Description |
accent | Accent | The accent colour. Iris is the default. |
theme | Theme | system follows the device's light or dark setting. The default. |
code_theme | CodeTheme | Code blocks in notes: match follows the theme (the default); light or dark keeps them in that scheme. |
updated_at | date-time | When the owner record last changed. |
AppearanceResponse
| Field | Type | Description |
data | Appearance | |
Avatar
Absolute URLs that redirect to the image. Anyone with a link can load it (long and unguessable;
owner decision). Thumb is 128 px square and large 256 px square, both WebP.
| Field | Type | Description |
url | uri | |
thumb_url | uri | |
large_url | uri | |
CalendarAttendee
Someone invited to an event (or its organizer). Matched to a person when the event is read, so never stale: a
live person with this email address (ignoring case), else the one live person whose name is this name
(ignoring case and accents; none when two or more share it).
| Field | Type | Description |
name | string | Their name as the feed gives it, else their email address. |
email | string or null | Their email address, lower case; null when the feed gives none. |
organizer | boolean | The event's organizer (listed first). |
person_id | ULID or null | The person they are; null when none matches. |
CalendarEvent
One occurrence of a feed's event (a repeating event gives one per instance). Read only.
| Field | Type | Description |
key | string | Opaque; the same for this occurrence across syncs. |
feed_id | ULID | A record's ID. |
title | string | Blank when the event has none. |
all_day | boolean | |
starts_at | date-time or null | Timed events only. |
ends_at | date-time or null | Timed events only; at or after starts_at. |
start_on | date or null | All-day events only. |
end_on | date or null | All-day events only: the last day, inclusive. |
location | string or null | |
description | string or null | Plain text, at most 4,000 characters. |
url | string or null | An http(s) link the event gives. |
meeting | CalendarMeeting or null | The online meeting to join, found in the event's conference properties, URL, location or description; null when none. |
attendee_count | integer | |
attendee_names | string[] | The first 10 attendees' names (else their addresses). |
attendees | CalendarAttendee[] | The first 10 attendees, the organizer first, each matched to a person when one fits. attendee_count counts
every attendee (but not an organizer who isn't one). |
rrule | string or null | The repeat rule (RFC 5545 RRULE value, e.g. FREQ=WEEKLY;BYDAY=MO); null unless it repeats. |
CalendarFeed
An iCal address whose events the calendar shows, read only. Synced; removing one records a calendar_feed
deletion. Every sync attempt gives it a new updated_at. Its events aren't records: read them with
GET /calendar.
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | |
color | string | A key from the vocabulary's colors. |
address_host | string | The address's host, shown instead of the address (which is secret). |
source | google | outlook | icloud | null | Where the calendar lives, from the host: a key from the vocabulary's calendar_sources; null when unknown. |
sync_every | CalendarSyncFrequency | How often the feed is fetched; the words are the vocabulary's calendar_sync_frequencies. |
last_synced_at | date-time or null | The last successful sync (changed or not). |
last_attempt_at | date-time or null | |
sync_error | CalendarFeedSyncError or null | Why the latest syncs failed; null while healthy. The events shown are from last_synced_at. |
created_at | date-time | |
updated_at | date-time | |
CalendarFeedResponse
| Field | Type | Description |
data | CalendarFeed | An iCal address whose events the calendar shows, read only. Synced; removing one records a calendar_feed
deletion. Every sync attempt gives it a new updated_at. Its events aren't records: read them with
GET /calendar. |
CalendarFeedSyncError
| Field | Type | Description |
code | CalendarSyncErrorCode | Why a feed couldn't be read; the words are the vocabulary's calendar_sync_errors. |
http_status | integer or null | The server's HTTP status, when it answered with one. |
failures | integer | Tries that have failed in a row. |
CalendarMeeting
| Field | Type | Description |
provider | CalendarMeetingProvider | The words are the vocabulary's meeting_providers. |
url | uri | An https link to join the meeting. |
CalendarMeetingProvider
The words are the vocabulary's meeting_providers.
google_meet | teams | zoom | webex | whereby | jitsi | goto | other
CalendarProjectDate
| Field | Type | Description |
date | date | |
kind | deadline | start | |
project | Project | Members, role changes and member removals change the project's updated_at, as does a member's person being deleted or restored. |
CalendarRange
| Field | Type | Description |
from | date | |
to | date | |
today | date | Today in the owner's time zone. |
events | CalendarEvent[] | |
tasks | Task[] | |
project_dates | CalendarProjectDate[] | |
key_dates | UpcomingKeyDate[] | |
people | Person[] | Each person an event's attendees name (person_id), once, by name. |
CalendarSyncErrorCode
Why a feed couldn't be read; the words are the vocabulary's calendar_sync_errors.
not_found | denied | server_error | unreachable | timed_out | tls | too_big | not_calendar | too_many_events | refused_address | address_unreadable | not_saved
CalendarSyncFrequency
How often the feed is fetched; the words are the vocabulary's calendar_sync_frequencies.
15_minutes | 30_minutes | hourly | 6_hours | daily
Category
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | |
color | string | A palette key from GET /category_choices (e.g. teal). Render it from your own palette. |
icon | string or null | A Lucide icon name, or null for none (show a dot). May be one no longer in the picker. |
position | integer | A sort key: order by position, then id. Gaps are possible after a delete. |
created_at | date-time | |
updated_at | date-time | |
CategoryChoices
| Field | Type | Description |
colors | object[] | |
icons | string[] | |
CategoryNoteCount
| Field | Type | Description |
category_id | ULID | A record's ID. |
note_count | integer | |
CategoryResponse
| Field | Type | Description |
data | Category | |
CodeTheme
Code blocks in notes: match follows the theme (the default); light or dark keeps them in that scheme.
match | light | dark
Company
Where people work. The name keeps its casing; matching ignores case.
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | |
created_at | date-time | |
updated_at | date-time | |
CompanyResponse
| Field | Type | Description |
data | Company | Where people work. The name keeps its casing; matching ignores case. |
Connection
One of a person's links, from their side. person_id is this person's kind.
| Field | Type | Description |
id | ULID | The person link's id. |
person_id | ULID | The other person. |
kind | PersonLinkRole | A role in a person link. Each inverse follows its forward role; the rest are symmetric. |
note | string or null | |
| Field | Type | Description |
suggestions | string[] | |
default | string or null | The label a new one starts with. |
Deletion
| Field | Type | Description |
type | string | The resource type, e.g. "person". |
id | ULID | A record's ID. |
deleted_at | date-time | |
Discovery
| Field | Type | Description |
app | "nifty" | |
version | string | The server's version; dev in an unreleased build. |
min_shell_version | string | The oldest desktop app that can open this server. |
api_versions | object[] | |
Document
A PDF. Synced; deleting one records a document deletion. Embeds no other record's data, only ids, so renaming
a folder or a linked record changes no document. Linking or unlinking it, deleting or restoring a record it's
linked to (or a list, person or project hiding a linked task), and deleting its folder while it's deleted give
it a new updated_at. Notes, projects, tasks and people don't list their documents: filter GET /documents.
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | |
folder_id | ULID or null | |
note_ids | ULID[] | Linked live notes, by id. |
project_ids | ULID[] | |
task_ids | ULID[] | |
person_ids | ULID[] | |
links | object[] | Its links to live records, for DELETE /document_links/{id}. Notes, projects, tasks, then people; each by id. |
file | object | |
created_at | date-time | |
updated_at | date-time | |
DocumentLink
A document's link to one note, project, task or person; the other three are null. Not synced (a document lists its own in links).
| Field | Type | Description |
id | ULID | A record's ID. |
document_id | ULID | A record's ID. |
note_id | ULID or null | |
project_id | ULID or null | |
task_id | ULID or null | |
person_id | ULID or null | |
created_at | date-time | |
DocumentLinkResponse
| Field | Type | Description |
data | DocumentLink | A document's link to one note, project, task or person; the other three are null. Not synced (a document lists its own in links). |
DocumentLinkWrite
document_id and exactly one of note_id, project_id, task_id and person_id.
| Field | Type | Description |
document_id | ULID | A record's ID. |
note_id optional | ULID or null | |
project_id optional | ULID or null | |
task_id optional | ULID or null | |
person_id optional | ULID or null | |
DocumentResponse
| Field | Type | Description |
data | Document | A PDF. Synced; deleting one records a document deletion. Embeds no other record's data, only ids, so renaming
a folder or a linked record changes no document. Linking or unlinking it, deleting or restoring a record it's
linked to (or a list, person or project hiding a linked task), and deleting its folder while it's deleted give
it a new updated_at. Notes, projects, tasks and people don't list their documents: filter GET /documents. |
DocumentWrite
| Field | Type | Description |
name optional | string | Spaces are squished. Needn't be unique. |
folder_id optional | ULID or null | Null: no folder. Not an id, or an unknown folder, is a 422. |
note_ids optional | ULID[] or null | Replaces its links to notes; null or [] removes them. An unknown or deleted id, or one that isn't an id, is a 422 on the field. |
project_ids optional | ULID[] or null | |
task_ids optional | ULID[] or null | A task in a deleted list, person or project counts as deleted. |
person_ids optional | ULID[] or null | |
EmailAddress
| Field | Type | Description |
id | ULID | A record's ID. |
address | string | |
label | string or null | |
created_at | date-time | |
updated_at | date-time | |
Error
| Field | Type | Description |
error | object | |
Folder
A folder of documents. Synced; deleting one records a folder deletion. No counts or paths (build them from parent_id).
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | Unique among its siblings, ignoring case. |
parent_id | ULID or null | The folder it's in; null at the top level. |
color | string | A key from the vocabulary's colors (e.g. teal). |
icon | string or null | A Lucide icon name, or null for the default folder icon. May be one no longer in the vocabulary's picker_icons. |
created_at | date-time | |
updated_at | date-time | |
FolderCount
| Field | Type | Description |
folder_id | ULID or null | The folder; null for the documents in no folder. |
document_count | integer | |
byte_size | integer | The sum of these documents' PDFs' file.byte_size. |
FolderResponse
| Field | Type | Description |
data | Folder | A folder of documents. Synced; deleting one records a folder deletion. No counts or paths (build them from parent_id). |
KeyDate
A date that comes round every year. Synced; destroying one records a key_date deletion.
| Field | Type | Description |
id | ULID | A record's ID. |
kind | KeyDateKind | |
label | string or null | Names an other date ("Sobriety"), or adds to any other kind ("Wedding"). |
day | integer | |
month | integer | |
year | integer or null | Null when unknown. |
created_at | date-time | |
updated_at | date-time | |
KeyDateKind
birthday | anniversary | work_anniversary | first_met | name_day | memorial | other
Licence
| Field | Type | Description |
state | licensed | trial | read_only | licensed: a valid key covers this version. trial: the 14-day free trial. read_only: neither, so writes get 403 read_only. |
problem | revoked | not_covered | null | Why the stored key doesn't apply: revoked, or not_covered (this version came out after its updates_until). Null without a key, or when it applies. |
licensee | Licensee or null | The stored key's owner (even with a problem); null without a key. |
key_id | string or null | The stored key's ID, for support. |
order_reference | string or null | The order the key was sold with (the payment provider's reference), when it has one; null otherwise. |
issued_on | date or null | |
updates_until | date or null | The key covers every version released until then; Nifty keeps working after it. |
trial_ends_at | date-time | The end of the free trial, 14 days after first launch (in the past once it's ended). |
trial_days_left | integer | Whole days left in the trial, rounded up; 0 once it's ended. |
released_on | date or null | When this version was released; null in a development build. |
onboarded | boolean | The welcome has been answered (or a key added). |
updated_at | date-time | |
LicenceResponse
| Field | Type | Description |
data | Licence | |
Licences
| Field | Type | Description |
groups | object[] | In display order. |
licences | object[] | |
Licensee
| Field | Type | Description |
name | string | |
email | string | |
Me
| Field | Type | Description |
name | string | |
username | string | What the owner signs in with. |
has_password | boolean | Whether the owner has a password. The desktop app's owner starts without one, so can't sign in elsewhere until one is set (PUT /password). |
time_zone | string | The owner's time zone, by its picker name, e.g. "London" or "Eastern Time (US & Canada)". |
time_zone_iana | string | The same zone's IANA identifier, e.g. "Europe/London", for Intl and other time zone libraries. |
today | date | Today's date in the owner's time zone. |
token | ApiToken or null | The token making the request; null under session auth. |
csrf_token | string or null | Under session auth, the session's current CSRF token for X-CSRF-Token (signing in again changes it); null with a bearer token. |
MeResponse
| Field | Type | Description |
data | Me | |
Note
| Field | Type | Description |
id | ULID | A record's ID. |
title | string or null | |
name | string | Read-only. What to call it in a list or a link, never blank. The title; without one, the body's first 60 characters (to a word, with mentions as @Name); else "Untitled note". |
title_generated | boolean | Read-only. The title was written by AI (the note_titles task), which keeps it current as the note
changes. Once a write changes or clears title, the title is the owner's and AI leaves it. |
category_id | ULID or null | |
tag_ids | ULID[] | Read-only, sorted. The tags in the body. |
person_ids | ULID[] | Read-only, sorted. The people mentioned in the body (their order is in body_html). |
project_ids | ULID[] | Read-only, sorted. The projects referenced in the body, deleted ones excluded. |
body_html | string | See "Note bodies". "" when empty. |
body_text | string | |
pinned | boolean | Read-only; change it with POST/DELETE /notes/{id}/pin. |
pinned_at | date-time or null | Read-only. When it was pinned; null when it isn't. |
edit_version | integer | Read-only. Goes up by one with each edited_at change; send it with an update to detect edits made elsewhere. |
edited_at | date-time | Read-only. The last change to the title, body or category through a write. |
created_at | date-time | |
updated_at | date-time | Also changes when a tag in the body is renamed, merged or deleted, a mentioned person or referenced project is renamed, deleted or restored, the category is deleted, AI writes the title (which leaves edited_at and edit_version), or the note is pinned or unpinned (likewise). |
NoteFields
A note's writable fields (NoteInput).
| Field | Type | Description |
title optional | string or null | Spaces are squished; blank is null. Send it only when the owner changed it: any change makes the
title the owner's (title_generated false), and AI no longer touches it. |
category_id optional | ULID or null | An unknown id is a 422 on category_id. |
body_html optional | string or null | See "Note bodies". Null or "" clears it. |
edit_version optional | integer | Updates only (ignored on create). The note's edit_version these changes are based on; if it has moved on, the update is 409 conflict. |
NoteResponse
| Field | Type | Description |
data | Note | |
NoteSummary
A note in a list (GET /notes?fields=summary). As Note, without body_html and body_text.
| Field | Type | Description |
id | ULID | A record's ID. |
title | string or null | |
name | string | As Note's. |
title_generated | boolean | As Note's. |
category_id | ULID or null | |
tag_ids | ULID[] | As Note's. |
person_ids | ULID[] | As Note's. |
project_ids | ULID[] | As Note's. |
excerpt | string | The body as one line (mentions "@Name", tags "#name", references "^Name", images left out), at most 240 characters, cut at a word with "...". "" when empty. |
image_count | integer | The images in the body. |
pinned | boolean | As Note's. |
pinned_at | date-time or null | As Note's. |
edit_version | integer | As Note's. |
edited_at | date-time | As Note's. |
created_at | date-time | |
updated_at | date-time | As Note's. |
| Field | Type | Description |
next_cursor | string or null | Pass as cursor for the next page; null on the last page. |
limit | integer | |
total_count optional | integer | Only with count=true. Every record in the filtered set, on every page. |
Person
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | |
avatar | Avatar or null | |
key_dates | KeyDate[] | |
email_addresses | EmailAddress[] | |
phone_numbers | PhoneNumber[] | |
social_profiles | SocialProfile[] | |
positions | Position[] | Their jobs, in the order added. Change them with the person's PATCH; company_id names a /companies record. |
relationships | RelationshipKey[] | Their relationships to the owner, in list order. |
connections | Connection[] | Read-only. Their links to other people, by link id. Change them through /person_links. |
created_at | date-time | |
updated_at | date-time | Also changes when a key date, a contact detail, a position, the photo, a relationship or a connection changes. |
PersonFields
A person's writable fields (PersonInput). Each array follows the rules on PATCH. A social
profile's platform equal to the one currently detected for it is no change, so a GET sent back as a
PATCH keeps it detected.
| Field | Type | Description |
name optional | string | |
avatar optional | string or null | The photo, saved with the rest: an upload's signed_id from POST /uploads, or null to remove it. Omitted
keeps it. Only an upload nothing uses yet is accepted; any other signed id (e.g. a note's image, or an
unknown one) is a 422 on avatar, "isn’t a new upload". The photo rules apply (a JPEG, PNG, WebP or GIF
of 10 MB or less). Anything but a string or null is a 400. A replaced or removed photo is deleted. |
relationships optional | RelationshipKeyInput[] | The whole set of relationships to the owner. Omitted keeps them; [] clears them. An unknown key is a 422. |
key_dates optional | object[] | A new row needs kind, day and month; the year is optional (1900 to this year, not in the future).
label is required for other. A new row with no day, month, year or label is ignored. At most one
birthday: to make another existing row the birthday, change the old birthday to another kind in an
earlier request (in one request it can be refused, depending on the rows' order). |
email_addresses optional | object[] | |
phone_numbers optional | object[] | |
social_profiles optional | object[] | |
positions optional | object[] | A row with id changes it, one without adds, _destroy removes. Each needs a title or a company (a new row
with neither is a 422 on title). The company changes when company_id or company_name is sent: company_id
names a /companies record (an unknown one is a 422), company_name finds a company ignoring case or adds
it, and null or a blank name clears it. Sending both non-null is a 422 on company_name. A company no
position names any more is deleted (it appears in /deletions). An id sent twice in one request is a 422 on
the later row. |
PersonLink
"person_id is related_person_id's kind". Always stored one way: kind is a forward or
symmetric role (parent, never child), and a symmetric link has the lower id in person_id.
Creating, changing or removing a link also changes both people's updated_at.
| Field | Type | Description |
id | ULID | A record's ID. |
person_id | ULID | A record's ID. |
kind | PersonLinkRole | A role in a person link. Each inverse follows its forward role; the rest are symmetric. |
related_person_id | ULID | A record's ID. |
note | string or null | |
created_at | date-time | |
updated_at | date-time | |
PersonLinkResponse
| Field | Type | Description |
data | PersonLink | "person_id is related_person_id's kind". Always stored one way: kind is a forward or
symmetric role (parent, never child), and a symmetric link has the lower id in person_id.
Creating, changing or removing a link also changes both people's updated_at. |
PersonLinkRole
A role in a person link. Each inverse follows its forward role; the rest are symmetric.
spouse | partner | ex_partner | sibling | step_sibling | cousin | sibling_in_law | parent | child | step_parent | step_child | guardian | ward | grandparent | grandchild | aunt_uncle | niece_nephew | parent_in_law | child_in_law | godparent | godchild | friend | acquaintance | housemate | neighbour | introduced | introduced_by | coworker | business_partner | manager | report | mentor | mentee | teacher | student | employer | employee | client | supplier | landlord | tenant
PersonResponse
| Field | Type | Description |
data | Person | |
PersonSummary
A person's name, photo and relationships to the owner, where a list shows them; GET /people/{id} has the rest.
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | |
avatar | Avatar or null | |
relationships | RelationshipKey[] | As Person's. |
PhoneNumber
| Field | Type | Description |
id | ULID | A record's ID. |
number | string | As typed. |
label | string or null | |
created_at | date-time | |
updated_at | date-time | |
Position
A job title, a company, or both (never neither). Synced; destroying one records a position deletion.
| Field | Type | Description |
id | ULID | A record's ID. |
title | string or null | |
company_id | ULID or null | A /companies record; its name comes from there. |
created_at | date-time | |
updated_at | date-time | |
Project
Members, role changes and member removals change the project's updated_at, as does a member's person being deleted or restored.
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | Unique among projects, ignoring case. |
status | ProjectStatus | planned, active and on_hold are open; done and cancelled are closed. |
status_changed_at | date-time or null | When the status last changed (its latest status event); null if no change is recorded. |
start_date | date or null | |
deadline | date or null | |
my_role | string or null | The owner's role on the project. |
external_reference | string or null | |
external_url | uri or null | An http or https link. |
description_html | string | Text-only HTML (no attachments), sanitised as notes are. "" when empty. |
description_text | string | |
members | object[] | Read-only, by id. The project's members who aren't deleted people; write them through /project_members. |
created_at | date-time | |
updated_at | date-time | |
ProjectActivity
One entry of a project's activity. The record named by kind is set; the others are absent.
| Field | Type | Description |
kind | note | task | document | event | created | |
at | date-time | A note's created_at, a task's completed_at, when a document was linked, an event's created_at, or the project's created_at. |
note optional | NoteSummary | A note in a list (GET /notes?fields=summary). As Note, without body_html and body_text. |
task optional | Task | Embeds no other record's data (only ids), so renaming a list changes no task. Renaming a person, project or
tag named in the description gives it a new updated_at (description_text changes), as does deleting or
restoring its home (it leaves and rejoins the listings, with a deletion record), the expiry archiving it, another
task's move renumbering it, or a document being linked or unlinked, or deleted, restored or purged while linked
(has_documents). |
document optional | Document | A PDF. Synced; deleting one records a document deletion. Embeds no other record's data, only ids, so renaming
a folder or a linked record changes no document. Linking or unlinking it, deleting or restoring a record it's
linked to (or a list, person or project hiding a linked task), and deleting its folder while it's deleted give
it a new updated_at. Notes, projects, tasks and people don't list their documents: filter GET /documents. |
event optional | ProjectEvent | A change to a project, never edited: updated_at is created_at until restoring its project (or a member event's person) gives it a new one. status: from and to are statuses.
deadline: dates, from null when one was set and to null when it was removed. member_added and
member_removed: person_id, and for member_added the role they were added with; from and to null. |
ProjectEvent
A change to a project, never edited: updated_at is created_at until restoring its project (or a member event's person) gives it a new one. status: from and to are statuses.
deadline: dates, from null when one was set and to null when it was removed. member_added and
member_removed: person_id, and for member_added the role they were added with; from and to null.
| Field | Type | Description |
id | ULID | A record's ID. |
project_id | ULID | A record's ID. |
kind | status | deadline | member_added | member_removed | |
from | string or null | A status key or a date (YYYY-MM-DD). |
to | string or null | A status key or a date (YYYY-MM-DD). |
person_id | ULID or null | |
role | string or null | |
created_at | date-time | |
updated_at | date-time | |
ProjectMember
A person on a project, once each. Synced; removing one records a project_member deletion.
| Field | Type | Description |
id | ULID | A record's ID. |
project_id | ULID | A record's ID. |
person_id | ULID | A record's ID. |
role | string or null | |
created_at | date-time | |
updated_at | date-time | |
ProjectMemberResponse
| Field | Type | Description |
data | ProjectMember | A person on a project, once each. Synced; removing one records a project_member deletion. |
ProjectResponse
| Field | Type | Description |
data | Project | Members, role changes and member removals change the project's updated_at, as does a member's person being deleted or restored. |
ProjectStatus
planned, active and on_hold are open; done and cancelled are closed.
planned | active | on_hold | done | cancelled
ProjectTaskCount
| Field | Type | Description |
project_id | ULID | A record's ID. |
open | integer | |
completed | integer | |
next_task | object or null | |
ProjectWrite
| Field | Type | Description |
name optional | string | Spaces are squished. A name another project has, ignoring case, is a 422. |
status optional | ProjectStatus | planned, active and on_hold are open; done and cancelled are closed. |
start_date optional | date or null | YYYY-MM-DD. One that isn't a date is a 422. |
deadline optional | date or null | YYYY-MM-DD; not before start_date (a 422). |
my_role optional | string or null | Spaces are squished; blank is null. |
external_reference optional | string or null | Spaces are squished; blank is null. |
external_url optional | string or null | http or https, with a host; without a scheme, https:// is added. Blank is null. |
description_html optional | string or null | HTML, sanitised as note bodies are. Attachments and images are dropped. A NUL or more than 1 MB is a 422. Null or "" clears it. |
RelationshipKey
A relationship to the owner: any person link role except introduced and introduced_by (an
introduction needs a third person), seen from the owner's side ("Ada is your mentee"). The family ones
are spouse to godchild.
spouse | partner | ex_partner | sibling | step_sibling | cousin | sibling_in_law | parent | child | step_parent | step_child | guardian | ward | grandparent | grandchild | aunt_uncle | niece_nephew | parent_in_law | child_in_law | godparent | godchild | friend | acquaintance | housemate | neighbour | coworker | business_partner | manager | report | mentor | mentee | teacher | student | employer | employee | client | supplier | landlord | tenant
A relationship key on input. The retired keys in_law and close_friend are still accepted as
sibling_in_law and friend; responses only ever use the current keys. A blank one is ignored.
RelationshipKey or in_law | close_friend or
RelationshipTypes
| Field | Type | Description |
relationships | object[] | |
person_links | object[] | |
key_date_kinds | object[] | A key date's kinds, in display order. |
social_platforms | object[] | A social profile's platforms, in display order. |
SearchGroup
| Field | Type | Description |
type | person | project | note | task | document | task_list | folder | |
results | SearchResult[] | Best match first. |
has_more | boolean | There are more matches than limit. |
SearchResult
| Field | Type | Description |
id | ULID | The person's, project's, note's, task's, document's, task list's or folder's id. |
title | string or null | A person's, project's, document's, task list's or folder's name, a note's title (null for an untitled note), or a task's title. |
subtitle | string or null | Null for a person. For a project, its status and, if it's open with a deadline, when it's due in the
owner's time zone ("Active · due today", "Active · due tomorrow", "Active · due 12 Nov", "On hold · overdue",
"Done"). For a note, the start of its text (at most 120 characters). For an open task, its home (a list's,
person's or project's name, or "Inbox") and, with a deadline, when it's due ("Errands · due Friday", "Inbox ·
overdue by 2 days"); for a closed one, how and when it closed in the owner's time zone ("Completed 3 Oct",
"Expired 3 Oct", "Won’t do 3 Oct"). For a document, its folder's path ("Home › Insurance"), "Unsorted" when
it has no folder and no links, or null. For a task list, "List". For a folder, "Folder" at the top level,
else "Folder · " and its parent's path ("Folder · Home › Insurance"). |
leading | object | What the omnibar shows before the title: a person's photo (url is its thumbnail, or null without one), a
colour swatch (a note's category colour, null without a category; a task list's or folder's colour) or a
Lucide icon by name (projects, tasks, documents). |
SocialProfile
| Field | Type | Description |
id | ULID | A record's ID. |
url | uri | |
platform | website | linkedin | instagram | facebook | x | bluesky | mastodon | threads | github | youtube | tiktok | other | |
platform_detected | boolean | True when platform was worked out from the link rather than chosen. |
label | string or null | |
created_at | date-time | |
updated_at | date-time | |
System
| Field | Type | Description |
version | string | The running version; dev in a development build. |
channel | UpdateChannel | stable (the default) gets stable releases; beta gets betas too, and stable releases newer than them. |
update_checks | boolean | Whether it checks daily (the owner's setting; false when managed). |
update_checks_managed | boolean | Updates come from elsewhere (the desktop app, a package manager: --update-checks=off), so update_checks can't change. |
update_checks_managed_by | desktop | null | Who manages updates: desktop when this is the desktop app's own server (--desktop-secret-stdin), whose updates come with the app; null otherwise. |
plain_http | boolean | It's reachable beyond this machine over plain http (no TLS). |
update | SystemUpdate | |
SystemResponse
| Field | Type | Description |
data | System | |
SystemUpdate
| Field | Type | Description |
status | up_to_date | available | not_covered | unknown | off | not_covered: a newer release is out but this install's licence doesn't cover it (not_covered_reason), so it isn't offered. |
not_covered_reason | updates_ended | null | With not_covered: updates_ended when the release came out after the licence's updates_until. Null otherwise. Revocation doesn't withhold updates: each release refuses the keys it lists (the licence's problem). |
latest_released_on | date or null | When latest_version was released; null before any check. |
can_check | boolean | This build can check (it has a release signing key and a release version) and checks aren't managed. |
latest_version | string or null | The channel's newest release at the last successful check; null before any check, or when the channel has no release yet (up_to_date). |
notes_url | uri or null | latest_version's release notes (Markdown). |
checked_at | date-time or null | The last check's time, successful or not. |
dismissed | boolean | dismissed_version is at least latest_version. |
error | string or null | Why the last check failed; null after a successful one. |
Tag
Shown with a leading #. The name keeps its casing; matching ignores case.
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | No whitespace. |
created_at | date-time | |
updated_at | date-time | |
TagResponse
| Field | Type | Description |
data | Tag | Shown with a leading #. The name keeps its casing; matching ignores case. |
Task
Embeds no other record's data (only ids), so renaming a list changes no task. Renaming a person, project or
tag named in the description gives it a new updated_at (description_text changes), as does deleting or
restoring its home (it leaves and rejoins the listings, with a deletion record), the expiry archiving it, another
task's move renumbering it, or a document being linked or unlinked, or deleted, restored or purged while linked
(has_documents).
| Field | Type | Description |
id | ULID | A record's ID. |
title | string | |
status | TaskStatus | archived is "Won't do", or expired: archived when its expires_on has passed. |
start_on | date or null | |
deadline_on | date or null | |
expires_on | date or null | |
completed_at | date-time or null | Set while completed. |
archived_at | date-time or null | Set while archived. It expired (rather than "Won't do") when this is the owner's midnight after expires_on. |
task_list_id | ULID or null | The home: at most one of task_list_id, person_id and project_id; none is the Inbox. |
person_id | ULID or null | |
project_id | ULID or null | |
position | integer | Its home's manual order: sort by position, then id. Closed tasks keep theirs. Gaps and ties are possible, and it may be zero or negative. |
description_html | string | See "Note bodies" (no images). "" when empty. |
description_text | string | |
has_documents | boolean | A live document is linked to it (GET /documents?task_id=). |
expired | boolean | Archived by its expiry rather than by hand ("Won't do"), in the owner's time zone. |
created_at | date-time | |
updated_at | date-time | |
TaskCounts
| Field | Type | Description |
inbox | integer | In the Inbox (inbox=true&status=open). |
today | integer | Started today or earlier, the Inbox too (view=today). |
due_soon | integer | Overdue or due within 14 days (view=due_soon). |
overdue | integer | Due before today. |
task_lists | object[] | Every live list, by position, with its open tasks. |
page optional | object | Only when a page is named. Its keys, in the order the page shows them (labels and icons in the vocabulary's
task_count_kinds): overview: open, overdue. today: open, completed_today. planned, one_day: open.
logbook: completed, wont_do, expired. One home: open, completed (all-time). |
TaskList
One of a task's homes. Synced; deleting one records task_list and task deletions.
| Field | Type | Description |
id | ULID | A record's ID. |
name | string | Unique among lists, ignoring case. |
color | string | A palette key from GET /category_choices (e.g. amber). |
icon | string or null | A Lucide icon name, or null for the default list icon. May be one no longer in the picker. |
position | integer | A sort key: order by position, then id. Gaps are possible after a delete. |
created_at | date-time | |
updated_at | date-time | |
TaskListResponse
| Field | Type | Description |
data | TaskList | One of a task's homes. Synced; deleting one records task_list and task deletions. |
TaskResponse
| Field | Type | Description |
data | Task | Embeds no other record's data (only ids), so renaming a list changes no task. Renaming a person, project or
tag named in the description gives it a new updated_at (description_text changes), as does deleting or
restoring its home (it leaves and rejoins the listings, with a deletion record), the expiry archiving it, another
task's move renumbering it, or a document being linked or unlinked, or deleted, restored or purged while linked
(has_documents). |
TaskStatus
archived is "Won't do", or expired: archived when its expires_on has passed.
open | completed | archived
TaskWrite
A home id that isn't an id, an unknown or deleted home, or two home ids at once (a 422 on base), are 422s.
| Field | Type | Description |
title optional | string | Spaces are squished. |
description_html optional | string or null | See "Note bodies": mentions, tags and project references by id. An image (signed-id) is a 422 on description. Null or "" clears it. |
start_on optional | date or null | YYYY-MM-DD. Today (owner's zone) or earlier puts it in view=today. One that isn't a date is a 422. |
deadline_on optional | date or null | Not before start_on (a 422), unless the deadline has already passed: an overdue task can start today. |
expires_on optional | date or null | Not before start_on or deadline_on, nor, on an open task, before today (each a 422). The task stays
open through that day and is archived from the next, with archived_at that midnight in the owner's zone. |
status optional | TaskStatus | Any status can change to any other. completed sets completed_at, archived sets archived_at, open
clears both, and reopening a task whose expires_on has passed clears it. Sending the current status
changes nothing. |
task_list_id optional | ULID or null | The home. Sending one home id clears the other two; all three null is the Inbox. |
person_id optional | ULID or null | |
project_id optional | ULID or null | |
position optional | integer | 1-based among the (new) home's open tasks; out-of-range values are clamped. |
document_ids optional | ULID[] or null | The task's documents after the save: the full set, linked and unlinked in the save's transaction. Omitted
leaves them as they are; null or [] unlinks them all. An unknown or deleted one is a 422 on documents,
and nothing saves. Not an array of ids is a 400. |
document_uploads optional | string[] or null | signed_ids of PDFs from POST /uploads, each becoming a document (named from its filename) linked to the
task, in the save. One that isn't an unused PDF upload is a 422 on documents, and nothing saves. |
Theme
system follows the device's light or dark setting. The default.
system | light | dark
Today
| Field | Type | Description |
date | date | Today in the owner's time zone. |
tasks_today | integer | Open, started today or earlier (view=today). |
tasks_due_not_today | integer | Overdue or due within 14 days, not in today's (view=due_not_today). |
tasks_inbox | integer | Open in the Inbox. |
key_dates_in_fortnight | integer | Live people's key dates falling from today to day 14. |
deadlines_in_fortnight | integer | Open projects due from today to day 14. |
overdue_deadlines | integer | Open projects due before today. |
notes_this_week | integer | Notes edited since the start of six days ago. |
pinned_notes | integer | |
ULID
A record's ID.
string
UpcomingDeadline
| Field | Type | Description |
date | date | The project's deadline. |
overdue | boolean | The deadline is before today. |
project | Project | Members, role changes and member removals change the project's updated_at, as does a member's person being deleted or restored. |
UpcomingKeyDate
| Field | Type | Description |
date | date | |
years | integer or null | The years it marks (the age a birthday turns). Null without a year, or on the first one. |
key_date | KeyDate | A date that comes round every year. Synced; destroying one records a key_date deletion. |
person | PersonSummary | A person's name, photo and relationships to the owner, where a list shows them; GET /people/{id} has the rest. |
UpdateChannel
stable (the default) gets stable releases; beta gets betas too, and stable releases newer than them.
stable | beta
Upload
| Field | Type | Description |
signed_id | string | The signed-id to embed in a note body, or send in a task's document_uploads. |
url | uri | Absolute; redirects to the file. Anyone with the link can load it. |
filename | string | |
content_type | image/jpeg | image/png | image/webp | image/gif | application/pdf | |
byte_size | integer | |
width | integer or null | Null for a PDF. |
height | integer or null | |
UploadLimit
| Field | Type | Description |
content_types | string[] | |
max_bytes | integer | |
Vocabulary
relationships, person_links, key_date_kinds and social_platforms are as in RelationshipTypes.
any
A whole number, or one written as a string ("5"); null or "" is none.
integer or null or string