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 queryAuthSource
POST/api/tasksCreate a task.Body: task fieldsJWT or user API keytasks/tasks.controller.ts
GET/api/tasksList tasks with filters.Query: status,priority,search,dueFrom,dueTo,labelIds,sortBy,sortDirection,page,limitJWT or user API keytasks/tasks.controller.ts
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 keytasks/tasks.controller.ts
GET/api/tasks/:idGet one task.Path: idJWT or user API keytasks/tasks.controller.ts
PATCH/api/tasks/:idUpdate one task.Path: id, body: partial task fieldsJWT or user API keytasks/tasks.controller.ts
DELETE/api/tasks/:idDelete one task.Path: idJWT or user API keytasks/tasks.controller.ts
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 keytasks/tasks.controller.ts
PATCH/api/tasks/:id/auto-schedule/enableTurn on auto-scheduling for a task.Path: idJWT or user API keytasks/tasks.controller.ts
PATCH/api/tasks/:id/auto-schedule/disableTurn off auto-scheduling for a task.Path: idJWT or user API keytasks/tasks.controller.ts
POST/api/tasks/:id/labelsReplace or extend task labels.Path: id, body: labelIds,inlineLabelsJWT or user API keytasks/tasks.controller.ts
DELETE/api/tasks/:id/labels/:labelIdRemove one label from a task.Path: id,labelIdJWT or user API keytasks/tasks.controller.ts
POST/api/tasks/:id/accept-assignmentAccept a task delegated to you. Only the current assignee may accept.Path: idJWT or user API keytasks/tasks.controller.ts
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 keytasks/tasks.controller.ts
POST/api/tasks/:id/dependenciesMark this task as depending on another owned task.Path: id, body: dependsOnTaskIdJWT or user API keytasks/tasks.controller.ts
DELETE/api/tasks/:id/dependencies/:dependsOnTaskIdRemove a dependency link.Path: id,dependsOnTaskIdJWT or user API keytasks/tasks.controller.ts

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 queryAuthSource
GET/api/tasks/:id/checklist-itemsList a task's checklist items, ordered.Path: idJWT or user API keytasks/tasks.controller.ts
POST/api/tasks/:id/checklist-itemsAdd a checklist item. Returns the full updated checklist.Path: id, body: titleJWT or user API keytasks/tasks.controller.ts
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 keytasks/tasks.controller.ts
DELETE/api/tasks/:id/checklist-items/:itemIdDelete a checklist item. Returns the full updated checklist.Path: id,itemIdJWT or user API keytasks/tasks.controller.ts

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 queryAuthSource
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 keytasks/focus-sessions.controller.ts
GET/api/focus-sessions/activeThe caller's current active/paused session, with task loaded, or null.NoneJWT or user API keytasks/focus-sessions.controller.ts
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 keytasks/focus-sessions.controller.ts
POST/api/focus-sessions/:id/pauseBank elapsed active time and pause. 409 if the session isn't active.Path: idJWT or user API keytasks/focus-sessions.controller.ts
POST/api/focus-sessions/:id/resumeResume a paused session. 409 if the session isn't paused.Path: idJWT or user API keytasks/focus-sessions.controller.ts
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 keytasks/focus-sessions.controller.ts
POST/api/focus-sessions/:id/abandonBank any remaining active time and mark abandoned. The linked task is left untouched.Path: idJWT or user API keytasks/focus-sessions.controller.ts

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 queryAuthSource
GET/api/tasks/labelsList task labels.NoneJWT or user API keytasks/task-labels.controller.ts
POST/api/tasks/labelsCreate a task label.Body: name,colorJWT or user API keytasks/task-labels.controller.ts
PATCH/api/tasks/labels/:idUpdate a task label.Path: id, body: partial label fieldsJWT or user API keytasks/task-labels.controller.ts
DELETE/api/tasks/labels/:idDelete a task label.Path: idJWT or user API keytasks/task-labels.controller.ts
GET/api/task-labelsLegacy alias for label listing.NoneJWT or user API keytasks/task-labels.controller.ts
POST/api/task-labelsLegacy alias for label creation.Body: name,colorJWT or user API keytasks/task-labels.controller.ts
PATCH/api/task-labels/:idLegacy alias for label update.Path: idJWT or user API keytasks/task-labels.controller.ts
DELETE/api/task-labels/:idLegacy alias for label deletion.Path: idJWT or user API keytasks/task-labels.controller.ts

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

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
  • 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"

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.
  • 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.