Notes
Notes, with their mentions, tags, project references, images and checklists.
On this page
List or search notes
GET/notes
Notes, by last edit (add direction=desc for the newest first), or searched with q.
Filters combine (AND). A filter id that doesn't exist gives an empty page; one that isn't an ID is 400.
Search. A non-blank q lists only matching notes, best match first (title, then tag and mentioned
names, then text; ties most recently edited first), as GET /search finds them. When more than 2,000 notes
match in their title or labels, those are most recently edited first instead of ranked; text-only matches are
most recently edited first and capped at the newest 1,000. It combines with the
filters, but not with order, direction or updated_since (400). Its cursor belongs to that q and
those filters (400 otherwise) and holds a position, so notes edited between pages may shift: don't
sync with it. A blank q is ignored.
curl "$NIFTY_URL/api/v1/notes" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
qstring · optional | Words to search for; each matches a word's start. Case and accents don't matter. More than 100 characters, or a NUL, is 400. |
orderedited_at | created_at | updated_at | id · optional | edited_at is the owner's last edit; side effects such as a tag rename don't change it. |
directionasc | desc · optional | asc (ascending) or desc (descending), for order. |
category_idULID · optional | A record's ID. |
tag_idULID · optional | A record's ID. |
person_idULID · optional | Only notes that mention this person (person_ids includes it), as the web person page lists them. An unknown or deleted person is an empty page. |
project_idULID · optional | Only notes that reference this project (project_ids includes it). An unknown or deleted project is an empty page. |
pinnedboolean · optional | true: only pinned notes; false: only unpinned ones. Anything else is 400. |
fieldssummary · optional | summary: each note as a NoteSummary, without body_html and body_text but with an excerpt and image_count, for lists (much faster for long notes). Anything else is 400. |
updated_sincedate-time · optional | Only records updated at or after this time (ISO 8601 with Z or a UTC offset), ordered by updated_at, id. |
cursorstring · optional | meta.next_cursor from the previous page. |
limitinteger · optional | Items per page, up to 100; a larger number is taken as 100. |
counttrue | false · optional | true adds meta.total_count: the size of the filtered set (with updated_since too), ignoring cursor and limit. Anything but true or false is a 400. |
Response
200 OK Errors: 400 401 429
{
"data": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"body_html": "<p>Talked about the move.</p>",
"body_text": "Talked about the move.",
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"edit_version": 1,
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"meta": {
"next_cursor": "eyJrIjpbIjIwMjYtMTAtMDlUMDg6MzA6MDBaIl19",
"limit": 1,
"total_count": 1
}
}
Write a note
POST/notes
Needs a title or a body. See "Note bodies" for body_html.
curl -X POST "$NIFTY_URL/api/v1/notes" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note":{"title":"Coffee with Alice","category_id":"01j9zq3c5d7e9f1g3h5j7k9m1n","body_html":"<p>Talked about the move with <nifty-mention person-id=\"01j9zq3k8m5x2v7c4n6b0t1r9e\"></nifty-mention>. <nifty-tag tag-name=\"moving\"></nifty-tag></p>"}}'
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Request body
| Name | Description |
|---|---|
noteNoteFields | A note's writable fields (NoteInput). |
note.titlestring or null · optional | 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.
|
note.category_idULID or null · optional | An unknown id is a 422 on category_id. |
note.body_htmlstring or null · optional | See "Note bodies". Null or "" clears it. |
note.edit_versioninteger · optional | 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. |
Response
201 Created Errors: 400 401 403 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"body_html": "<p>Talked about the move.</p>",
"body_text": "Talked about the move.",
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"edit_version": 1,
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Get a note
GET/notes/{id}
curl "$NIFTY_URL/api/v1/notes/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 OK Errors: 401 404 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"body_html": "<p>Talked about the move.</p>",
"body_text": "Talked about the move.",
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"edit_version": 1,
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Change a note
PATCH/notes/{id}
Also PUT /notes/{id}, the same.
Only the fields sent change. An omitted body_html is kept; "" clears it.
Send the edit_version your changes are based on to avoid overwriting an edit made elsewhere: if the
note has been edited since, it's 409 conflict and nothing is saved. Without it, the last write wins.
curl -X PATCH "$NIFTY_URL/api/v1/notes/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note":{"title":"Coffee with Alice","category_id":"01j9zq3c5d7e9f1g3h5j7k9m1n","body_html":"<p>Talked about the move with <nifty-mention person-id=\"01j9zq3k8m5x2v7c4n6b0t1r9e\"></nifty-mention>. <nifty-tag tag-name=\"moving\"></nifty-tag></p>"}}'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Request body
| Name | Description |
|---|---|
noteNoteFields | A note's writable fields (NoteInput). |
note.titlestring or null · optional | 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.
|
note.category_idULID or null · optional | An unknown id is a 422 on category_id. |
note.body_htmlstring or null · optional | See "Note bodies". Null or "" clears it. |
note.edit_versioninteger · optional | 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. |
Response
200 OK Errors: 400 401 403 404 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"body_html": "<p>Talked about the move.</p>",
"body_text": "Talked about the move.",
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"edit_version": 1,
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Delete a note
DELETE/notes/{id}
Restorable for 20 seconds (POST /notes/{id}/restoration), then permanent. The note is gone from every
endpoint at once, with a note deletion record. Once the 20 seconds have passed, any tag no other note
uses is deleted too (with a tag deletion record). Deleting again is a 404.
curl -X DELETE "$NIFTY_URL/api/v1/notes/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
204 Deleted Errors: 401 403 404 429
Restore a note
POST/notes/{note_id}/restoration
Undo deleting a note.
Within 20 seconds of DELETE /notes/{id}: brings the note back as it was. Its deletion record is removed and
it gets a new updated_at (not edited_at). No body. Restoring a note that isn't deleted changes nothing
and returns it, so a retry is safe. Once the 20 seconds have passed, or for an unknown id, it's a 404.
curl -X POST "$NIFTY_URL/api/v1/notes/01j9zq3k8m5x2v7c4n6b0t1r9e/restoration" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
note_idULID | A record's ID. |
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Response
200 Restored. The note, as GET /notes/{id} returns it. Errors: 400 401 403 404 409 415 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"body_html": "<p>Talked about the move.</p>",
"body_text": "Talked about the move.",
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"edit_version": 1,
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Pin a note
POST/notes/{note_id}/pin
Sets pinned and pinned_at. Not an edit: the note gets a new updated_at, but edited_at and
edit_version stay, so an update based on the earlier edit_version still succeeds. No body. Pinning a
pinned note changes nothing and returns it. A deleted or unknown note is a 404.
curl -X POST "$NIFTY_URL/api/v1/notes/01j9zq3k8m5x2v7c4n6b0t1r9e/pin" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
note_idULID | A record's ID. |
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Response
200 Pinned. The note, as GET /notes/{id} returns it. Errors: 400 401 403 404 409 415 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"body_html": "<p>Talked about the move.</p>",
"body_text": "Talked about the move.",
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"edit_version": 1,
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Unpin a note
DELETE/notes/{note_id}/pin
Clears pinned and pinned_at, as pinning sets them: a new updated_at, not an edit. Unpinning a note
that isn't pinned changes nothing and returns it. A deleted or unknown note is a 404.
curl -X DELETE "$NIFTY_URL/api/v1/notes/01j9zq3k8m5x2v7c4n6b0t1r9e/pin" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
note_idULID | A record's ID. |
Response
200 Unpinned. The note, as GET /notes/{id} returns it. Errors: 401 403 404 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"body_html": "<p>Talked about the move.</p>",
"body_text": "Talked about the move.",
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"edit_version": 1,
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Tick a checklist item
PATCH/notes/{note_id}/checklist_items/{position}
Also PUT /notes/{note_id}/checklist_items/{position}, the same.
Tick or untick a checklist item in a note.
An edit, like PATCH /notes/{id}: edit_version, edited_at and updated_at move. edit_version is
required: if the note has been edited since, it's 409 conflict and nothing is saved, as the position may
name another item by then. An item already in that state saves nothing and returns the note. A position
with no item, or an unknown or deleted note, is a 404.
curl -X PATCH "$NIFTY_URL/api/v1/notes/01j9zq3k8m5x2v7c4n6b0t1r9e/checklist_items/0" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"checklist_item":{"checked":true,"edit_version":3}}'
Path parameters
| Name | Description |
|---|---|
note_idULID | A record's ID. |
positioninteger | The item's position among the body's checklist items (<li role="checkbox">), 0-based, in document order. |
Request body
| Name | Description |
|---|---|
checklist_itemobject | |
checklist_item.checkedboolean | true ticks it, false unticks it. Anything else is 400. |
checklist_item.edit_versioninteger | The note's edit_version the position was read at. Missing or not a positive integer is 400. |
Response
200 Ticked or unticked, or already so (nothing saved). The note, as GET /notes/{id} returns it. Errors: 400 401 403 404 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"body_html": "<p>Talked about the move.</p>",
"body_text": "Talked about the move.",
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"edit_version": 1,
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Note bodies
body_html is HTML. Mentions, tags, project references and images are empty elements
carrying ids:
| Attachment | Element |
|---|---|
| Mention | <nifty-mention person-id="…"> |
| Tag | <nifty-tag tag-id="…">, or <nifty-tag tag-name="Work trip"> to find or make one |
| Project | <nifty-project project-id="…"> |
| Image | <nifty-image signed-id="…"> (signed-id from POST /uploads), and an optional caption |
- What you send is cleaned with the same rules used for display: scripts,
on*attributes,javascript:links and raw<img>are removed. - The body's mentions, tags and project references set the note's
person_ids,tag_idsandproject_ids(read-only). A tag no note uses any more is deleted. - Checklists are
<ul class="checklist">lists of<li role="checkbox" aria-checked="true|false">items; tick one withPATCH /notes/{id}/checklist_items/{position}. body_textis the plain text: mentions as "@Name", tags as "#name", projects as "^Name".- Renaming, merging or deleting a tag, a person or a project gives the notes that use it a new
updated_at. - A body over 1 MB, or with a NUL, an unknown
tag-idorproject-id, or a badsigned-id, is a422.