Skip to content

Tasks

Tasks are the work items in your planner. These endpoints act on your own tasks only: an id belonging to anyone else returns 404 Not Found.

Returns one page of your tasks, with filters for status, space, project, label and due date.

GET /api/tasks

Fine-tune the list with these query parameters:

Name Type Required Description
status backlog | todo | in_progress | completed | cancelled | archived No Only tasks with this status. Setting it replaces the default status filter
spaceId string No Only tasks in this space
projectId string No Only tasks in this project
labelId string No Only tasks carrying this label
dueBefore string No Only tasks with a due date at or before this ISO 8601 timestamp
includeCompleted true No Include completed, cancelled and archived tasks
page integer No Page number from 1; defaults to 1
limit integer No Tasks per page; defaults to 50, maximum 100

Unless you set status or includeCompleted=true, the list leaves out completed, cancelled and archived tasks. Each task carries its space, project, labels, checklist items and non-cancelled time blocks. Results come back ordered by status, then priority (highest first), due date and sortOrder.

Returns a data array of tasks and a pagination object with page, limit, total and pages:

{
"data": [
{
"id": "9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04",
"userId": "6f0d2a9b-1c74-4e8f-8a3b-5c9d0e2f6a71",
"spaceId": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02",
"projectId": "b7d3e9a1-5c02-4f47-9a86-1d6b0e4c8a53",
"parentTaskId": null,
"title": "Write project brief",
"description": "First draft for the client review.",
"status": "todo",
"priority": "high",
"dueAt": "2026-10-07T13:00:00.000Z",
"startAfter": null,
"completedAt": null,
"estimateMinutes": 90,
"actualMinutes": null,
"minSessionMinutes": 10,
"maxSessionMinutes": 60,
"cooldownMinutes": 5,
"autoSchedule": true,
"autoSplit": true,
"schedulePreferenceId": null,
"locked": false,
"sortOrder": 0,
"createdAt": "2026-09-30T01:12:03.000Z",
"updatedAt": "2026-09-30T01:12:03.000Z",
"space": { "id": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02", "name": "Work", "color": "#3B82F6", "icon": "briefcase" },
"project": { "id": "b7d3e9a1-5c02-4f47-9a86-1d6b0e4c8a53", "name": "Website refresh", "color": "#10B981" },
"labels": [
{ "id": "d2a6f8c4-9b31-4e57-8c02-7a4d1e6b3f89", "name": "writing", "color": "#F59E0B" }
],
"checklistItems": [
{ "id": "4e8b0c6d-3a92-4f15-8d7e-2b6c9a0f4e51", "taskId": "9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04", "title": "Gather notes", "completed": true, "sortOrder": 0 }
],
"timeBlocks": [
{
"id": "7a2c5e8b-4d61-4a93-b0f8-9e3c6d2a7b40",
"userId": "6f0d2a9b-1c74-4e8f-8a3b-5c9d0e2f6a71",
"taskId": "9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04",
"title": "Write project brief",
"type": "task",
"startAt": "2026-10-01T00:30:00.000Z",
"endAt": "2026-10-01T02:00:00.000Z",
"locked": false,
"defended": false,
"source": "auto",
"sortOrder": 0,
"status": "scheduled",
"explanation": "Fits before the team meeting",
"createdAt": "2026-09-30T01:12:05.000Z",
"updatedAt": "2026-09-30T01:12:05.000Z"
}
]
}
],
"pagination": { "page": 1, "limit": 50, "total": 1, "pages": 1 }
}

total counts every task matching the filters, so use page and limit to fetch the rest.

Code When
200 OK the list returned
401 Unauthorized no valid session or API key

Creates one task.

POST /api/tasks

Send these fields in the JSON body:

Name Type Required Description
title string Yes Task title, 1–500 characters
description string No Longer text
priority none | low | medium | high | urgent No Defaults to none
status backlog | todo | in_progress | completed | cancelled | archived No Defaults to todo
dueAt string No ISO 8601 timestamp the task is due
startAfter string No ISO 8601 timestamp; the scheduler won’t place work before it
estimateMinutes integer No How long the task takes, 0 to 960 minutes (16 hours). Longer than one working day is accepted but flagged as oversized
minSessionMinutes integer No Shortest scheduling session, 1 to 60 minutes; defaults to 5
maxSessionMinutes integer No Longest scheduling session, 1 to 480 minutes; defaults to 60
cooldownMinutes integer No Gap between scheduling sessions; defaults to 5
autoSchedule boolean No Let the scheduler place time blocks; defaults to true
autoSplit boolean No Let the scheduler split the estimate across sessions; defaults to true
spaceId string No Id of one of your spaces; without it the task has no space
projectId string No Id of one of your projects
parentTaskId string No Id of one of your tasks, which makes this task a subtask
schedulePreferenceId string No Id of one of your schedule preferences
labelIds array of string No Ids of your labels; repeated ids are dropped

The spaceId, projectId, parentTaskId, schedulePreferenceId and labelIds fields must name records you own, and each id must be a UUID. With autoSchedule on and an estimateMinutes set, the scheduler places work for the task — see How the scheduler places work.

The new task comes back with 201 Created, including its space, project, labels and checklist items:

{
"id": "9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04",
"userId": "6f0d2a9b-1c74-4e8f-8a3b-5c9d0e2f6a71",
"spaceId": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02",
"projectId": "b7d3e9a1-5c02-4f47-9a86-1d6b0e4c8a53",
"parentTaskId": null,
"title": "Write project brief",
"description": "First draft for the client review.",
"status": "todo",
"priority": "high",
"dueAt": "2026-10-07T13:00:00.000Z",
"startAfter": null,
"completedAt": null,
"estimateMinutes": 90,
"actualMinutes": null,
"minSessionMinutes": 10,
"maxSessionMinutes": 60,
"cooldownMinutes": 5,
"autoSchedule": true,
"autoSplit": true,
"schedulePreferenceId": null,
"locked": false,
"sortOrder": 0,
"createdAt": "2026-09-30T01:12:03.000Z",
"updatedAt": "2026-09-30T01:12:03.000Z",
"space": { "id": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02", "name": "Work", "color": "#3B82F6", "icon": "briefcase" },
"project": { "id": "b7d3e9a1-5c02-4f47-9a86-1d6b0e4c8a53", "name": "Website refresh", "color": "#10B981" },
"labels": [
{ "id": "d2a6f8c4-9b31-4e57-8c02-7a4d1e6b3f89", "name": "writing", "color": "#F59E0B" }
],
"checklistItems": []
}

A fresh task has no time blocks yet and an empty checklist; those come from the scheduler and from checklist items you add. An estimate longer than one working day is accepted but flagged as oversized — it appears in oversizedTaskIds when you next run the planner, so you can split the task or turn it into a project.

This request creates a 90-minute task in a space, with one label and a due time:

Terminal window
curl -X POST https://tasks.example.com/api/tasks \
-H "Authorization: Bearer dp_change-me" \
-H "Content-Type: application/json" \
-d '{"title": "Write project brief", "estimateMinutes": 90, "dueAt": "2026-10-07T13:00:00Z", "spaceId": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02", "labelIds": ["d2a6f8c4-9b31-4e57-8c02-7a4d1e6b3f89"]}'

Change the spaceId and labelIds values to ids from your own spaces and labels.

Code When
201 Created the task was created
400 Bad Request the body failed validation (Validation failed), or named a record you don’t own (spaceId not found and similar)
401 Unauthorized no valid session or API key

Returns one task with everything attached to it: time blocks, checklist items, subtasks, parent task and linked documents.

GET /api/tasks/{id}

Set {id} to the task’s id.

The full task comes back as one JSON object:

{
"id": "9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04",
"userId": "6f0d2a9b-1c74-4e8f-8a3b-5c9d0e2f6a71",
"spaceId": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02",
"projectId": "b7d3e9a1-5c02-4f47-9a86-1d6b0e4c8a53",
"parentTaskId": null,
"title": "Write project brief",
"description": "First draft for the client review.",
"status": "in_progress",
"priority": "high",
"dueAt": "2026-10-07T13:00:00.000Z",
"startAfter": null,
"completedAt": null,
"estimateMinutes": 90,
"actualMinutes": null,
"minSessionMinutes": 10,
"maxSessionMinutes": 60,
"cooldownMinutes": 5,
"autoSchedule": true,
"autoSplit": true,
"schedulePreferenceId": null,
"locked": false,
"sortOrder": 0,
"createdAt": "2026-09-30T01:12:03.000Z",
"updatedAt": "2026-09-30T02:45:19.000Z",
"space": { "id": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02", "name": "Work", "color": "#3B82F6", "icon": "briefcase" },
"project": { "id": "b7d3e9a1-5c02-4f47-9a86-1d6b0e4c8a53", "name": "Website refresh", "color": "#10B981" },
"labels": [
{ "id": "d2a6f8c4-9b31-4e57-8c02-7a4d1e6b3f89", "name": "writing", "color": "#F59E0B" }
],
"checklistItems": [
{ "id": "4e8b0c6d-3a92-4f15-8d7e-2b6c9a0f4e51", "taskId": "9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04", "title": "Gather notes", "completed": true, "sortOrder": 0 }
],
"timeBlocks": [
{
"id": "7a2c5e8b-4d61-4a93-b0f8-9e3c6d2a7b40",
"userId": "6f0d2a9b-1c74-4e8f-8a3b-5c9d0e2f6a71",
"taskId": "9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04",
"title": "Write project brief",
"type": "task",
"startAt": "2026-10-01T00:30:00.000Z",
"endAt": "2026-10-01T02:00:00.000Z",
"locked": false,
"defended": false,
"source": "auto",
"sortOrder": 0,
"status": "scheduled",
"explanation": "Fits before the team meeting",
"createdAt": "2026-09-30T01:12:05.000Z",
"updatedAt": "2026-09-30T01:12:05.000Z"
}
],
"subtasks": [],
"parentTask": null,
"documents": [
{ "id": "c9f4a1b7-6e28-4d50-a3c9-8b5e0d7f2a63", "title": "Brief template", "docType": "reference", "url": "https://example.com/brief-template" }
]
}

Unlike the task list, timeBlocks includes every block, cancelled ones too. subtasks holds task objects without their own relations, parentTask gives the parent’s id and title (or null), and documents gives each linked document’s id, title, docType and url.

Code When
200 OK the task returned
401 Unauthorized no valid session or API key
404 Not Found no task with that id belongs to you (Task not found)

Changes fields of one task. Omit the fields you want to keep.

PATCH /api/tasks/{id}

Send only the fields to change in the JSON body:

Name Type Required Description
title string No New title, 1–500 characters
description string No New description; send "" to empty it
status backlog | todo | in_progress | completed | cancelled | archived No New status
priority none | low | medium | high | urgent No New priority
dueAt string No ISO 8601 timestamp; send "" to clear it
startAfter string No ISO 8601 timestamp; send "" to clear it
estimateMinutes integer No How long the task takes, 0 to 960 minutes (16 hours). Longer than one working day is accepted but flagged as oversized
minSessionMinutes integer No Shortest scheduling session, 1 to 60 minutes
maxSessionMinutes integer No Longest scheduling session, 1 to 480 minutes
cooldownMinutes integer No Gap between scheduling sessions
autoSchedule boolean No Let the scheduler place time blocks
autoSplit boolean No Let the scheduler split the estimate across sessions
spaceId string or null No Move to one of your spaces, or null for no space
projectId string or null No Move to one of your projects, or null to detach
schedulePreferenceId string or null No Attach one of your schedule preferences, or null to detach
locked boolean No Stop the scheduler moving this task
completedAt string No When the task was finished; send "" to clear it
sortOrder number No Position within lists
labelIds array of string No Replaces every label on the task

Sending labelIds replaces the task’s whole set of labels. Setting status to completed copies estimateMinutes into actualMinutes when actualMinutes is unset; it does not stamp completedAt, so send completedAt yourself if you want one.

The updated task comes back with its space, project, labels and checklist items, in the same shape as the create response:

{
"id": "9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04",
"userId": "6f0d2a9b-1c74-4e8f-8a3b-5c9d0e2f6a71",
"spaceId": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02",
"projectId": "b7d3e9a1-5c02-4f47-9a86-1d6b0e4c8a53",
"parentTaskId": null,
"title": "Write project brief",
"description": "First draft for the client review.",
"status": "in_progress",
"priority": "high",
"dueAt": "2026-10-07T13:00:00.000Z",
"startAfter": null,
"completedAt": null,
"estimateMinutes": 90,
"actualMinutes": null,
"minSessionMinutes": 10,
"maxSessionMinutes": 60,
"cooldownMinutes": 5,
"autoSchedule": true,
"autoSplit": true,
"schedulePreferenceId": null,
"locked": true,
"sortOrder": 0,
"createdAt": "2026-09-30T01:12:03.000Z",
"updatedAt": "2026-09-30T02:45:19.000Z",
"space": { "id": "0e5a7c3b-2f16-4d98-b7a4-8c1e3f5d9b02", "name": "Work", "color": "#3B82F6", "icon": "briefcase" },
"project": { "id": "b7d3e9a1-5c02-4f47-9a86-1d6b0e4c8a53", "name": "Website refresh", "color": "#10B981" },
"labels": [
{ "id": "d2a6f8c4-9b31-4e57-8c02-7a4d1e6b3f89", "name": "writing", "color": "#F59E0B" }
],
"checklistItems": []
}

updatedAt changes on every edit. This is the only task endpoint that accepts locked and sortOrder — time blocks accepts both as well.

This marks a task finished and records when:

Terminal window
curl -X PATCH https://tasks.example.com/api/tasks/9c1f4b2e-7d83-4c6a-9e50-3f8a2b7d1c04 \
-H "Authorization: Bearer dp_change-me" \
-H "Content-Type: application/json" \
-d '{"status": "completed", "completedAt": "2026-10-01T05:00:00Z"}'

The response’s actualMinutes becomes 90 because the task had a 90-minute estimate and no logged time; drop completedAt if you don’t want a completion time recorded.

Code When
200 OK the task was updated
400 Bad Request the body failed validation (Validation failed), or named a record you don’t own (spaceId not found and similar)
401 Unauthorized no valid session or API key
404 Not Found no task with that id belongs to you (Task not found)

Marks one task as archived. Nothing is removed from the database.

DELETE /api/tasks/{id}

No body is needed. Add ?permanent=true to delete the task for good instead: its checklist items, label links, dependencies and document links go with it, while its time blocks and subtasks are kept and detached. Permanent delete can’t be undone.

The endpoint confirms the change:

{
"success": true
}

The task keeps its archived status, so GET /api/tasks/{id} still finds it while the task list leaves it out. Archived tasks older than 30 days are permanently removed the next time the planner runs, so the trash stays bounded on its own.

Code When
200 OK the task was archived (or deleted, with ?permanent=true)
401 Unauthorized no valid session or API key
404 Not Found no task with that id belongs to you (Task not found)

Applies one action to many of your tasks in a single request.

POST /api/tasks/batch

Send the action and the task ids in the JSON body:

Name Type Required Description
action complete | delete | archive Yes complete finishes tasks; delete and archive both set status to archived
taskIds array of string Yes Ids of the tasks to act on; the array must not be empty

Only tasks that belong to you are touched. Ids that don’t match one of your tasks are skipped and counted in invalid. complete sets status to completed, stamps completedAt with the current time and copies estimateMinutes into actualMinutes where actualMinutes is unset. delete is a soft delete: like archive, it sets status to archived.

The counts come back in one object:

{
"affected": 3,
"requested": 4,
"invalid": 1
}

requested is how many ids you sent, affected is how many tasks changed, and invalid is how many were skipped.

Code When
200 OK the action ran
400 Bad Request action or taskIds[] was missing (action and taskIds[] are required), or action wasn’t one of the three (Invalid action. Use: complete, delete, archive)
401 Unauthorized no valid session or API key