Apply a sparse field patch to up to 100 tasks in a single transaction
POST/api/tasks/bulk-update
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, priority and cycleId — at least one field must be present, otherwise the request is rejected with 400. cycleCapacityOverrideReason is a request control, not a task field: supply it only when retrying a CYCLE_CAPACITY_EXCEEDED response. 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. cycleId (null detaches) is validated per task against the target cycle's scope arm before anything is written: a partially eligible selection is rejected whole, naming how many tasks failed and which. A completed cycle is frozen in both directions — the batch is rejected if any task would join it or leave one.
Request
Responses
- 200
- 400
- 401
- 403
- 404
- 409
Successful response
Validation error. Triggers: empty patch, malformed or duplicate task ids, > 100 ids in the selection, a status name that is not configured for one or more selected tasks’ projects, or a cycle rejection. The status rejection carries details.reason STATUS_NOT_CONFIGURED with ineligibleCount, selectedCount and ineligibleTaskIds — the selected tasks in the projects that lack the status. The cycle rejections that report on the SELECTION carry a machine-readable details.reason — CYCLE_INELIGIBLE (one or more tasks fail the cycle’s scope rule), CYCLE_FROZEN_JOIN (the target cycle is completed) or CYCLE_FROZEN_LEAVE (a selected task sits in a completed cycle) — alongside ineligibleCount, selectedCount and ineligibleTaskIds. The two rejections about the TARGET rather than the selection carry a details.reason too — CYCLE_NOT_FOUND and CYCLE_ARCHIVED — but no counts, since no particular task is at fault. CYCLE_ARCHIVED is the expected outcome of a cycle archived between an eligibility read and the write it informed, and is permanent: a client should refresh its options rather than retry. CYCLE_ESTIMATE_REQUIRED (PT-1081) reports the joining tasks whose OWN project requires a story-point estimate on its scale for planned and active cycle work that they do not have (null, 0, or an off-scale value); it carries the same counts plus ineligibleTasks, each with taskId, taskKey, estimatePoints and that project's allowed storyPointScale. It is checked after the scope rule, and the whole batch is refused.
Unauthorized — authentication credentials are missing or invalid
Forbidden — requires tasks.update.any permission
One or more task IDs not found, not visible to the caller, or outside the projects the caller may change in a bulk write. Carries details.reason TASKS_NOT_FOUND with ineligibleCount, selectedCount and ineligibleTaskIds — the caller’s own unresolved ids, canonicalised. The cause is deliberately not distinguished, so the response does not reveal whether an id exists. Permanent for that selection: drop the ids rather than retry.
PT-968 CYCLE_CAPACITY_EXCEEDED — the write would take the target cycle past its targetPoints. details carries the overage, the committed and projected figures, and an ordered list of the cheapest cut candidates (shortest prefix covering the overage, capped at ten). Visibility is ALL-OR-NOTHING: a caller who cannot read every project the cycle spans — or who lacks full tasks.read — receives the restricted arm instead, which names the requested cycle and nothing else: no committed or projected totals, no overage, and an empty candidate list. Resubmit the identical request with cycleCapacityOverrideReason to admit the breach — it is then recorded on the cycle membership event with the overage.