Gipo
Resources

Subtasks

Nested subtasks under a parent task.

A subtask is a checklist item under a parent task. Subtasks have no standalone REST resource — every operation goes through /api/v1/tasks/{id}/subtasks/..., and they share the parent task's project visibility.

Subtasks are organized into groups. Every task has at least one group (a default group named for the creator's language). A subtask always belongs to exactly one group, exposed as groupId. Group management itself is not yet part of the public API — but you can target a group when creating a subtask, and the create endpoint stays backward compatible if you ignore groups entirely.

Endpoints

MethodPathNotes
GET/api/v1/tasks/{id}/subtasksList subtasks of a task, ordered by group, then by sortOrder within each group.
POST/api/v1/tasks/{id}/subtasksCreate. Accepts Idempotency-Key.
PATCH/api/v1/tasks/{id}/subtasks/{subId}Update, finalize, or reopen.
DELETE/api/v1/tasks/{id}/subtasks/{subId}Delete.

Subtasks are also returned inline with the parent task by GET /api/v1/tasks/{id}.

Subtask model

{
  "id": "s1…",
  "taskId": "t1…",
  "groupId": "g1…",
  "name": "Hook up the Google provider",
  "descriptionHtml": null,
  "sortOrder": 0,
  "finalizedAt": null,
  "finalizationDate": null,
  "estimatedHours": "1.5",
  "expectedEndDate": "2026-10-20T00:00:00.000Z",
  "assignees": [{ "user": { "id": "...", "name": "Alex" } }],
  "createdAt": "...",
  "updatedAt": "..."
}

estimatedHours is a decimal serialized as a string, like the task field. expectedEndDate is the subtask due date: a calendar day stored as UTC midnight. Both are null when not set. See Estimate and due date for how they change the parent task.

sortOrder is scoped within a group, so two subtasks in different groups can share the same value. Order a full list by group first (the GET endpoint already does this).

Creating a subtask

POST /api/v1/tasks/{id}/subtasks
{
  "name": "Hook up the Google provider",
  "descriptionHtml": "<p>Use the existing config.</p>",
  "assigneeIds": ["..."],
  "groupId": "g1…",
  "estimatedHours": 1.5,
  "expectedEndDate": "2026-10-20"
}
FieldTypeNotes
namestring1–300 chars, required.
descriptionHtmlstring≤ 50 000 chars, sanitized server-side.
assigneeIdsUUID[]Must be org members; if the parent task belongs to a project, each must also have access to that project (the project's members, plus org owners/admins). Triggers in-app + email notifications. A non-project assignee returns 400.
groupIdUUIDOptional. Must belong to the task. Omit it and the subtask lands in the task's first (default) group.
estimatedHoursnumber ≥ 0 | nullOptional. ≤ 99 999.99.
expectedEndDateISO date | nullOptional. The subtask due date.
confirmParentUpdatebooleanOptional. Confirms a parent change — see Estimate and due date.

Response: 201 Created with the subtask. A create that needs confirmation returns 409 and creates nothing.

Updating, finalizing, reopening

PATCH /api/v1/tasks/{id}/subtasks/{subId}
{ "name": "Hook up Google OAuth" }
FieldTypeNotes
namestring1–300 chars.
descriptionHtmlstring≤ 50 000 chars.
finalizationDateISO date | nullPass a date to finalize. Pass null to reopen a finalized subtask.
estimatedHoursnumber ≥ 0 | null≤ 99 999.99. null clears the estimate. 0 is stored, but does not count as an estimate.
expectedEndDateISO date | nullThe subtask due date. null clears it.
confirmParentUpdatebooleanConfirms a parent change — see Estimate and due date.

Invalid state transitions return 412 Precondition Failed:

  • Setting finalizationDate on an already-finalized subtask.
  • Setting finalizationDate: null on a subtask that isn't finalized.
  • Setting finalizationDate while a timer is still running on the subtask — a finalized subtask is read-only, so its timer must be stopped first.
  • Changing estimatedHours or expectedEndDate on a finalized subtask, or on a subtask of a finalized or blocked task.
  • Changing estimatedHours or expectedEndDate in the same request as finalizationDate. Finalize or reopen in its own PATCH.

Estimate and due date

A subtask estimate and a subtask due date change the parent task. The API applies the same rules as the app.

Estimate rollup. A subtask estimate counts when it is greater than 0. While at least one subtask estimate counts, the parent estimatedHours is the sum of the subtask estimates, finalized subtasks included. The parent estimate is then read-only. When the last counted estimate goes away, the parent keeps the last sum as its own value.

Due date extension. A subtask due date later than the parent due date (by calendar day) moves the parent due date to the subtask due date. A parent without a due date takes the subtask due date. A due date on or before the parent due date, or a cleared one, leaves the parent unchanged.

Two of these changes replace a parent value, so they need confirmation:

codeWhen
parentEstimateOverrideThe first counted subtask estimate would replace a parent estimate that is already set.
parentDueDateExtendThe subtask due date is later than the parent due date.

A POST or PATCH that needs confirmation, sent without confirmParentUpdate: true, returns 409 Conflict and writes nothing:

{
  "error": "The task already has an estimate. Confirm to replace it with the sum of the subtask estimates.",
  "code": "parentEstimateOverride",
  "parent": { "from": 10, "to": 1.5 }
}

parent.from and parent.to are the parent values before and after the change: hours for parentEstimateOverride, ISO dates for parentDueDateExtend. When one request needs both confirmations, the body carries the estimate first and the due date in also, with the same code and parent shape.

A change that would make the parent sum larger than 99 999.99 hours returns 400.

To apply the change, send the same request again with "confirmParentUpdate": true. The server checks the rules again against the current data. A change that needs no confirmation applies without the flag. When you retry a POST that had an Idempotency-Key, use a new key: the body changed, so the old key returns 409.

Every automatic parent change writes a task.updated entry in the audit log, with source subtaskRollup and the subtask that caused it. The parent task rules for these fields are in Updating or moving a task.

Deleting a subtask

DELETE /api/v1/tasks/{id}/subtasks/{subId}

Returns { "data": { "success": true } }. Cascade-deletes the subtask's time entries. The parent estimate is recalculated without confirmation, and the parent due date does not change.

On this page