Gipo
Resources

Tasks

Create, update, move, and finalize tasks via the REST API.

A task is the core unit of work. Every task belongs to exactly one workspace and one stage, optionally to a project, and may have assignees, tags, subtasks, comments, attachments, and time entries.

Endpoints

MethodPathNotes
GET/api/v1/tasksList with filters. Cursor pagination.
POST/api/v1/tasksCreate. Requires workspaceId. Accepts Idempotency-Key.
GET/api/v1/tasks/{id}Single task with assignees, tags, type, project, stage, subtasks, custom fields.
PATCH/api/v1/tasks/{id}Update fields (incl. custom fields), or move by sending workspaceId / stageId / sortOrder.
DELETE/api/v1/tasks/{id}Delete the task.
POST/api/v1/tasks/{id}/duplicateCreate an open copy of the task. No body. Accepts Idempotency-Key.

Subtasks have their own nested resource.

Task model

{
  "id": "t1…",
  "organizationId": "...",
  "number": 128,
  "workspaceId": "...",
  "stageId": "...",
  "sortOrder": 2,
  "projectId": null,
  "taskTypeId": null,
  "subject": "Wire the OAuth login button",
  "descriptionHtml": "<p>Hook the button up to Better Auth.</p>",
  "priority": "high",
  "expectedStartDate": null,
  "expectedEndDate": null,
  "estimatedHours": 4,
  "finalizedAt": null,
  "finalizationDate": null,
  "blockedAt": null,
  "blockReason": null,
  "blockedById": null,
  "assignees": [{ "user": { "id": "...", "name": "Alex" } }],
  "tags": [{ "tag": { "id": "...", "name": "backend" } }],
  "taskType": null,
  "project": null,
  "stage": { "id": "...", "name": "Doing", "isFinalizing": false },
  "subtasks": [],
  "customFields": [
    { "definitionId": "cf1…", "name": "Severity", "type": "select", "value": "opt2…", "optionLabel": "High" },
    { "definitionId": "cf2…", "name": "Estimate", "type": "number", "value": 4.5 }
  ]
}
FieldTypeNotes
numberintegerRead-only. Human-readable task number, unique inside the organization and starting at 1. Assigned on creation and never changed; a deleted task's number is never reused, so gaps are expected. It is the number members see in the app. Not settable and not filterable.
priorityenumlow · normal · high · urgent
descriptionHtmlstringSanitized HTML produced by the Tiptap editor. Inline image URLs must be app-hosted (no base64).
estimatedHoursdecimal | nullHours planned; reported time is computed separately from TimeEntry rows.
finalizedAtISO date | nullSet when the task is closed; auto-set when moved into a finalizing stage.
blockedAtISO date | nullSet while the task is blocked (read-only). Blocking is done in the app, not via the API.
blockReasonstring | nullWhy the task is blocked, shown while blockedAt is set.
blockedByIduuid | nullThe member who blocked the task.

Listing tasks

GET /api/v1/tasks?workspaceId=...&priority=high&limit=50
ParamTypeNotes
workspaceIdUUIDFilter to one workspace.
stageIdUUIDFilter to one stage.
projectIdUUIDProject filter. Combines with project visibility.
taskTypeIdUUIDTask-type filter.
assigneeIdUUIDTasks assigned to a specific member.
tagIdUUIDTasks carrying a specific tag.
priorityenumOne of the priority values.
querystringCase-insensitive search on subject.
finalizedbooleanOmit for all tasks; false returns only open tasks, true only finalized.
orderByenumcreatedAt (default) or dueDate (by expectedEndDate ascending, undated last).
cursorUUIDFrom a previous response.
limitint 1–100Default 50.

Returns { data, pagination: { nextCursor } }. By default tasks are ordered by (createdAt DESC, id DESC) so newer work surfaces first; pass orderBy=dueDate to sort by expectedEndDate ascending (tasks without a due date last).

Creating a task

POST /api/v1/tasks
Content-Type: application/json
Idempotency-Key: ...

{
  "workspaceId": "1f3c…",
  "subject": "Wire the OAuth login button",
  "priority": "high",
  "estimatedHours": 4
}

Required: workspaceId, subject.

The task is placed in the workspace's leftmost stage (lowest sortOrder) — you cannot pass stageId on create. Within that stage it's inserted at the top (sortOrder: 0); any tasks already there shift down one position, keeping their order relative to one another. If the leftmost stage is the finalizing stage, the task is finalized on creation.

Optional fieldTypeNotes
descriptionHtmlstring≤ 50 000 chars.
projectIdUUID | nullProject access enforced. The project must be open — a completed or cancelled project is rejected with 400.
taskTypeIdUUID | nullMust belong to the org.
priorityenumDefaults to normal.
expectedStartDate / expectedEndDateISO date | null—
estimatedHoursnumber ≥ 0≤ 99 999.99.
assigneeIdsUUID[]Must be org members; for a task with a projectId, each must also have access to that project (the project's members, plus org owners/admins). Triggers in-app + email notifications.
tagIdsUUID[]Must belong to the org.
customFieldsarrayValues to set on the new task — see Custom fields.

Response: 201 Created with the full task (including its customFields).

Updating or moving a task

PATCH /api/v1/tasks/{id}
{ "subject": "OAuth wiring", "priority": "normal" }

All fields are optional. Sending any of workspaceId, stageId, or sortOrder switches the request to move mode:

  • Move within the same stage (sortOrder only) — reorders the task.
  • Move to a new stage — stageId must belong to the same (or target) workspace.
  • Cross-workspace move — pass workspaceId (and optionally stageId). If stageId is omitted, the task lands in the target workspace's leftmost stage.
  • Moving into a finalizing stage auto-sets finalizedAt/finalizationDate to today (in the calling user's timezone).
  • Moving out of a finalizing stage clears them.
  • Moving into a finalizing stage is rejected with 412 while a timer is still running on the task or on any of its subtasks — a finalized task is read-only, so its timers must be stopped first. Stop them with the app's timer control, then retry the move.

A move on a project-restricted task requires project edit access; otherwise the response is 404.

Subtask estimates and due dates limit two task fields. Both return 409 Conflict:

  • estimatedHours cannot change while any subtask has an estimate greater than 0. The task estimate is then the sum of the subtask estimates — change the subtask estimates instead.
  • expectedEndDate cannot be earlier than the latest subtask due date, and cannot be null while any subtask has a due date. Finalized subtasks count.

Sending the current value again is not a change and succeeds. See Estimate and due date.

Custom fields

Tasks carry your organization's custom field values. Discover the field ids, types, and select options with GET /api/v1/custom-fields first — you need them to write values.

Reading. Every task's customFields array holds one entry per set field (fields with no value are omitted):

"customFields": [
  { "definitionId": "cf1…", "name": "Severity", "type": "select", "value": "opt2…", "optionLabel": "High" },
  { "definitionId": "cf2…", "name": "Estimate", "type": "number", "value": 4.5 },
  { "definitionId": "cf3…", "name": "Due contract", "type": "date", "value": "2026-08-01T00:00:00.000Z" }
]

value is typed by type: a string for text / longText, a number for number, an ISO date-time for date, a boolean for boolean, and the selected option's id for select (with optionLabel alongside).

Writing. Send customFields on POST or PATCH. Each entry names a definitionId and a value object keyed by type. Set the type's field to store a value, or to null to clear it:

"customFields": [
  { "definitionId": "cf1…", "value": { "type": "select", "optionId": "opt2…" } },
  { "definitionId": "cf2…", "value": { "type": "number", "number": 4.5 } },
  { "definitionId": "cf3…", "value": { "type": "date", "date": "2026-08-01T00:00:00.000Z" } },
  { "definitionId": "cf4…", "value": { "type": "text", "text": null } }
]

The value's field per type is text (text / longText), number, date (ISO string), boolean, or optionId (select). On PATCH, a null value removes the stored value; on POST it is simply a no-op. A definitionId may appear at most once per request.

FailureCode
definitionId from another org (or unknown)404
value.type doesn't match the field's type400
select optionId not on that field400
Same definitionId twice in one request422

Duplicating a task

POST /api/v1/tasks/{id}/duplicate

Returns 201 with { "data": <task> } — the new task, in the same shape as GET /api/v1/tasks/{id}.

  • The subject gets a localized suffix: " - copy" (or " - cópia" for a Portuguese API key owner).
  • Copied: fields, assignees, tags, custom field values, subtask groups, and subtasks with their assignees.
  • Not copied: comments, attachments, time entries, and history.
  • The copy is always open. Subtasks start unfinalized.
  • The copy goes to the top of the source stage. If that stage is a finalizing stage, the copy goes to the first non-finalizing stage.
  • Assignees who no longer have access to the task's project are left out.

Deleting a task

DELETE /api/v1/tasks/{id}

Returns { "data": { "success": true } }. Cascade-deletes subtasks, comments, attachments, and time entries owned by the task.

Common precondition failures

CodeCause
404Cross-org id, missing workspace, missing task-type, or project access denied.
412Target workspace has no stages — you cannot place a task in it.
412The task is finalized or blocked and therefore read-only — PATCH and DELETE are rejected until it is reopened / unblocked (both are app-only actions).
412A timer is still running on the task or one of its subtasks, and the PATCH would finalize it by moving it into a finalizing stage. Stop the timer first.
409estimatedHours changed while the estimate is the sum of the subtask estimates.
409expectedEndDate is earlier than the latest subtask due date, or cleared while a subtask has a due date.
400stageId belongs to a different workspace than the target, or an assignee is not a member of the task's project.
400projectId points at a closed project (completed / cancelled). Closed projects are outside the billing cycle, so no task may be linked to one. Clearing the link (projectId: null) is always allowed.

On this page