Tasks
Task CRUD, reordering, and approval
List tasks for a project
List tasks for a project. When called without query params, returns the full task list (with relations). Requires `tasks.read`. Pass `?fields=minimal` to receive only `id`, `title`, `taskNumber`, and `taskKey` (used for gate criterion linking and MCP key resolution) β that mode accepts full-scope `tasks.read`, `gates.create`, or `gates.update`. The `.own` scope on `tasks.read` does NOT grant access here, since minimal mode returns every task in the project regardless of ownership. PT-602/PT-684/PT-685/PT-905: `?ready=true`, `?review=true`, `?blocked=true`, and `?awaiting_decision=true` (mutually exclusive) switch to the flow views β the response becomes `{ data: FlowTaskRow[], pagination }`, the same row shape registered under `taskListResponseSchema` for `GET /api/tasks`. Every flow row carries `epic` and `slaUrgency`. Review rows additionally carry `reviewWaitMinutes` and `pullRequestUrl` (the open `pr_link` value, or null when the review row has no PR). Blocked-reveal rows additionally carry `blockedBy`. Decisions rows (ADR-028) additionally carry `decisionWaitMinutes`, `decisionQuestion`, `decisionOptions`, `decisionRequestId`, and `decisionAskedByUserId`, ordered oldest-first by asked_at.
Create a task in a project
Create a task in a project. Cross-field constraints: when isRecurring is true, recurrenceIntervalDays (1β365) is required and parentTaskId must be absent (subtasks cannot be recurring).
PT-448 cross-project 'goal' view
PT-448 cross-project 'goal' view. Lists every task carrying any of the requested `labels` across the projects the caller can read (tenant-scoped). Requires full `tasks.read` (the list is not ownership-scoped, so `tasks.read.own` is rejected) AND `projects.read` (or `.own`) plus an active membership. A GUEST member or a `projects.read.own`-only caller is restricted to collaborator/creator projects. Tasks in projects the caller cannot read are omitted; cross-project dependency references are redacted (taskKey + projectId omitted, title + status replaced with sentinels, isCrossProject=true) per the PT-283 precedent, so the real key/title never leak. Each row carries project, key, title, status, priority, and `isBlocked`/`isBlocking` flags; sorted by priority (URGENT first), task key as tiebreak. Capped at the 500 highest-priority rows; `truncated` is true when more match. This endpoint also serves four cross-project flow roll-ups (mutually exclusive, and ignoring `labels`): `?ready=true` β the dependency-gated ready set plus per-project agent `wip` (PT-667); `?review=true` β the review queue (PT-602); `?blocked=true` β the blocked reveal, would-be-ready tasks held out by an unfinished predecessor, each row carrying `blockedBy` (PT-685); and `?awaiting_decision=true` β the Decisions queue (ADR-028), open agent decision-requests ordered oldest-first by `askedAt`, each row carrying `decisionWaitMinutes`, `decisionQuestion`, `decisionOptions`, `decisionRequestId`, and `decisionAskedByUserId` (PT-905). Narrow any roll-up to one project with `?projectId=<uuid>`.
PT-482 label suggestions
PT-482 label suggestions. Returns the distinct labels in use for the tenant (optionally narrowed to one project), most-used first, as the autocomplete vocabulary behind the Jira-style label combobox. Tenant-scoped; the response is non-sensitive autocomplete strings, never task content β so the gate is lighter than the goal view: an active member who can read OR create tasks. Any scope satisfies it β full `tasks.read` / `tasks.create` or the `.own`-scoped variants (`tasks.read.own` / `tasks.create.own`).
PT-491 cross-project epic-children listing
PT-491 cross-project epic-children listing. Lists every task whose `epicId` points at the given EPIC, across every project in the tenant (the `epicId` edge is tenant-scoped as of PT-446), PLUS the subtasks (`parentTaskId`) of those readable children so the Child Issues panel can render a nested task β subtask tree (PT-528). The project-scoped `GET /api/projects/:id/tasks?epicId=` filter is unchanged and only returns children in that one project β this route is its cross-project complement. Requires full `tasks.read` (the list is not ownership-scoped) AND `projects.read` (or `.own`) plus an active membership. Envelope: the caller must be able to read the EPIC itself β a non-existent, cross-tenant, or unreadable-project epic all return 403 (no existence oracle); a readable non-EPIC target returns 400. Children in projects the caller cannot read are redacted to an opaque id + the PT-283 sentinels (taskKey/projectId/issueType/progress dropped, title/status sentinelled, isCrossProject=true), so the real key/title never leak. Only a full org-wide `projects.read` holder sees every child un-redacted. Readable rows also carry `parentTaskId` (PT-528); a parent edge is nulled when its parent is unreadable, and a subtask of an unreadable child β or one in a project the caller cannot read β is omitted, so no cross-project parentβchild relationship leaks.
PT-577 initiative-scoped Epic picker options for the task-detail Epic field
PT-577 initiative-scoped Epic picker options for the task-detail Epic field. Returns every EPIC in the current project PLUS every EPIC tagged to an initiative this project participates in (PT-447 Initiative β Epic membership, unioned with the legacy project-containment FK), across the other projects in the tenant. Cross-project epics are bounded to the caller readable-project set so an unreadable epic is never offered (PT-283 β omitted, not sentinelled); a tenant-wide epic list is out of scope. Requires the same epic-list read gate as the project-scoped minimal listing (full-scope `tasks.read | gates.create | gates.update`). The cross-project expansion additionally needs full `tasks.read` + `projects.read` (or `.own`) + an active membership, and degrades gracefully to same-project epics when that is missing rather than 403ing. Options carry `projectName` / `isCrossProject` / `initiativeName` so the picker can group by project (current project first).
PT-611 write-guard-aligned Assignee picker options for the task-detail Assignee field and the task create / add-subtask form
PT-611 write-guard-aligned Assignee picker options for the task-detail Assignee field and the task create / add-subtask form. The people analogue of `/api/projects/{id}/epic-options`. Unlike `mentionable-users?assignable=true` β a *visibility* list that offers every ACTIVE non-guest org member β this endpoint offers only users who pass the same predicate the task write guard (`assertAssigneeAssignable` β `verifyUserProjectAccess`) enforces on save: the project creator, an ACTIVE collaborator (including cross-org collaborators), or an INTERNAL member with full-scope `projects.read`. `Guest Customer`s and synthetic agent users are excluded. An INTERNAL member holding only `projects.read.own` who is neither creator nor collaborator is therefore NOT offered β the picker no longer surfaces candidates the PATCH would reject with a 400. The candidate pool is the same requester-scoped visibility set `mentionable-users` builds, then narrowed, so a scoped caller can never enumerate more org members here than there. Options are the minimal `{ id, name }` picker shape (no email), sorted by name. The guard is evaluated against fetch-time state; the PATCH independently re-validates on save.
PT-431 task velocity
PT-431 task velocity. Returns completed-tasks-per-week buckets over the trailing `weeks` window (default 12, clamped to [1, 52]), the data source for the dashboard Task Velocity widget. A completion is a task whose `completedAt` (stamped when it transitions into a project-configured completed status) falls in the window. Weeks are ISO-8601 (Monday 00:00 UTC). Requires `tasks.read | tasks.read.own` AND `projects.read | projects.read.own` plus an active membership. A `tasks.read.own` caller counts only their own completions; a GUEST / `projects.read.own` caller is restricted to collaborator/creator projects. Archived (COMPLETED / CANCELLED) projects are excluded.
PT-604: atomically claim the top task in the project ready set (PT-602) for `assigneeId`
PT-604: atomically claim the top task in the project ready set (PT-602) for `assigneeId`. Race-free (READ COMMITTED conditional CAS β concurrent callers never both claim the same task; the loser advances to the next ready row) and audited in the same transaction. The claimed task moves to the project's first in_progress-category status (by sortOrder); a project with no such status keeps the task status unchanged. Returns `{ data: <task> }` on success, or `{ data: null }` (still HTTP 200) when nothing is ready.
Get task details
Get task details. The response carries `decisionRequest` (PT-905) β by default the task's LATEST decision-request, suppressed to `null` while the task is complete. Pass `decisionRequestId` to hydrate a specific one: that is the only correlation path a resuming agent has once a NEWER request has opened on the same task, which would otherwise hide its answered one for good. An id-addressed request that is already `answered` or `cancelled` is returned even on a complete task β the suppression exists to stop a stale OPEN question being offered as a live control, not to withhold the answer an agent is resuming from, which is frequently what completed the task. An `open` request stays suppressed on a complete task whether or not it was addressed by id.
Update a task
Update a task
Delete a task
Delete a task
Reorder a task within its status column
Reorder a task within its status column
Approve or reject a task
Approve or reject a task
Check dependency status for a task
Check dependency status for a task
Raise a decision request β an in-progress agent marks its task as awaiting a human decision (question + optional proposed options)
Raise a decision request β an in-progress agent marks its task as awaiting a human decision (question + optional proposed options). At most one OPEN request exists per task; a re-mark while one is already open idempotently returns the existing row (HTTP 200) rather than creating a second one (HTTP 201 on genuine creation). Authorised as the task's CURRENT assignee β which under the INF-113 claim convention is a synthetic `agent+<class>@constellation.local` user rather than the token owner, so a caller driving that agent supplies the optional `agentId` body field naming it and the call then acts as that agent. Supplying it requires `tasks.update.any` (403 without), and the id must be an ACTIVE synthetic agent in this tenant β a human, suspended, soft-deleted or foreign-tenant id is rejected (400), so this delegates to a robot account and cannot impersonate a person. Omit it when the token itself is the assignee. Authorised only while the task is in an in_progress-category status AND is not already complete β a task counts as complete by ANY of a completed-category status, a stamped `completedAt`, or `progress >= 100`, because a task can be completed without its status saying so (progress-only completion). All re-validated authoritatively under a row lock, never trusted from client input. Never mutates task.status/assigneeId and never emits projects.task.unblocked. On first creation only, best-effort posts a task comment carrying the question.
Answer the decision request named by the `requestId` query parameter
Answer the decision request named by the `requestId` query parameter. Exactly one of `chosenOptionIndex` (an in-range index into the request's own `options`) or a non-empty free-text `answer` must be given β the effective answer is the chosen option's label, or the free text. Addressing by explicit id (not 'the task's latest request') keeps a stale retry idempotent: it resolves the exact request it named and can never hit a newer one that opened since. Open-guarded: answering an already-terminal request is a no-op that returns the existing (resolved) row rather than erroring. Requires `tasks.update.any`. Records one auditCritical entry (metadata only β never the answer body) on the transition that actually resolves it.
Cancel the decision request named by the `requestId` query parameter
Cancel the decision request named by the `requestId` query parameter. Authorised for a caller who holds `tasks.update.any` OR IS the request's asking user (ADR-028 β the raising agent may always cancel its own question). Addressed by explicit id like PATCH; open-guarded, same no-op-on-terminal shape.
Create a typed lateral link from the source task ([taskId]) to another task in the same TENANT β the target may be in a different project (PT-909)
Create a typed lateral link from the source task ([taskId]) to another task in the same TENANT β the target may be in a different project (PT-909). The body either links an existing task (`{ targetTaskId, linkType }`) or creates a new follow-up task and links it (`{ newTaskTitle, linkType }`, status BACKLOG or the project default, reporter = caller β PT-479). Supported link types: BLOCKS | RELATES | DUPLICATES | CLONES | CAUSES | FOLLOWS. Both tasks must be in the same tenant. When the target is in a different project, the caller must also have access to that project; an unauthorized target and an absent one return the same 404, so the endpoint is not an existence oracle. The create-new branch also requires `tasks.create`.
Delete a typed lateral link by id
Delete a typed lateral link by id. The link must belong to the route tenant, and the route task must be one of its endpoints β returns 404 otherwise. Since PT-909 a link may span two projects; when the OTHER endpoint is in a different project, the caller must also have access to that project, because deleting a BLOCKS edge changes what is ready there. Unauthorized and absent both return 404.
Chronological task activity feed merging comments, audit changes, and file attachments
Chronological task activity feed merging comments, audit changes, and file attachments. Change entries are included only when the caller has the `audit.read` permission; file entries only when they have `files.read`. For null-tenant projects (no organisationId) both gates are denied, so the feed contains comments only.
PT-602: ready-set and review-queue sizes for the project, for the Flow area card
PT-602: ready-set and review-queue sizes for the project, for the Flow area card. Split out of the project overview aggregate so the readiness scan is fetched lazily and never blocks the hub. Gated on `projects.read | projects.read.own` evaluated against the projectβs own org (plus project access). Returns `{ data: { readyCount, reviewCount } }`.
PT-803: the flow-metrics read API over the `task_flow_events` capture stream (PT-801/PT-802)
PT-803: the flow-metrics read API over the `task_flow_events` capture stream (PT-801/PT-802). Returns four series in ONE payload β cumulative flow, review-queue depth/age, rework rate, and cycle time by agent class β because the first three derive from ONE scan of the same window; a chart-per-endpoint split would repeat that scan for a response measured in kilobytes. (Cycle time is the series that scan cannot answer on its own β a task completed inside the window may have started long before it β so it adds a second, targeted read for completions and their histories.) The cumulative-flow and review-queue series are built by reconstructing each taskβs timeline PER TASK from that taskβs own transitions, walking backward from live state, and their population figures are a COUNT OF TASKS rather than an accumulated `+1 to_category / β1 from_category` delta: a cross-project move, which the stream records with no discriminator, can therefore misplace at most the one task it belongs to instead of desynchronising the whole series. Rework and cycle time do not use that reconstruction β rework buckets qualifying events directly, and cycle time works from the histories of the tasks that completed inside the window. The window is the RESOLVED one, not necessarily the requested one: `to` is capped at the present (sampling the future would report fabricated history, since the reconstruction projects each taskβs live status forward) and an over-long range clamps its LEFT edge. A third normalisation is silent: the grammar accepts arbitrary fractional seconds, but the value is carried as a JS `Date` and so is TRUNCATED to milliseconds β `β¦:00.123999Z` becomes `β¦:00.123Z`, which is what the returned `window` reports and what the exclusive-`from` / inclusive-`to` predicates apply, while the stored timestamps keep microsecond precision. Sub-millisecond boundary membership is therefore unreliable (PT-913). `truncated` reports when an internal row cap clipped the reads. Residuals a chart reader must not misread as data are documented at /modules/project-tracker/flow. Gated on `projects.read | projects.read.own` evaluated against the projectβs own org (plus project access), like the sibling flow-counts and overview reads.
List the distinct task labels for the Tasks-page Labels filter chip (PT-483)
List the distinct task labels for the Tasks-page Labels filter chip (PT-483). Scoped to exactly the tasks the caller can list via /api/my-tasks: the collaborator-or-creator project window for GUEST / projects.read.own-only callers, and pinned to the caller's own assigned tasks for tasks.read.own-only callers. Requires (tasks.read OR tasks.read.own) AND (projects.read OR projects.read.own) plus active membership.
Apply a sparse field patch to up to 100 tasks in a single transaction
Apply a sparse field patch to up to 100 tasks in a single transaction. The patch accepts any non-empty subset of status, assigneeId, dueDate, and priority β at least one field must be present, otherwise the request is rejected with 400. Fields left out are untouched on every selected task. All-or-nothing semantics: if any task in the batch fails validation or update, the whole batch rolls back. Status values are validated per task's project β a status name missing from any selected task's project rejects the batch.
Move up to 100 tasks from their current project(s) to a target project within the same tenant
Move up to 100 tasks from their current project(s) to a target project within the same tenant. Allocates new task keys from the target project, reconciles statuses, creates redirect entries for old keys, and emits per-task audit + event entries. Refuses the move if any selected task has subtasks or a parent not fully included in the selection (SUBTREE_NOT_INTACT). Cross-tenant moves are rejected.