Projects
Projects, their timeline and their deadlines.
On this page
List projects
GET/projects
Projects, with their members.
Filters combine (AND). Filters are for browsing; sync unfiltered.
curl "$NIFTY_URL/api/v1/projects" \
-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. |
statusstring · optional | Comma-separated statuses, e.g. active,on_hold. Anything else is 400. |
person_idULID · optional | Only projects this person is a member of. An unknown or deleted person is an empty page. |
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": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_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 project
POST/projects
Needs a name. A new project is active unless status says otherwise. Add members with POST /project_members.
curl -X POST "$NIFTY_URL/api/v1/projects" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"project":{"name":"Kitchen renovation","status":"active","start_date":"2026-09-01","deadline":"2026-12-18"}}'
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 |
|---|---|
projectProjectWrite | |
project.namestring · optional | Spaces are squished. A name another project has, ignoring case, is a 422. |
project.statusProjectStatus · optional | planned, active and on_hold are open; done and cancelled are closed. |
project.start_datedate or null · optional | YYYY-MM-DD. One that isn't a date is a 422. |
project.deadlinedate or null · optional | YYYY-MM-DD; not before start_date (a 422). |
project.external_referencestring or null · optional | Spaces are squished; blank is null. |
project.external_urlstring or null · optional | http or https, with a host; without a scheme, https:// is added. Blank is null. |
project.description_htmlstring or null · optional | 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. |
project.coverCoverWrite · optional | A cover to set. Every project wears one, so null is a 422 on cover. Kind color needs a color; kind
photo keeps the photo the cover has (upload a new one with PUT /projects/{id}/cover_photo), so it's for
moving position. An unknown colour, a photo when there's none, or a position out of range is a 422 on
cover.<field>. A project created without one gets one of the colours its projects use least, at random. A
photo a colour replaces is deleted.
|
Response
201 Created Errors: 400 401 403 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Set a project's cover photo
PUT/projects/{id}/cover_photo
The body is the image itself, up to 20 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). It becomes the project's cover, framed in the middle, replacing any cover; a
photo it replaces is deleted. Choose a colour instead through the project's cover.
curl -X PUT "$NIFTY_URL/api/v1/projects/01j9zq3k8m5x2v7c4n6b0t1r9e/cover_photo" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '"@file.pdf"'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 OK Errors: 401 403 404 411 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Set a project's cover to an Unsplash photo
PUT/projects/{id}/cover_photo/unsplash
A Nifty Plus feature. Downloads the Unsplash photo (GET /unsplash/photos's id), telling Unsplash it was
used, and makes it the project's cover as PUT /projects/{id}/cover_photo does, with its credit. A photo
Unsplash no longer has is a 422 on photo_id.
curl -X PUT "$NIFTY_URL/api/v1/projects/01j9zq3k8m5x2v7c4n6b0t1r9e/cover_photo/unsplash" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"photo_id":"Dwu85P9SOIk"}'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Request body
| Name | Description |
|---|---|
photo_idstring |
Response
200 OK Errors: 400 401 403 404 422 429 502
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Get a project
GET/projects/{id}
curl "$NIFTY_URL/api/v1/projects/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": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Change a project
PATCH/projects/{id}
Only the fields sent change. Renaming it gives the notes referencing it a new updated_at (their body_text changes).
curl -X PATCH "$NIFTY_URL/api/v1/projects/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"project":{"name":"Kitchen renovation","status":"active","start_date":"2026-09-01","deadline":"2026-12-18"}}'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Request body
| Name | Description |
|---|---|
projectProjectWrite | |
project.namestring · optional | Spaces are squished. A name another project has, ignoring case, is a 422. |
project.statusProjectStatus · optional | planned, active and on_hold are open; done and cancelled are closed. |
project.start_datedate or null · optional | YYYY-MM-DD. One that isn't a date is a 422. |
project.deadlinedate or null · optional | YYYY-MM-DD; not before start_date (a 422). |
project.external_referencestring or null · optional | Spaces are squished; blank is null. |
project.external_urlstring or null · optional | http or https, with a host; without a scheme, https:// is added. Blank is null. |
project.description_htmlstring or null · optional | 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. |
project.coverCoverWrite · optional | A cover to set. Every project wears one, so null is a 422 on cover. Kind color needs a color; kind
photo keeps the photo the cover has (upload a new one with PUT /projects/{id}/cover_photo), so it's for
moving position. An unknown colour, a photo when there's none, or a position out of range is a 422 on
cover.<field>. A project created without one gets one of the colours its projects use least, at random. A
photo a colour replaces is deleted.
|
Response
200 OK Errors: 400 401 403 404 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Delete a project
DELETE/projects/{id}
Delete a project and its members.
Restorable for 10 seconds (POST /projects/{id}/restore), then permanent. The project is gone from
every endpoint at once, with deletion records for the project and each member who isn't a deleted
person. Notes referencing it get a new updated_at (they drop it from project_ids). Once the 10 seconds have
passed, each reference becomes plain text "^Name" and those notes get a new updated_at (but not
edited_at). Its name is free for a new project at once. Deleting again is a 404.
curl -X DELETE "$NIFTY_URL/api/v1/projects/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 project
POST/projects/{id}/restore
Undo deleting a project.
Within 10 seconds of DELETE /projects/{id}: brings the project back with its description, members and
references. Its deletion records are removed, and it, its members and the notes referencing it get a new
updated_at. If another project took its name meanwhile, it comes back renamed "Name (2)" (the first free
number), so check name. No body. Restoring a project that isn't deleted changes nothing and returns it,
so a retry is safe. Once the 10 seconds have passed, or for an unknown id, it's a 404. If other projects take
the name it picks twice in a row, it's a 422 on name and the project stays deleted; try again.
curl -X POST "$NIFTY_URL/api/v1/projects/01j9zq3k8m5x2v7c4n6b0t1r9e/restore" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
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 project, as GET /projects/{id} returns it. Errors: 400 401 403 404 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Pin a project
PUT/projects/{id}/pin
Sets pinned and pinned_at (the app lists pinned projects in its Projects menu), and gives the project a new updated_at. No body. Pinning
a pinned project changes nothing and returns it. A deleted or unknown project is a 404.
curl -X PUT "$NIFTY_URL/api/v1/projects/01j9zq3k8m5x2v7c4n6b0t1r9e/pin" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 Pinned. The project, as GET /projects/{id} returns it. Errors: 401 403 404 415 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Unpin a project
DELETE/projects/{id}/pin
Clears pinned and pinned_at, as pinning sets them. Unpinning a project that isn't pinned changes nothing
and returns it. A deleted or unknown project is a 404.
curl -X DELETE "$NIFTY_URL/api/v1/projects/01j9zq3k8m5x2v7c4n6b0t1r9e/pin" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 Unpinned. The project, as GET /projects/{id} returns it. Errors: 401 403 404 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
List a project's activity
GET/projects/{id}/activity
What happened on a project, newest first.
One feed of the project's notes (live ones referencing it, at their created_at), completed tasks (live ones
homed in it, at completed_at; reopening one removes it), linked documents (live ones, at the link's
created_at; unlinking removes it), events (/project_events; a deleted person's member events are left out
until they're restored) and, last, its creation. Newest first; entries at the same time go events, tasks,
documents, notes, then created. Not a record: there's nothing to sync here (sync the records themselves).
An unknown or deleted project is a 404.
curl "$NIFTY_URL/api/v1/projects/01j9zq3k8m5x2v7c4n6b0t1r9e/activity" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Query parameters
| Name | Description |
|---|---|
cursorstring · optional | meta.next_cursor from the previous page. |
limitinteger · optional | Items per page, up to 100; a larger number is taken as 100. |
Response
200 OK Errors: 400 401 404 429
{
"data": [
{
"kind": "note",
"at": "2026-10-10T09:30:00Z",
"note": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Coffee with Maya",
"name": "Coffee with Maya",
"title_generated": true,
"category_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"mood": "anxious",
"kind": "meeting",
"kind_source": "owner",
"tag_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"excerpt": "Talked about the move.",
"image_count": 0,
"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"
},
"task": {
"planned_start_on": "2026-10-10",
"delays": 0,
"deadline_moves": 0,
"delegated_at": "2026-10-10T09:30:00Z",
"now_since": "2026-10-10T09:30:00Z",
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "Book the plumber",
"status": "open",
"start_on": "2026-10-10",
"deadline_on": "2026-10-10",
"expires_on": "2026-10-10",
"completed_at": "2026-10-10T09:30:00Z",
"archived_at": "2026-10-10T09:30:00Z",
"task_list_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"assignee_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"position": 1,
"description_html": "<p>Ask about the boiler too.</p>",
"description_text": "Ask about the boiler too.",
"has_documents": true,
"document_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"expired": true,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
},
"task_change": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"change": "added",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"title": "string"
},
"document": {
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "pdf",
"name": "Boiler warranty",
"dated_on": "2026-10-10",
"dated_on_set": true,
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"to": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"cc": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"sent_at": "2026-10-10T09:30:00Z",
"forwarded": true,
"forward_note": "string",
"forwarded_at": "2026-10-10T09:30:00Z",
"has_html": true,
"remote_images": true,
"body_url": "https://example.com",
"attachments": [
{
"filename": "string",
"content_type": "application/pdf",
"byte_size": 1,
"status": "saved",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
]
},
"from_email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"sent_at": "2026-10-10T09:30:00Z",
"forwarded_at": "2026-10-10T09:30:00Z"
},
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"task_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"links": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"file": {
"filename": "string",
"byte_size": 1,
"content_type": "application/pdf",
"width": 1,
"height": 1,
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"analysis": {
"status": "reading",
"type": "payslip",
"type_label": "Payslip"
},
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
},
"event": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "status",
"from": "string",
"to": "string",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "Builder",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
],
"meta": {
"next_cursor": "eyJrIjpbIjIwMjYtMTAtMDlUMDg6MzA6MDBaIl19",
"limit": 1,
"total_count": 1
}
}
List project changes
GET/project_events
Changes to projects' status, deadline and members.
Recorded as a project's status or deadline changes and as members are added or removed; never written
through the API, and never edited. A status or deadline change within 10 minutes of the project's last of
the same kind replaces it: that event is deleted (with a deletion record) and the new one runs from its from, or
neither stays if it ends where that one began. Events of live projects, without a deleted person's member events.
Deleting a project records a project_event deletion record for each of its events, and deleting a person for
each of their member events; undoing the delete removes them and gives the events a new updated_at. For
sync; GET /projects/{id}/activity shows them with the rest.
curl "$NIFTY_URL/api/v1/project_events" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
project_idULID · optional | A record's ID. |
orderid | created_at | 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",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "status",
"from": "string",
"to": "string",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "Builder",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"meta": {
"next_cursor": "eyJrIjpbIjIwMjYtMTAtMDlUMDg6MzA6MDBaIl19",
"limit": 1,
"total_count": 1
}
}
List upcoming deadlines
GET/upcoming_deadlines
Upcoming project deadlines.
Open projects (planned, active, on_hold) due from today to days from today, inclusive, and every
overdue one, ordered by deadline, then name, then id; with limit alone, the nearest limit open
deadlines with no window. Today is in the owner's time zone. Not paginated.
curl "$NIFTY_URL/api/v1/upcoming_deadlines" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
daysinteger · optional | The window in days. Without days or limit, 30 (as /upcoming_key_dates); with limit alone, no
window.
|
limitinteger · optional | At most this many. With limit and no days: the nearest limit open deadlines, whatever the date,
overdue first. With both, both apply.
|
Response
200 OK Errors: 400 401 429
{
"data": [
{
"date": "2026-10-10",
"overdue": true,
"project": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Kitchen renovation",
"status": "planned",
"status_changed_at": "2026-10-10T09:30:00Z",
"start_date": "2026-10-10",
"deadline": "2026-10-10",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"cover": {
"kind": "color",
"color": "dusk",
"photo": {
"url": "https://example.com",
"preview_url": "https://example.com",
"thumb_url": "https://example.com",
"width": 1,
"height": 1,
"credit": {
"name": "string",
"profile_url": "https://example.com",
"photo_url": "https://example.com"
}
},
"position": 0
},
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"pinned": true,
"pinned_at": "2026-10-10T09:30:00Z",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
]
}
Each project's last activity
GET/project_last_activity
When each live project's newest activity entry happened (GET /projects/{id}/activity's first at; its
creation when nothing has happened since), for ordering Projects by last activity. Every live project, by id.
Not paginated, and not a record: ask again when projects, notes, tasks or documents change.
curl "$NIFTY_URL/api/v1/project_last_activity" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Response
200 OK Errors: 401 429
{
"data": [
{
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"last_activity_at": "2026-10-10T09:30:00Z"
}
]
}