People
The people you know, with their key dates, contact details and positions.
On this page
List people
GET/people
People, with their key dates and contact details.
curl "$NIFTY_URL/api/v1/people" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
ordername | created_at | updated_at | id · 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. |
relationshipRelationshipKeyInput or family · optional | Only people with this relationship to the owner, or any family one (family). 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",
"name": "Maya Okafor",
"avatar": {
"url": "https://example.com",
"thumb_url": "https://example.com",
"large_url": "https://example.com"
},
"key_dates": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "birthday",
"label": "Graduation",
"day": 1,
"month": 1,
"year": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"email_addresses": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"address": "[email protected]",
"label": "Work",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"phone_numbers": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"number": "+44 20 7946 0958",
"label": "Mobile",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"social_profiles": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"url": "https://example.com",
"platform": "website",
"platform_detected": true,
"label": "Blog",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"positions": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Head of Design",
"company_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"relationships": [
"spouse"
],
"connections": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "spouse",
"note": "Met at university"
}
],
"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 person
POST/people
Add a person, with any key dates, contact details and positions.
curl -X POST "$NIFTY_URL/api/v1/people" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"person":{"name":"Alice Hart","relationships":["friend"],"key_dates":[{"kind":"birthday","day":14,"month":3,"year":1988}],"email_addresses":[{"address":"[email protected]","label":"home"}],"positions":[{"title":"Head of Design","company_name":"Northwind"}]}}'
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 |
|---|---|
personPersonFields | 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.
|
person.namestring · optional | |
person.avatarstring or null · optional | 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.
|
person.relationshipsRelationshipKeyInput[] · optional | The whole set of relationships to the owner. Omitted keeps them; [] clears them. An unknown key is a 422. |
person.key_datesobject[] · optional | 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).
|
person.key_dates[].idULID · optional | A record's ID. |
person.key_dates[].kindKeyDateKind · optional | |
person.key_dates[].labelstring or null · optional | |
person.key_dates[].dayWholeNumberInput · optional | A whole number, or one written as a string ("5"); null or "" is none. |
person.key_dates[].monthWholeNumberInput · optional | A whole number, or one written as a string ("5"); null or "" is none. |
person.key_dates[].yearWholeNumberInput · optional | A whole number, or one written as a string ("5"); null or "" is none. |
person.key_dates[]._destroyboolean · optional | |
person.email_addressesobject[] · optional | |
person.email_addresses[].idULID · optional | A record's ID. |
person.email_addresses[].addressstring · optional | |
person.email_addresses[].labelstring or null · optional | |
person.email_addresses[]._destroyboolean · optional | |
person.phone_numbersobject[] · optional | |
person.phone_numbers[].idULID · optional | A record's ID. |
person.phone_numbers[].numberstring · optional | |
person.phone_numbers[].labelstring or null · optional | |
person.phone_numbers[]._destroyboolean · optional | |
person.social_profilesobject[] · optional | |
person.social_profiles[].idULID · optional | A record's ID. |
person.social_profiles[].urlstring · optional | https:// is added when there's no scheme. |
person.social_profiles[].platformwebsite | linkedin | instagram | facebook | x | bluesky | mastodon | threads | github | youtube | tiktok | other | null · optional | Null (or omitted) detects it from the link. |
person.social_profiles[].labelstring or null · optional | |
person.social_profiles[]._destroyboolean · optional | |
person.positionsobject[] · optional | 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.
|
person.positions[].idULID · optional | A record's ID. |
person.positions[].titlestring or null · optional | Spaces are squished; blank is null. |
person.positions[].company_idULID or null · optional | A record's ID. |
person.positions[].company_namestring or null · optional | Spaces are squished. |
person.positions[]._destroyboolean · optional |
Response
201 Created Errors: 400 401 403 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Maya Okafor",
"avatar": {
"url": "https://example.com",
"thumb_url": "https://example.com",
"large_url": "https://example.com"
},
"key_dates": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "birthday",
"label": "Graduation",
"day": 1,
"month": 1,
"year": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"email_addresses": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"address": "[email protected]",
"label": "Work",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"phone_numbers": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"number": "+44 20 7946 0958",
"label": "Mobile",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"social_profiles": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"url": "https://example.com",
"platform": "website",
"platform_detected": true,
"label": "Blog",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"positions": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Head of Design",
"company_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"relationships": [
"spouse"
],
"connections": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "spouse",
"note": "Met at university"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Get a person
GET/people/{id}
curl "$NIFTY_URL/api/v1/people/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": "Maya Okafor",
"avatar": {
"url": "https://example.com",
"thumb_url": "https://example.com",
"large_url": "https://example.com"
},
"key_dates": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "birthday",
"label": "Graduation",
"day": 1,
"month": 1,
"year": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"email_addresses": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"address": "[email protected]",
"label": "Work",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"phone_numbers": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"number": "+44 20 7946 0958",
"label": "Mobile",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"social_profiles": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"url": "https://example.com",
"platform": "website",
"platform_detected": true,
"label": "Blog",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"positions": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Head of Design",
"company_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"relationships": [
"spouse"
],
"connections": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "spouse",
"note": "Met at university"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Change a person
PATCH/people/{id}
Also PUT /people/{id}, the same.
Change a person, their key dates, contact details and positions.
Only the fields sent change. In each key-date, contact-detail and position array, a row with an id updates that row, one
without adds a row, and "_destroy": true removes one; rows not sent are kept. An id that isn't
this person's is a 422 on that row (removing one that's already gone is fine). avatar sets or removes the
photo in the same save.
curl -X PATCH "$NIFTY_URL/api/v1/people/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"person":{"name":"Alice Hart","relationships":["friend"],"key_dates":[{"kind":"birthday","day":14,"month":3,"year":1988}],"email_addresses":[{"address":"[email protected]","label":"home"}],"positions":[{"title":"Head of Design","company_name":"Northwind"}]}}'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Request body
| Name | Description |
|---|---|
personPersonFields | 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.
|
person.namestring · optional | |
person.avatarstring or null · optional | 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.
|
person.relationshipsRelationshipKeyInput[] · optional | The whole set of relationships to the owner. Omitted keeps them; [] clears them. An unknown key is a 422. |
person.key_datesobject[] · optional | 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).
|
person.key_dates[].idULID · optional | A record's ID. |
person.key_dates[].kindKeyDateKind · optional | |
person.key_dates[].labelstring or null · optional | |
person.key_dates[].dayWholeNumberInput · optional | A whole number, or one written as a string ("5"); null or "" is none. |
person.key_dates[].monthWholeNumberInput · optional | A whole number, or one written as a string ("5"); null or "" is none. |
person.key_dates[].yearWholeNumberInput · optional | A whole number, or one written as a string ("5"); null or "" is none. |
person.key_dates[]._destroyboolean · optional | |
person.email_addressesobject[] · optional | |
person.email_addresses[].idULID · optional | A record's ID. |
person.email_addresses[].addressstring · optional | |
person.email_addresses[].labelstring or null · optional | |
person.email_addresses[]._destroyboolean · optional | |
person.phone_numbersobject[] · optional | |
person.phone_numbers[].idULID · optional | A record's ID. |
person.phone_numbers[].numberstring · optional | |
person.phone_numbers[].labelstring or null · optional | |
person.phone_numbers[]._destroyboolean · optional | |
person.social_profilesobject[] · optional | |
person.social_profiles[].idULID · optional | A record's ID. |
person.social_profiles[].urlstring · optional | https:// is added when there's no scheme. |
person.social_profiles[].platformwebsite | linkedin | instagram | facebook | x | bluesky | mastodon | threads | github | youtube | tiktok | other | null · optional | Null (or omitted) detects it from the link. |
person.social_profiles[].labelstring or null · optional | |
person.social_profiles[]._destroyboolean · optional | |
person.positionsobject[] · optional | 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.
|
person.positions[].idULID · optional | A record's ID. |
person.positions[].titlestring or null · optional | Spaces are squished; blank is null. |
person.positions[].company_idULID or null · optional | A record's ID. |
person.positions[].company_namestring or null · optional | Spaces are squished. |
person.positions[]._destroyboolean · optional |
Response
200 OK Errors: 400 401 403 404 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Maya Okafor",
"avatar": {
"url": "https://example.com",
"thumb_url": "https://example.com",
"large_url": "https://example.com"
},
"key_dates": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "birthday",
"label": "Graduation",
"day": 1,
"month": 1,
"year": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"email_addresses": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"address": "[email protected]",
"label": "Work",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"phone_numbers": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"number": "+44 20 7946 0958",
"label": "Mobile",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"social_profiles": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"url": "https://example.com",
"platform": "website",
"platform_detected": true,
"label": "Blog",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"positions": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Head of Design",
"company_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"relationships": [
"spouse"
],
"connections": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "spouse",
"note": "Met at university"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Delete a person
DELETE/people/{id}
Delete a person, their key dates, contact details, positions and links.
Restorable for 20 seconds (POST /people/{id}/restoration), then permanent. The person is gone from
every endpoint at once, and deletion records are written for the person, each key date, contact detail and position,
each person link (either side) whose other person isn't deleted too, and each project membership whose
project isn't deleted. The other person in each link, their projects (members changes), and notes
mentioning them get a new updated_at (notes drop them from person_ids). Once the 20 seconds
have passed, each mention becomes plain text "@Name" and those notes get a new updated_at (but not
edited_at). Deleting again is a 404.
curl -X DELETE "$NIFTY_URL/api/v1/people/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 person
POST/people/{person_id}/restoration
Undo deleting a person.
Within 20 seconds of DELETE /people/{id}: brings the person back with everything they had (contact
details, positions, links, project memberships, photo, mentions). Their deletion records are removed, and they, their links,
the people they're linked to, their memberships and projects, and the notes mentioning them get a new
updated_at. No body. Restoring someone who isn't
deleted changes nothing and returns them, 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/people/01j9zq3k8m5x2v7c4n6b0t1r9e/restoration" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
person_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 person, as GET /people/{id} returns them. Errors: 400 401 403 404 409 415 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Maya Okafor",
"avatar": {
"url": "https://example.com",
"thumb_url": "https://example.com",
"large_url": "https://example.com"
},
"key_dates": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "birthday",
"label": "Graduation",
"day": 1,
"month": 1,
"year": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"email_addresses": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"address": "[email protected]",
"label": "Work",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"phone_numbers": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"number": "+44 20 7946 0958",
"label": "Mobile",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"social_profiles": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"url": "https://example.com",
"platform": "website",
"platform_detected": true,
"label": "Blog",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"positions": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Head of Design",
"company_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"relationships": [
"spouse"
],
"connections": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "spouse",
"note": "Met at university"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
List a person's meetings
GET/people/{person_id}/meetings
A person's upcoming meetings.
The calendar feeds' event occurrences the person attends (see CalendarAttendee for how attendees are
matched), from now to 60 days from today in the owner's time zone: those not yet ended (an all-day one through
its last day), soonest first, then all-day ones first, then title. Not paginated: limit bounds it, and
meta.has_more says whether there are more in the 60 days. A view, not a record: nothing to sync.
curl "$NIFTY_URL/api/v1/people/01j9zq3k8m5x2v7c4n6b0t1r9e/meetings" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
person_idULID | A record's ID. |
Query parameters
| Name | Description |
|---|---|
limitinteger · optional | At most this many; otherwise 400. |
Response
200 OK Errors: 400 401 404 429
{
"data": [
{
"key": "string",
"feed_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Team stand-up",
"all_day": true,
"starts_at": "2026-10-10T09:30:00Z",
"ends_at": "2026-10-10T09:30:00Z",
"start_on": "2026-10-10",
"end_on": "2026-10-10",
"location": "Room 2",
"description": "Weekly check-in.",
"url": "string",
"meeting": {
"provider": "google_meet",
"url": "https://example.com"
},
"attendee_count": 0,
"attendee_names": [
"string"
],
"attendees": [
{
"name": "Maya Okafor",
"email": "[email protected]",
"organizer": true,
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"rrule": "FREQ=WEEKLY;BYDAY=MO,WE"
}
],
"meta": {
"limit": 1,
"has_more": true
}
}
Set a person's photo
PUT/people/{person_id}/avatar
Also PATCH /people/{person_id}/avatar, the same.
The body is the image itself, up to 10 MB, with a Content-Length (without one it's 411 length_required). The type is read from the bytes; the Content-Type must still be one of the image
types below (anything else is 415). Replaces any existing photo.
curl -X PUT "$NIFTY_URL/api/v1/people/01j9zq3k8m5x2v7c4n6b0t1r9e/avatar" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '"@file.pdf"'
Path parameters
| Name | Description |
|---|---|
person_idULID | A record's ID. |
Response
200 OK Errors: 401 403 404 411 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Maya Okafor",
"avatar": {
"url": "https://example.com",
"thumb_url": "https://example.com",
"large_url": "https://example.com"
},
"key_dates": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "birthday",
"label": "Graduation",
"day": 1,
"month": 1,
"year": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"email_addresses": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"address": "[email protected]",
"label": "Work",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"phone_numbers": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"number": "+44 20 7946 0958",
"label": "Mobile",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"social_profiles": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"url": "https://example.com",
"platform": "website",
"platform_detected": true,
"label": "Blog",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"positions": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Head of Design",
"company_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"relationships": [
"spouse"
],
"connections": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "spouse",
"note": "Met at university"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Remove a person's photo
DELETE/people/{person_id}/avatar
Succeeds when there's no photo too.
curl -X DELETE "$NIFTY_URL/api/v1/people/01j9zq3k8m5x2v7c4n6b0t1r9e/avatar" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
person_idULID | A record's ID. |
Response
200 OK Errors: 401 403 404 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Maya Okafor",
"avatar": {
"url": "https://example.com",
"thumb_url": "https://example.com",
"large_url": "https://example.com"
},
"key_dates": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "birthday",
"label": "Graduation",
"day": 1,
"month": 1,
"year": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"email_addresses": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"address": "[email protected]",
"label": "Work",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"phone_numbers": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"number": "+44 20 7946 0958",
"label": "Mobile",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"social_profiles": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"url": "https://example.com",
"platform": "website",
"platform_detected": true,
"label": "Blog",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"positions": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Head of Design",
"company_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"relationships": [
"spouse"
],
"connections": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "spouse",
"note": "Met at university"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
List upcoming key dates
GET/upcoming_key_dates
Key dates (birthdays, anniversaries…) that fall from today to days from today, inclusive, in the owner's
time zone, ordered by date, then person name, then kind. Not paginated (days bounds it). 29 February
falls on 1 March in non-leap years.
curl "$NIFTY_URL/api/v1/upcoming_key_dates" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
daysinteger · optional | |
kindstring · optional | Comma-separated kinds, e.g. birthday,anniversary. Omitted gives every kind. |
Response
200 OK Errors: 400 401 429
{
"data": [
{
"date": "2026-10-10",
"years": 1,
"key_date": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "birthday",
"label": "Graduation",
"day": 1,
"month": 1,
"year": 1,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
},
"person": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Maya Okafor",
"avatar": {
"url": "https://example.com",
"thumb_url": "https://example.com",
"large_url": "https://example.com"
},
"relationships": [
"spouse"
]
}
}
]
}
List relationship and key date types
GET/relationship_types
The relationship, key date and social platform types, with labels.
Deprecated, use GET /vocabulary, which has these four lists too. Not paginated. Relationship and person link labels are lower case, read as "Ada is your <label>" and "Alice is Bob's <label>"; key date kind and platform labels are as the web UI shows them ("Work anniversary", "LinkedIn").
curl "$NIFTY_URL/api/v1/relationship_types" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Response
200 OK Errors: 401 429
{
"data": {
"relationships": [
{
"key": "spouse",
"label": "direct report",
"group": "family"
}
],
"person_links": [
{
"key": "spouse",
"label": "parent",
"group": "family",
"inverse_key": "spouse",
"inverse_label": "child",
"symmetric": true,
"synonyms": [
"mother",
"father",
"mum",
"mom",
"dad"
]
}
],
"key_date_kinds": [
{
"key": "birthday",
"label": "Work anniversary",
"icon": "cake"
}
],
"social_platforms": [
{
"key": "website",
"label": "LinkedIn"
}
]
}
}
Relationships
A person's relationships are how they relate to you, as keys from GET /vocabulary (the
person link roles except introduced and introduced_by). Links between two people are /person_links.
Every kind names the role of the person in the record's person_id: the link
{ person_id: A, kind: "parent", related_person_id: B } reads "A is B's parent".