Skip to content
Niftyhelp 2026.10.1-beta.1 Support

Beta This is the help for Nifty 2026.10.1-beta.1, which isn't released yet. Help for the current release

Tasks

Tasks and task lists, and the counts behind the Tasks sidebar.

On this page

List task lists

GET/task_lists

Task lists, in the owner's order.

curl "$NIFTY_URL/api/v1/task_lists" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Query parameters

NameDescription
orderposition | id | name | updated_at · optionalThe field to sort by; ties are broken by id, so the order is stable.
directionasc | desc · optionalasc (ascending) or desc (descending), for order.
updated_sincedate-time · optionalOnly records updated at or after this time (ISO 8601 with Z or a UTC offset), ordered by updated_at, id.
cursorstring · optionalmeta.next_cursor from the previous page.
limitinteger · optionalItems per page, up to 100; a larger number is taken as 100.
counttrue | false · optionaltrue 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": "Garden",
      "color": "emerald",
      "icon": "leaf",
      "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 task list

POST/task_lists

Needs a name. Goes last unless position is sent. A blank color gets the first one no other list uses (colours and icons are those of GET /category_choices).

curl -X POST "$NIFTY_URL/api/v1/task_lists" \
  -H "Authorization: Bearer $NIFTY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task_list":{"name":"Garden","color":"emerald","icon":"leaf"}}'

Headers

NameDescription
Idempotency-Keystring · optionalMakes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth).

Request body

NameDescription
task_listobject
task_list.namestring · optionalSpaces are squished. A name another list has, ignoring case, is a 422.
task_list.colorstring or null · optionalA key from GET /category_choices. Blank picks the first one no other list uses.
task_list.iconstring or null · optionalAn icon from GET /category_choices, or null for the default list icon.
task_list.positioninteger · optional1-based; out-of-range values are clamped. Moves the list there.

Response

201 Created Errors: 400 401 403 409 415 422 429

{
  "data": {
    "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
    "name": "Garden",
    "color": "emerald",
    "icon": "leaf",
    "position": 1,
    "created_at": "2026-10-10T09:30:00Z",
    "updated_at": "2026-10-10T09:30:00Z"
  }
}

Get a task list

GET/task_lists/{id}

curl "$NIFTY_URL/api/v1/task_lists/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
idULIDA record's ID.

Response

200 OK Errors: 401 404 429

{
  "data": {
    "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
    "name": "Garden",
    "color": "emerald",
    "icon": "leaf",
    "position": 1,
    "created_at": "2026-10-10T09:30:00Z",
    "updated_at": "2026-10-10T09:30:00Z"
  }
}

Change or move a task list

PATCH/task_lists/{id}

Also PUT /task_lists/{id}, the same.

Only the fields sent change. Sending position moves the list there and renumbers the live lists 1..n; the others whose position changed get a new updated_at, so re-sync with updated_since after a move. Tasks hold only task_list_id, so renaming or recolouring a list changes no task.

curl -X PATCH "$NIFTY_URL/api/v1/task_lists/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task_list":{"name":"Garden","color":"emerald","icon":"leaf"}}'

Path parameters

NameDescription
idULIDA record's ID.

Request body

NameDescription
task_listobject
task_list.namestring · optionalSpaces are squished. A name another list has, ignoring case, is a 422.
task_list.colorstring or null · optionalA key from GET /category_choices. Blank picks the first one no other list uses.
task_list.iconstring or null · optionalAn icon from GET /category_choices, or null for the default list icon.
task_list.positioninteger · optional1-based; out-of-range values are clamped. Moves the list there.

Response

200 OK Errors: 400 401 403 404 415 422 429

{
  "data": {
    "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
    "name": "Garden",
    "color": "emerald",
    "icon": "leaf",
    "position": 1,
    "created_at": "2026-10-10T09:30:00Z",
    "updated_at": "2026-10-10T09:30:00Z"
  }
}

Delete a task list

DELETE/task_lists/{id}

Delete a task list and its tasks.

Restorable for 20 seconds (POST /task_lists/{id}/restoration), then permanent. The list and its tasks are gone from every endpoint at once, with deletion records for the list and each of its tasks. Its name is free for a new list at once. Deleting again is a 404.

curl -X DELETE "$NIFTY_URL/api/v1/task_lists/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
idULIDA record's ID.

Response

204 Deleted Errors: 401 403 404 429

Restore a task list

POST/task_lists/{task_list_id}/restoration

Undo deleting a task list.

Within 20 seconds of DELETE /task_lists/{id}: brings the list back with its tasks. Their deletion records are removed, and they get a new updated_at. A task deleted on its own before the list stays deleted. If another list took its name meanwhile, it comes back renamed "Name (2)", so check name. No body. Restoring a list 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 lists take the name it picks twice in a row, it's a 422 on name; try again.

curl -X POST "$NIFTY_URL/api/v1/task_lists/01j9zq3k8m5x2v7c4n6b0t1r9e/restoration" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
task_list_idULIDA record's ID.

Headers

NameDescription
Idempotency-Keystring · optionalMakes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth).

Response

200 Restored. The list, as GET /task_lists/{id} returns it. Errors: 400 401 403 404 409 415 422 429

{
  "data": {
    "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
    "name": "Garden",
    "color": "emerald",
    "icon": "leaf",
    "position": 1,
    "created_at": "2026-10-10T09:30:00Z",
    "updated_at": "2026-10-10T09:30:00Z"
  }
}

List tasks

GET/tasks

Live tasks: not deleted, and not in a deleted list, person or project. Filters combine (AND); they're for browsing, so sync unfiltered. Without view or status, every status is listed. Before answering, open tasks whose expires_on has passed are archived (as every task endpoint does), so none is listed as open.

curl "$NIFTY_URL/api/v1/tasks" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Query parameters

NameDescription
statusstring · optionalComma-separated statuses, e.g. open,completed. Anything else, or with view, is 400.
viewoverview | today | planned | one_day | due_soon | due_not_today | today_queue | logbook · optionalA smart list, with today in the owner's time zone. Each lists only open tasks, except logbook. Combines with one home filter (inbox, task_list_id, person_id or project_id). - overview: everything open in the Inbox, and open tasks overdue or due within 14 days, from every home. - today: started today or earlier (start_on ≤ today), from every home, the Inbox too. - planned: starting after today, not in the Inbox. Ordered by start_on by default. - one_day: no start date, not in the Inbox. - due_soon: overdue, or due within 14 days, from every home. Ordered by deadline_on by default. - due_not_today: due_soon without today's (no start date, or starting after today). Ordered by deadline_on by default. - today_queue: Today's queue: overdue, due today, or started (start_on ≤ today); no undated task unless it's due. Ordered by deadline (none last) by default. - logbook: completed and archived tasks, by when they closed (completed_at or archived_at, whichever is set; ordered as closed_at), newest first by default.
inboxtrue · optionaltrue: only the Inbox (tasks with no home). Anything else is 400.
task_list_idULID · optionalOnly this list's tasks. An unknown or deleted list is an empty page. At most one home filter (inbox, task_list_id, person_id, project_id); two is 400. A home filter combines with view (e.g. view=logbook&task_list_id=…, one list's Logbook).
person_idULID · optionalOnly the tasks whose home is this person.
project_idULID · optionalOnly the tasks whose home is this project.
orderposition | created_at | updated_at | id | start_on | deadline_on | closed_at | completed_at · optionalposition (the default: the home's manual order; ties, e.g. across homes, by id), created_at, updated_at or id. view=planned adds start_on (its default), view=due_soon, view=due_not_today and view=today_queue deadline_on (their default; today_queue puts tasks with none last) and view=logbook closed_at (its default: completed_at or archived_at, whichever is set; not a field of its own). status=completed (alone) adds completed_at, e.g. the latest completed with direction=desc. Anything else is 400.
directionasc | desc · optionalasc by default; desc for view=logbook.
updated_sincedate-time · optionalOnly records updated at or after this time (ISO 8601 with Z or a UTC offset), ordered by updated_at, id.
cursorstring · optionalmeta.next_cursor from the previous page.
limitinteger · optionalItems per page, up to 100; a larger number is taken as 100.
counttrue | false · optionaltrue 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": "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"
    }
  ],
  "meta": {
    "next_cursor": "eyJrIjpbIjIwMjYtMTAtMDlUMDg6MzA6MDBaIl19",
    "limit": 1,
    "total_count": 1
  }
}

Add a task

POST/tasks

Needs a title. A new task is open unless status is sent, in the Inbox unless a home id is sent, and goes to the top of its home's open tasks unless position is sent (a closed one, to the bottom). Going to the top changes no other task: its position is one less than the first's, so it may be zero or negative.

curl -X POST "$NIFTY_URL/api/v1/tasks" \
  -H "Authorization: Bearer $NIFTY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task":{"title":"Book the plumber","deadline_on":"2026-10-16","project_id":"01j9zq3p4q6r8s0t2v5w7x9y1z"}}'

Headers

NameDescription
Idempotency-Keystring · optionalMakes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth).

Request body

NameDescription
taskTaskWriteA home id that isn't an id, an unknown or deleted home, or two home ids at once (a 422 on base), are 422s.
task.titlestring · optionalSpaces are squished.
task.description_htmlstring or null · optionalSee "Note bodies": mentions, tags and project references by id. An image (signed-id) is a 422 on description. Null or "" clears it.
task.start_ondate or null · optionalYYYY-MM-DD. Today (owner's zone) or earlier puts it in view=today. One that isn't a date is a 422.
task.deadline_ondate or null · optionalNot before start_on (a 422), unless the deadline has already passed: an overdue task can start today.
task.expires_ondate or null · optionalNot before start_on or deadline_on, nor, on an open task, before today (each a 422). The task stays open through that day and is archived from the next, with archived_at that midnight in the owner's zone.
task.statusTaskStatus · optionalAny status can change to any other. completed sets completed_at, archived sets archived_at, open clears both, and reopening a task whose expires_on has passed clears it. Sending the current status changes nothing.
task.task_list_idULID or null · optionalThe home. Sending one home id clears the other two; all three null is the Inbox.
task.person_idULID or null · optionalA record's ID.
task.project_idULID or null · optionalA record's ID.
task.positioninteger · optional1-based among the (new) home's open tasks; out-of-range values are clamped.
task.document_idsULID[] or null · optionalThe task's documents after the save: the full set, linked and unlinked in the save's transaction. Omitted leaves them as they are; null or [] unlinks them all. An unknown or deleted one is a 422 on documents, and nothing saves. Not an array of ids is a 400.
task.document_uploadsstring[] or null · optionalsigned_ids of PDFs from POST /uploads, each becoming a document (named from its filename) linked to the task, in the save. One that isn't an unused PDF upload is a 422 on documents, and nothing saves.

Response

201 Created Errors: 400 401 403 409 415 422 429

{
  "data": {
    "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"
  }
}

Get a task

GET/tasks/{id}

A deleted task, or one in a deleted list, person or project, is a 404.

curl "$NIFTY_URL/api/v1/tasks/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
idULIDA record's ID.

Response

200 OK Errors: 401 404 429

{
  "data": {
    "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"
  }
}

Change a task

PATCH/tasks/{id}

Also PUT /tasks/{id}, the same.

Change, move, complete or reopen a task.

Only the fields sent change. Moving it to another home puts it at the bottom there unless position is sent too. Sending position moves it among its home's open tasks and renumbers them 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/tasks/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task":{"title":"Book the plumber","deadline_on":"2026-10-16","project_id":"01j9zq3p4q6r8s0t2v5w7x9y1z"}}'

Path parameters

NameDescription
idULIDA record's ID.

Request body

NameDescription
taskTaskWriteA home id that isn't an id, an unknown or deleted home, or two home ids at once (a 422 on base), are 422s.
task.titlestring · optionalSpaces are squished.
task.description_htmlstring or null · optionalSee "Note bodies": mentions, tags and project references by id. An image (signed-id) is a 422 on description. Null or "" clears it.
task.start_ondate or null · optionalYYYY-MM-DD. Today (owner's zone) or earlier puts it in view=today. One that isn't a date is a 422.
task.deadline_ondate or null · optionalNot before start_on (a 422), unless the deadline has already passed: an overdue task can start today.
task.expires_ondate or null · optionalNot before start_on or deadline_on, nor, on an open task, before today (each a 422). The task stays open through that day and is archived from the next, with archived_at that midnight in the owner's zone.
task.statusTaskStatus · optionalAny status can change to any other. completed sets completed_at, archived sets archived_at, open clears both, and reopening a task whose expires_on has passed clears it. Sending the current status changes nothing.
task.task_list_idULID or null · optionalThe home. Sending one home id clears the other two; all three null is the Inbox.
task.person_idULID or null · optionalA record's ID.
task.project_idULID or null · optionalA record's ID.
task.positioninteger · optional1-based among the (new) home's open tasks; out-of-range values are clamped.
task.document_idsULID[] or null · optionalThe task's documents after the save: the full set, linked and unlinked in the save's transaction. Omitted leaves them as they are; null or [] unlinks them all. An unknown or deleted one is a 422 on documents, and nothing saves. Not an array of ids is a 400.
task.document_uploadsstring[] or null · optionalsigned_ids of PDFs from POST /uploads, each becoming a document (named from its filename) linked to the task, in the save. One that isn't an unused PDF upload is a 422 on documents, and nothing saves.

Response

200 OK Errors: 400 401 403 404 415 422 429

{
  "data": {
    "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"
  }
}

Delete a task

DELETE/tasks/{id}

Restorable for 20 seconds (POST /tasks/{id}/restoration), then permanent. Writes a task deletion record at once. Deleting again is a 404.

curl -X DELETE "$NIFTY_URL/api/v1/tasks/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
idULIDA record's ID.

Response

204 Deleted Errors: 401 403 404 429

Restore a task

POST/tasks/{task_id}/restoration

Undo deleting a task.

Within 20 seconds of DELETE /tasks/{id}: brings the task back, removes its deletion record and gives it a new updated_at. No body. Restoring a task that isn't deleted changes nothing and returns it, so a retry is safe. Once the 20 seconds have passed, for an unknown id, or while its list, person or project is deleted (restore that instead), it's a 404. If its expiry passed meanwhile, it comes back archived.

curl -X POST "$NIFTY_URL/api/v1/tasks/01j9zq3k8m5x2v7c4n6b0t1r9e/restoration" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
task_idULIDA record's ID.

Headers

NameDescription
Idempotency-Keystring · optionalMakes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth).

Response

200 Restored. The task, as GET /tasks/{id} returns it. Errors: 400 401 403 404 409 415 429

{
  "data": {
    "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"
  }
}

Task counts per list

GET/task_counts

How many open tasks are in the Inbox, Today, due soon, overdue and each list; and one page's counts.

Live, open tasks, with today in the owner's time zone; each count except overdue matches its GET /tasks listing. Expired tasks are archived first. Not a record: there's nothing to sync, so ask again when tasks change.

Name a page with view, or one home (inbox, task_list_id, person_id or project_id), to get its counts in page. view=logbook may also name one home. An unknown or hidden home counts zeros.

curl "$NIFTY_URL/api/v1/task_counts" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Query parameters

NameDescription
viewoverview | today | planned | one_day | logbook · optionalA Tasks page. Anything else, or any but logbook with a home, is 400.
inboxtrue · optionaltrue: the Inbox's page. Anything else is 400. At most one home; two is 400.
task_list_idULID · optionalA list's page. Not an id is 400.
person_idULID · optionalA person's Tasks.
project_idULID · optionalA project's Tasks.

Response

200 OK Errors: 400 401 429

{
  "data": {
    "inbox": 1,
    "today": 1,
    "due_soon": 1,
    "overdue": 1,
    "task_lists": [
      {
        "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
        "open": 1
      }
    ],
    "page": {
      "open": 1,
      "completed": 1,
      "completed_today": 1,
      "overdue": 1,
      "wont_do": 1,
      "expired": 1
    }
  }
}

Task counts per project

GET/project_task_counts

Each project's open and completed tasks, and its next task.

Live tasks of live projects, by project_id; a project with none is left out (archived tasks aren't counted). next_task is the open task to do next: started ones (no start_on, or today or earlier in the owner's time zone) first, then by deadline_on (none last), position and id; null when none is open. Expired tasks are archived first. Not paginated, and not a record: ask again when tasks change.

curl "$NIFTY_URL/api/v1/project_task_counts" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Response

200 OK Errors: 401 429

{
  "data": [
    {
      "project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
      "open": 1,
      "completed": 1,
      "next_task": {
        "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
        "title": "string",
        "start_on": "2026-10-10",
        "deadline_on": "2026-10-10"
      }
    }
  ]
}

Menu

2026.10.1-beta.1Contact support