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",
"my_role": "Organiser",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"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","my_role":"Organiser"}}'
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.my_rolestring or null · optional | Spaces are squished; blank is null. |
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. |
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",
"my_role": "Organiser",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"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",
"my_role": "Organiser",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Change a project
PATCH/projects/{id}
Also PUT /projects/{id}, the same.
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","my_role":"Organiser"}}'
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.my_rolestring or null · optional | Spaces are squished; blank is null. |
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. |
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",
"my_role": "Organiser",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"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 20 seconds (POST /projects/{id}/restoration), 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 20 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/{project_id}/restoration
Undo deleting a project.
Within 20 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 20 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/restoration" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
project_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",
"my_role": "Organiser",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
List a project's activity
GET/projects/{project_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 |
|---|---|
project_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",
"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": {
"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",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"position": 1,
"description_html": "<p>Ask about the boiler too.</p>",
"description_text": "Ask about the boiler too.",
"has_documents": true,
"expired": true,
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
},
"document": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Boiler warranty",
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"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",
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"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",
"my_role": "Organiser",
"external_reference": "JIRA-123",
"external_url": "https://example.com",
"description_html": "<p>New units and worktops.</p>",
"description_text": "New units and worktops.",
"members": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"role": "string"
}
],
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
]
}