Facts
Things you know about a person, such as their children's names or how they take their coffee. Each fact has a fact label (e.g. "Coffee"), shared by everyone; a person's facts follow the labels' order.
On this page
List fact labels
GET/fact_labels
Fact labels, in the owner's order: a person's facts follow it.
curl "$NIFTY_URL/api/v1/fact_labels" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
orderposition | id | name | updated_at · optional | The field to sort by; ties are broken by id, so the order is stable. |
directionasc | desc · optional | asc (ascending) or desc (descending), for order. |
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",
"name": "Coffee",
"icon": "coffee",
"position": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"meta": {
"next_cursor": "eyJrIjpbIjIwMjYtMTAtMDlUMDg6MzA6MDBaIl19",
"limit": 1,
"total_count": 1
}
}
Add a fact label
POST/fact_labels
Goes last unless position is sent. A name another fact label has, ignoring case and spacing, is a 422.
curl -X POST "$NIFTY_URL/api/v1/fact_labels" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fact_label":{"name":"Coffee","icon":"coffee"}}'
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 |
|---|---|
fact_labelobject | |
fact_label.namestring · optional | Spaces are squished. Unique ignoring case and spacing. |
fact_label.iconstring or null · optional | An icon from GET /category_choices, or null for none. |
fact_label.positioninteger · optional | 1-based; out-of-range values are clamped. Moves the label there. |
Response
201 Created Errors: 400 401 403 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Coffee",
"icon": "coffee",
"position": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Get a fact label
GET/fact_labels/{id}
curl "$NIFTY_URL/api/v1/fact_labels/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",
"name": "Coffee",
"icon": "coffee",
"position": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Change or move a fact label
PATCH/fact_labels/{id}
Only the fields sent change. A rename shows on every person's facts, which keep their edited_at and
updated_at. A name another fact label has, ignoring case and spacing, is a 422 (name: "already exists");
there's no merge. Sending position moves the label there and renumbers every label 1..n; the others whose
position changed get a new updated_at, so re-sync with updated_since after a move.
curl -X PATCH "$NIFTY_URL/api/v1/fact_labels/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fact_label":{"name":"Coffee","icon":"coffee"}}'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Request body
| Name | Description |
|---|---|
fact_labelobject | |
fact_label.namestring · optional | Spaces are squished. Unique ignoring case and spacing. |
fact_label.iconstring or null · optional | An icon from GET /category_choices, or null for none. |
fact_label.positioninteger · optional | 1-based; out-of-range values are clamped. Moves the label there. |
Response
200 OK Errors: 400 401 403 404 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Coffee",
"icon": "coffee",
"position": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Delete a fact label
DELETE/fact_labels/{id}
Permanent, with every fact that has it (GET /fact_label_counts says how many). Writes a fact_label
deletion record and a fact one for each of its facts. Other labels keep their positions (gaps are fine).
curl -X DELETE "$NIFTY_URL/api/v1/fact_labels/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
204 Deleted Errors: 401 403 404 429
Fact counts per fact label
GET/fact_label_counts
How many facts each fact label has, among live people.
Not a record and not paginated: there's nothing to sync, so ask again when facts change. Ordered by
fact_label_id; labels with no facts are left out.
curl "$NIFTY_URL/api/v1/fact_label_counts" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Response
200 OK Errors: 401 429
{
"data": [
{
"fact_label_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"fact_count": 1
}
]
}
List facts
GET/facts
Every live person's facts. With person_id, only that person's (at most one per label), in the order of their
fact labels by default. To keep a copy in sync, list them without person_id.
curl "$NIFTY_URL/api/v1/facts" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
person_idULID · optional | Only this person's facts. An unknown or deleted person's is an empty page. |
orderlabel | id | created_at | updated_at · optional | The field to sort by: label is the fact labels' order. Ties are broken by id, so the order is stable. The default is label with person_id, otherwise id. |
directionasc | desc · optional | asc (ascending) or desc (descending), for order. |
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",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"label_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"text": "Flat white, no sugar",
"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
}
}
Add a fact
POST/facts
Add a fact to a person, given as person_id (that can be you). Send label_id, or label: a name, found ignoring case and spacing or
created last, in the same transaction (a 422 creates neither). Both, or neither, is a 422 on label. A person
has at most one fact per label, its text holding everything under it: a label they have a fact under already is a
422 on the field sent ("already has a fact for this person…"). An unknown or deleted person is a 422 on
person_id.
curl -X POST "$NIFTY_URL/api/v1/facts" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fact":{"person_id":"01j9zq3k8m5x2v7c4n6b0t1r9e","label":"Coffee","text":"Flat white, no sugar"}}'
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 |
|---|---|
factobject | |
fact.person_idULID · optional | Create only, and required there: the person the fact is about. |
fact.label_idULID · optional | The fact label. Send this or label. |
fact.labelstring · optional | A fact label's name: found ignoring case and spacing, or created. Send this or label_id. |
fact.textstring · optional | Plain text, 1–2,000 characters; may span lines. Leading and trailing space is trimmed. |
Response
201 Created Errors: 400 401 403 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"label_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"text": "Flat white, no sugar",
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Get a fact
GET/facts/{id}
A deleted person's facts are a 404.
curl "$NIFTY_URL/api/v1/facts/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",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"label_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"text": "Flat white, no sugar",
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Change a fact
PATCH/facts/{id}
Only the fields sent change: text, and the label as label_id or label (as on create). A fact can't move to
another person, nor onto a label the person has another fact under (a 422, as on create). edited_at changes
only when the text or label does.
curl -X PATCH "$NIFTY_URL/api/v1/facts/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fact":{"person_id":"01j9zq3k8m5x2v7c4n6b0t1r9e","label":"Coffee","text":"Flat white, no sugar"}}'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Request body
| Name | Description |
|---|---|
factobject | |
fact.person_idULID · optional | Create only, and required there: the person the fact is about. |
fact.label_idULID · optional | The fact label. Send this or label. |
fact.labelstring · optional | A fact label's name: found ignoring case and spacing, or created. Send this or label_id. |
fact.textstring · optional | Plain text, 1–2,000 characters; may span lines. Leading and trailing space is trimmed. |
Response
200 OK Errors: 400 401 403 404 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"label_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"text": "Flat white, no sugar",
"edited_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Delete a fact
DELETE/facts/{id}
Permanent. Writes a fact deletion record.
curl -X DELETE "$NIFTY_URL/api/v1/facts/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
204 Deleted Errors: 401 403 404 429