Skip to main content
Was this helpful?

Tasks API

Task Workspace

Create tasks, filter work, and manage reusable task labels

These routes back the PrimeCal task workspace. They are all scoped to the authenticated user and include task CRUD, label management, the Focus view, auto-scheduling, delegation, and dependencies.

JWT or user API keyPagination and filteringInline labelsAuto-schedulingDelegation and dependencies

Authentication and Permissions​

  • All routes on this page require authentication.
  • Task and label ownership is scoped to the current user.
  • Task label routes are available under both /api/tasks/labels and legacy /api/task-labels.

Endpoint Reference​

Tasks​

MethodPathPurposeRequest or queryAuth
POST/api/tasksCreate a task.Body: task fieldsJWT or user API key
GET/api/tasksList tasks with filters.Query: status,priority,search,dueFrom,dueTo,labelIds,archived,sortBy,sortDirection,page,limitJWT or user API key
GET/api/tasks/settingsGet the caller's task settings (currently just auto-archive).NoneJWT or user API key
PATCH/api/tasks/settingsUpdate the caller's task settings.Body: autoArchiveCompletedTasksDays (integer 1-3650, or null to disable)JWT or user API key
PATCH/api/tasks/reorderReassign rank for every task in one (caller-owned) status column to match the given order.Body: status,taskIds (full ordered array for that column)JWT or user API key
POST/api/tasks/archive-doneArchive every one of the caller's done, not-yet-archived tasks in one call.NoneJWT or user API key
GET/api/tasks/focusToday's routine-instantiated and due tasks, schedule-ordered, excluding tasks blocked by an incomplete dependency.Query: date (optional YYYY-MM-DD, defaults to today UTC)JWT or user API key
GET/api/tasks/:idGet one task.Path: idJWT or user API key
PATCH/api/tasks/:idUpdate one task.Path: id, body: partial task fieldsJWT or user API key
DELETE/api/tasks/:idDelete one task.Path: idJWT or user API key
POST/api/tasks/:id/auto-scheduleFind the task's next open calendar slot (using duration, priority, context, and preferred window) and set its due date/time to that slot.Path: idJWT or user API key
PATCH/api/tasks/:id/auto-schedule/enableTurn on auto-scheduling for a task.Path: idJWT or user API key
PATCH/api/tasks/:id/auto-schedule/disableTurn off auto-scheduling for a task.Path: idJWT or user API key
POST/api/tasks/:id/labelsReplace or extend task labels.Path: id, body: labelIds,inlineLabelsJWT or user API key
DELETE/api/tasks/:id/labels/:labelIdRemove one label from a task.Path: id,labelIdJWT or user API key
POST/api/tasks/:id/accept-assignmentAccept a task delegated to you. Only the current assignee may accept.Path: idJWT or user API key
POST/api/tasks/:id/bounce-assignmentBounce a delegated task back to unassigned. Only the current assignee may bounce it.Path: id, body: reason (optional)JWT or user API key
POST/api/tasks/:id/dependenciesMark this task as depending on another owned task.Path: id, body: dependsOnTaskIdJWT or user API key
DELETE/api/tasks/:id/dependencies/:dependsOnTaskIdRemove a dependency link.Path: id,dependsOnTaskIdJWT or user API key
PATCH/api/tasks/:id/archiveArchive one task (hides it from the default list; does not delete it).Path: idJWT or user API key
PATCH/api/tasks/:id/unarchiveUn-archive one task.Path: idJWT or user API key

Task Checklist Items​

Sub-steps inside a single task — distinct from task dependencies (which link two separate tasks) and from routine templates (which define recurring tasks). Use this for a one-off task with its own steps, e.g. "Plan birthday party" → "book venue", "order cake", "send invites".

MethodPathPurposeRequest or queryAuth
GET/api/tasks/:id/checklist-itemsList a task's checklist items, ordered.Path: idJWT or user API key
POST/api/tasks/:id/checklist-itemsAdd a checklist item. Returns the full updated checklist.Path: id, body: titleJWT or user API key
PATCH/api/tasks/:id/checklist-items/:itemIdRename, reorder, or check off an item. Returns the full updated checklist.Path: id,itemId, body: partial title,isDone,orderJWT or user API key
DELETE/api/tasks/:id/checklist-items/:itemIdDelete a checklist item. Returns the full updated checklist.Path: id,itemIdJWT or user API key

Focus Sessions​

A Focus Session is an optional Pomodoro-style timer against a task — a session's accumulatedSeconds is banked server-side on every pause/resume/complete/abandon, so elapsed time is never trusted from the client. Only one session may be active/paused per user at a time.

MethodPathPurposeRequest or queryAuth
POST/api/focus-sessionsStart a session against a task the caller owns or is assigned to. 409 if the caller already has an active/paused session; 404 if the task doesn't exist or isn't accessible to the caller.Body: taskId,plannedDurationMinutes?JWT or user API key
GET/api/focus-sessions/activeThe caller's current active/paused session, with task loaded, or null.NoneJWT or user API key
GET/api/focus-sessions/historyPaginated completed/abandoned sessions, newest endedAt first, with task loaded.Query: limit (default 20, max 100), offset (default 0)JWT or user API key
POST/api/focus-sessions/:id/pauseBank elapsed active time and pause. 409 if the session isn't active.Path: idJWT or user API key
POST/api/focus-sessions/:id/resumeResume a paused session. 409 if the session isn't paused.Path: idJWT or user API key
POST/api/focus-sessions/:id/completeBank any remaining active time, mark completed, and optionally mark the linked task done.Path: id, body: completeTask?JWT or user API key
POST/api/focus-sessions/:id/abandonBank any remaining active time and mark abandoned. The linked task is left untouched.Path: idJWT or user API key

All :id routes resolve ownership via a WHERE id = :id AND userId = :userId lookup and return 404 on any mismatch — never 403 — so a non-owner can't distinguish "not yours" from "doesn't exist."

Task Labels​

MethodPathPurposeRequest or queryAuth
GET/api/tasks/labelsList task labels.NoneJWT or user API key
POST/api/tasks/labelsCreate a task label.Body: name,colorJWT or user API key
PATCH/api/tasks/labels/:idUpdate a task label.Path: id, body: partial label fieldsJWT or user API key
DELETE/api/tasks/labels/:idDelete a task label.Path: idJWT or user API key
GET/api/task-labelsLegacy alias for label listing.NoneJWT or user API key
POST/api/task-labelsLegacy alias for label creation.Body: name,colorJWT or user API key
PATCH/api/task-labels/:idLegacy alias for label update.Path: idJWT or user API key
DELETE/api/task-labels/:idLegacy alias for label deletion.Path: idJWT or user API key

Request Shapes​

Task payload​

CreateTaskDto

  • title: required, max 240 chars
  • body: optional, max 8000 chars
  • bodyFormat: optional, currently only markdown
  • color: optional 6-digit hex color
  • priority: optional enum high|medium|low
  • status: optional enum todo|in_progress|done
  • place: optional, max 255 chars
  • dueDate: optional ISO date string
  • dueEnd: optional ISO date string
  • dueTimezone: optional, max 100 chars
  • assigneeId: optional integer
  • durationMinutes: optional integer, 1..1440 — required on a task before it can be auto-scheduled
  • autoScheduled: optional boolean
  • preferredWindowStartHour / preferredWindowEndHour: optional integer, 0..23 — overrides the default 9–18 auto-scheduling search window for this task
  • context: optional enum deep_work|errand|admin|kid_safe|low_energy — used by auto-scheduling (deep_work prefers a roomier gap)
  • labelIds: optional unique integer array, max 12 items

Defaults:

  • bodyFormat: markdown
  • color: #eab308
  • priority: medium
  • status: todo

Reorder payload​

ReorderTasksDto

  • status: required enum todo|in_progress|done — the column being reordered
  • taskIds: required unique integer array, 1-500 items, in the desired order. All ids must belong to the caller and currently have the given status, or the whole call is rejected (403) — a partial match is never applied.

Task settings payload​

UpdateTaskSettingsDto

  • autoArchiveCompletedTasksDays: optional. Send an integer 1..3650 to enable auto-archive at that interval, or explicit null to disable it. Omitting the field leaves the current value unchanged.

UserTaskSettings response shape

  • userId, autoArchiveCompletedTasksDays (nullable — null means auto-archive is off), createdAt, updatedAt. A caller who has never touched their settings gets a default { userId, autoArchiveCompletedTasksDays: null } back without a row being created just to answer the read.

Delegation and dependency payloads​

  • BounceTaskAssignmentDto.reason: optional string, max 500 chars — logged, not persisted to a dedicated audit table
  • AddTaskDependencyDto.dependsOnTaskId: required positive integer

Checklist item payloads​

CreateTaskChecklistItemDto

  • title: required, max 240 chars

UpdateTaskChecklistItemDto (all optional)

  • title: max 240 chars
  • isDone: boolean
  • order: integer — no reorder endpoint; set order directly on the item(s) you want to move

A task's checklistItems are only included on GET /api/tasks/:id (the single-task read), not on the paginated GET /api/tasks list or GET /api/tasks/focus — fetch them via the dedicated endpoints above when you need them for a list view.

Focus query​

  • date: optional query string on GET /api/tasks/focus, YYYY-MM-DD, defaults to today (UTC)

Focus Session payloads​

StartFocusSessionDto

  • taskId: required positive integer
  • plannedDurationMinutes: optional integer, 1..180 — omit for an open-ended (count-up) session

CompleteFocusSessionDto

  • completeTask: optional boolean — when true, also calls task update to set status: done on the linked task (best-effort: a failure here is logged but never prevents the session itself from completing)

GetFocusSessionHistoryQueryDto (query params on GET /api/focus-sessions/history)

  • limit: optional integer, 1..100, default 20
  • offset: optional integer, >= 0, default 0

FocusSession response shape

  • id, userId, taskId, status (active|paused|completed|abandoned)
  • plannedDurationMinutes (nullable), startedAt, pausedAt (nullable), accumulatedSeconds, endedAt (nullable)
  • task: populated only on GET /active and GET /history (not on the mutating pause/resume/complete/abandon/start responses)

Query filters​

QueryTasksDto

  • status: optional enum todo|in_progress|done
  • priority: optional enum high|medium|low
  • search: optional string, max 120 chars
  • dueFrom: optional ISO date string
  • dueTo: optional ISO date string
  • labelIds: optional unique integer array, max 10 items
  • archived: optional boolean (?archived=true). Omitted or false excludes archived tasks (the default); true returns only archived tasks. There is no "both" mode — a client that needs both makes two calls.
  • sortBy: updatedAt|createdAt|dueDate
  • sortDirection: asc|desc
  • page: int >= 1, default 1
  • limit: int 1..100, default 25

Label payloads​

  • CreateTaskLabelDto.name: required, max 64 chars
  • CreateTaskLabelDto.color: optional 6-digit hex color
  • UpdateTaskLabelsDto.labelIds: optional ids of existing labels
  • UpdateTaskLabelsDto.inlineLabels: optional new labels to create and attach in one call

Example Calls​

Create a task​

curl -X POST "$PRIMECAL_API/api/tasks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Pack school bags",
"priority": "high",
"dueDate": "2026-03-30T18:00:00.000Z",
"dueTimezone": "Europe/Budapest",
"labelIds": [3, 7]
}'

Filter tasks​

curl "$PRIMECAL_API/api/tasks?status=todo&sortBy=updatedAt&sortDirection=desc&limit=25" \
-H "Authorization: Bearer $TOKEN"

Create a label​

curl -X POST "$PRIMECAL_API/api/tasks/labels" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "School",
"color": "#14b8a6"
}'

Get today's Focus list​

curl "$PRIMECAL_API/api/tasks/focus?date=2026-07-05" \
-H "Authorization: Bearer $TOKEN"

Create a task with a duration and context, then auto-schedule it​

curl -X POST "$PRIMECAL_API/api/tasks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Write school newsletter",
"durationMinutes": 90,
"context": "deep_work"
}'

curl -X POST "$PRIMECAL_API/api/tasks/57/auto-schedule" \
-H "Authorization: Bearer $TOKEN"

Bounce a delegated task​

curl -X POST "$PRIMECAL_API/api/tasks/58/bounce-assignment" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reason": "Forgot which bin is recycling this week"
}'

Start, pause, and complete a Focus Session​

curl -X POST "$PRIMECAL_API/api/focus-sessions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"taskId": 57,
"plannedDurationMinutes": 25
}'

curl -X POST "$PRIMECAL_API/api/focus-sessions/9/pause" \
-H "Authorization: Bearer $TOKEN"

curl -X POST "$PRIMECAL_API/api/focus-sessions/9/resume" \
-H "Authorization: Bearer $TOKEN"

curl -X POST "$PRIMECAL_API/api/focus-sessions/9/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"completeTask": true
}'

Check for an in-progress session and view history​

curl "$PRIMECAL_API/api/focus-sessions/active" \
-H "Authorization: Bearer $TOKEN"

curl "$PRIMECAL_API/api/focus-sessions/history?limit=10" \
-H "Authorization: Bearer $TOKEN"

Reorder a column​

curl -X PATCH "$PRIMECAL_API/api/tasks/reorder" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "todo",
"taskIds": [58, 57, 60]
}'

Archive and un-archive​

curl -X PATCH "$PRIMECAL_API/api/tasks/58/archive" \
-H "Authorization: Bearer $TOKEN"

curl -X POST "$PRIMECAL_API/api/tasks/archive-done" \
-H "Authorization: Bearer $TOKEN"

curl -X PATCH "$PRIMECAL_API/api/tasks/58/unarchive" \
-H "Authorization: Bearer $TOKEN"

Configure auto-archive​

curl -X PATCH "$PRIMECAL_API/api/tasks/settings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"autoArchiveCompletedTasksDays": 30
}'

Add a dependency​

curl -X POST "$PRIMECAL_API/api/tasks/60/dependencies" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"dependsOnTaskId": 59
}'

Add and complete a checklist item​

curl -X POST "$PRIMECAL_API/api/tasks/60/checklist-items" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Book the venue"
}'

curl -X PATCH "$PRIMECAL_API/api/tasks/60/checklist-items/12" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"isDone": true
}'

Response and Behavior Notes​

  • Tasks can be linked to mirrored calendar events through the task-calendar bridge, but that linkage is not directly configured in these DTOs.
  • POST /api/tasks/:id/labels supports both existing labels and inline label creation.
  • Task label routes are intentionally duplicated under the legacy /api/task-labels path for compatibility.
  • GET /api/tasks/focus excludes done tasks and any task with an incomplete dependency, ordered by due date (nulls last), then routine item order, then creation time.
  • POST /api/tasks/:id/auto-schedule requires durationMinutes to be set on the task; it returns 400 if it isn't. If no open slot is found within the priority-based search horizon, the task's scheduling state is set to unscheduled rather than erroring.
  • POST /api/tasks/:id/accept-assignment and POST /api/tasks/:id/bounce-assignment both return 403 if the caller is not the task's current assignee.
  • POST /api/tasks/:id/dependencies rejects self-references and the direct two-task cycle (B depending on A when A already depends on B) with 400. It does not check longer dependency chains.
  • Changing a task's status fires the task.status_changed automation trigger for any rule that listens for it.
  • New tasks are appended to the end of their (ownerId, status) column: rank is set to one past the current max rank in that column, not 0 — so newly created tasks don't jump to the top of a manually-ordered column.
  • rank is scoped per (ownerId, status), not global — moving a task to a different status (via PATCH /api/tasks/:id) does not change its rank value, so it may land anywhere in the destination column depending on the other tasks already there. Follow up with PATCH /api/tasks/reorder if a specific position in the destination column matters.
  • A background job archives done, non-archived tasks whose completedAt is older than the owner's autoArchiveCompletedTasksDays once a day. It only ever acts on users who have explicitly set a value — there is no global default.
  • Archiving is unrelated to deletion: an archived task still exists, is still returned by GET /api/tasks/:id, and can still be fetched via GET /api/tasks?archived=true. It is not part of the Trash/recoverable-delete system used elsewhere in the app.
  • All checklist-item endpoints return the task's entire updated checklist (not just the affected item), ordered by order then id — simplest for a client to just replace its local list with the response.
  • Checklist items have no owner column of their own; every checklist endpoint first verifies the caller owns the parent task, then acts on the item.
  • Focus Session elapsed time is always computed server-side from startedAt/now and banked into accumulatedSeconds on every pause/complete/abandon — the client's live countdown/count-up display is a UI-only computation and is never sent back to the server.
  • POST /api/focus-sessions returns 409 if the caller already has an active or paused session; sessions are otherwise never auto-abandoned by the server, so a session left open simply waits to be resumed.
  • POST /api/focus-sessions/:id/pause and /resume return 409 if the session isn't currently in the expected state (active for pause, paused for resume) rather than silently no-op'ing.

Best Practices​

  • Use sortBy=updatedAt and a small limit for interactive task lists.
  • Prefer labelIds when attaching known labels and inlineLabels only when the label truly does not exist yet.
  • Keep dueTimezone explicit for tasks that may be mirrored or interpreted across time zones.
  • Set durationMinutes and, if relevant, context before calling auto-schedule — auto-scheduling only ever moves dueDate/dueEnd, it never creates the task.
  • Treat bounce-assignment's reason as advisory context for the person reassigning the task, not a persisted audit trail.
  • Treat /api/tasks/labels as the canonical label path and /api/task-labels as a compatibility route.
  • Send the full ordered taskIds array to /api/tasks/reorder on every drag, not a delta — the endpoint always assigns rank by array position across the whole column.
  • Prefer POST /api/tasks/archive-done for a bulk "clean up Done" action over looping PATCH /:id/archive calls per task.