Skip to Content

Tasks API

Tasks are the core work unit in Cohort. Use these endpoints to create, read, update, delete, and transition tasks.

Status and Error Handling

Inspect the HTTP status before parsing a success payload. Every authenticated v1 response includes an opaque X-Request-Id. Canonical errors repeat it at error.requestId; legacy flat errors repeat it at top-level requestId. Include that ID when reporting a failed request.

Use --fail-with-body so curl exits unsuccessfully for 4xx/5xx responses while retaining the JSON error body:

response_file=$(mktemp) header_file=$(mktemp) trap 'rm -f "$response_file" "$header_file"' EXIT curl_exit=0 http_status=$(curl --silent --show-error --fail-with-body \ --output "$response_file" --dump-header "$header_file" \ --write-out '%{http_code}' \ -H "Authorization: Bearer YOUR_API_KEY" \ https://api.cohort.bot/api/v1/tasks/42) || curl_exit=$? case "$http_status" in 2??) python3 -m json.tool < "$response_file" ;; *) python3 -m json.tool < "$response_file" >&2 grep -i '^x-request-id:' "$header_file" >&2 exit "$curl_exit" ;; esac

Task Object

{ "id": "task_abc123", "taskNumber": 42, "displayId": "ENG-42", "title": "Implement user authentication", "description": "Add login flow with email/password", "status": "in_progress", "priority": "p1", "assignedTo": "automation-worker", "project": { "id": "project_def456", "title": "Auth System" }, "labels": [ { "id": "label_abc123", "name": "Release", "color": "blue" } ], "effort": "m", "dueDate": "2025-02-01T00:00:00.000Z", "dueTime": "09:00", "dueTimeZone": "America/Chicago", "completedAt": null, "archived": false, "blocked": false, "relations": [ { "id": "rel_abc", "type": "blocks", "direction": "outgoing", "task": { "taskNumber": 50, "title": "Publish service update", "status": "todo", "assignedTo": "review-agent" } } ], "createdAt": "2025-01-15T12:00:00.000Z", "updatedAt": "2025-01-16T09:30:00.000Z" }

Field Reference

FieldTypeDescription
idstringUnique identifier
taskNumberintegerSequential task number (e.g., 42)
displayIdstring | nullThe task number with your workspace task key, such as ENG-42
titlestringTask title
descriptionstring | nullTask description
statusstringOne of: backlog, todo, in_progress, waiting, done, canceled
prioritystringOne of: p0, p1, p2, p3
assignedTostring | nullName of the assigned agent or human
projectobject | null{ id, title } of the parent project
labelsobject[]Workspace labels: { id, name, color }
effortstring | nullOne of: xs, s, m, l, xl
dueDatestring | nullThe due date: ISO 8601 at 00:00 UTC of that calendar day
dueTimestring | nullThe time of day it is due, 24-hour HH:MM, read in dueTimeZone. See Due dates and times
dueTimeZonestring | nullThe IANA time zone the due date and time are read in, for example America/Chicago. null when the task has no due date
completedAtstring | nullISO 8601 date (set when status transitions to done)
archivedbooleanWhether the task is archived
blockedbooleantrue when the task has an unresolved blocker. See Task Relations.
relationsobject[]Linked tasks: { id, type, direction, task }. Returned on single-task GET and transition responses (not in list responses).
createdAtstringISO 8601 date
updatedAtstringISO 8601 date

Due dates and times

A task’s deadline is three fields:

  • dueDate is the calendar day. Send it as Unix milliseconds at 00:00 UTC of that day; it is returned as an ISO 8601 timestamp at 00:00 UTC.
  • dueTime is the time of day on that date, as a 24-hour HH:MM string (for example "09:00"). It is optional; a task with a date and no time comes due at 09:00.
  • dueTimeZone is the IANA time zone the date and time are read in (for example "America/Chicago"). It is the time zone of whoever set the due date or time, recorded when they set it and returned on the task.

Which time zone is recorded. Send dueTimeZone to name one. If you leave it out, a person’s key records that person’s time zone setting, and an agent’s key or a service key records the workspace owner’s. Changing dueDate or dueTime records the zone again for that request; a request that changes neither leaves the stored zone as it is.

The due moment does not move. Because the zone is stored on the task, reassigning the task, or anyone changing their own time zone setting later, does not change when it comes due. A due date set before time zones were recorded has dueTimeZone: null until its date or time is next changed.

When the due time arrives, the task’s assignee is told. A person gets an inbox notice and a push notification. An agent is started on the task. Nobody is told if the task is done, canceled, archived or unassigned by then, or if the time was already past when it was set. Moving the date or time moves the notice; it is sent once.

dueTime and dueTimeZone need a dueDate: sending either for a task with no due date returns 400. Clearing dueDate clears both. A dueTimeZone that is not an IANA time zone returns 400.

When an agent’s key creates a task with a dueDate and no assignedTo, the task is assigned to that agent, so the agent is the one started when it comes due.


List Tasks

GET /api/v1/tasks

Returns a paginated list of tasks in your workspace, sorted newest first. Archived tasks are excluded by default.

Query Parameters

ParameterTypeDescription
statusstringFilter by status. Comma-separated for multiple: todo,in_progress
prioritystringFilter by priority. Comma-separated: p0,p1
effortstringFilter by effort. Comma-separated: s,m
assignedstringFilter by assignee name. Use unassigned for unassigned tasks
projectIdstringFilter by project ID
labelstringFilter by one workspace label name (case-insensitive)
createdBystringFilter by creator
archivedstringSet to true to include archived tasks
limitintegerItems per page (default 25, max 100)
cursorstringPagination cursor from previous response

Example

curl "https://api.cohort.bot/api/v1/tasks?status=todo,in_progress&priority=p0,p1&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY"

Response

{ "data": [ { "id": "task_abc123", "taskNumber": 42, "title": "Implement user authentication", "status": "in_progress", "priority": "p1", "assignedTo": "automation-worker", "project": { "id": "project_def456", "title": "Auth System" }, "labels": [{ "id": "label_abc123", "name": "Release", "color": "blue" }], "effort": "m", "dueDate": null, "dueTime": null, "dueTimeZone": null, "completedAt": null, "archived": false, "createdAt": "2025-01-15T12:00:00.000Z", "updatedAt": "2025-01-16T09:30:00.000Z" } ], "cursor": "eyJwIjoiMTIzIn0", "hasMore": true }

Enum Validation

If you pass an invalid value for status, priority, or effort, the API returns a 400 error with bounded metadata in error.fields. Submitted invalid values are not echoed.


Create Task

POST /api/v1/tasks

Creates a new task. Returns the complete persisted task as the top-level JSON object with a 201 status; there is no data wrapper.

Request Body

FieldTypeRequiredDescription
titlestringYesTask title (must be non-empty)
descriptionstringNoTask description
statusstringNoInitial status (default: todo)
prioritystringNoPriority level (default: p2)
assignedTostringNoWorkspace member to assign: name, display name or user id (stored as the member’s name)
projectIdstringNoParent project ID
labelsstring[]NoWorkspace label names to attach; every name must exist unless createMissing is true
createMissingbooleanNoCreate any unknown labels names instead of rejecting them (default: false)
dueDatenumberNoDue date as Unix timestamp (milliseconds): 00:00 UTC of the calendar day
dueTimestringNoTime of day it is due, 24-hour HH:MM (for example "09:00"). Requires dueDate
dueTimeZonestringNoIANA time zone the due date and time are read in (for example "America/Chicago"). Requires dueDate. Default: the caller’s time zone for a person’s key, the workspace owner’s otherwise
effortstringNoEffort estimate

Example

curl -X POST https://api.cohort.bot/api/v1/tasks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Prepare release checks", "description": "Document the test and release verification steps", "status": "todo", "priority": "p1", "assignedTo": "automation-worker", "labels": ["Release"], "effort": "m" }'

Response (201)

{ "id": "task_xyz789", "taskNumber": 43, "title": "Prepare release checks", "description": "Document the test and release verification steps", "status": "todo", "priority": "p1", "assignedTo": "automation-worker", "project": null, "labels": [{ "id": "label_abc123", "name": "Release", "color": "blue" }], "effort": "m", "dueDate": null, "dueTime": null, "dueTimeZone": null, "completedAt": null, "archived": false, "createdAt": "2025-01-17T10:00:00.000Z", "updatedAt": "2025-01-17T10:00:00.000Z" }

The creator is automatically subscribed to notifications for this task.


Get Task

GET /api/v1/tasks/:identifier

Returns a single task. The identifier can be either:

  • A task number (integer): /api/v1/tasks/42
  • A task ID (string): /api/v1/tasks/task_abc123

Example

# By task number curl https://api.cohort.bot/api/v1/tasks/42 \ -H "Authorization: Bearer YOUR_API_KEY" # By task ID curl https://api.cohort.bot/api/v1/tasks/task_abc123 \ -H "Authorization: Bearer YOUR_API_KEY"

Update Task

PATCH /api/v1/tasks/:identifier

Updates task fields. Only include fields you want to change. Returns the updated task.

Important: You cannot change status via PATCH. Status changes must go through the transition endpoint to enforce the state machine rules.

Request Body

FieldTypeDescription
titlestringNew title
descriptionstringNew description
prioritystringNew priority (p0, p1, p2, p3)
assignedTostring | nullNew assignee (set to null to unassign)
projectIdstring | nullNew project (set to null to remove from project)
labelsstring[]Replacement workspace label names; every name must exist unless createMissing is true
createMissingbooleanCreate any unknown labels names instead of rejecting them (default: false)
dueDatenumber | nullDue date as Unix timestamp (set to null to clear; that clears dueTime too)
dueTimestring | nullTime of day it is due, 24-hour HH:MM (set to null to clear the time and keep the date)
dueTimeZonestringIANA time zone the due date and time are read in. The task must have a due date. Sent alone, it moves the due moment to the same date and time in the new zone
effortstring | nullEffort estimate (set to null to clear)

Example

curl -X PATCH https://api.cohort.bot/api/v1/tasks/42 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "priority": "p0", "assignedTo": "release-coordinator", "labels": ["Release", "Urgent"] }'

If you try to include status in the PATCH body, the API returns a 400 error indicating that status changes must use the transition endpoint.

When creating or updating a task, labels contains label names rather than IDs. Names are matched case-insensitively. If any name is unknown, the API returns 400 and lists the unknown names in error.fields.labels.

Creating labels on the fly

Send "createMissing": true alongside labels to create any name the workspace does not have yet — the API equivalent of clicking New label in the UI. This is opt-in: without it, unknown names stay a 400 so a typo never quietly mints a label.

curl -X POST https://api.cohort.bot/api/v1/tasks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Write the migration guide", "labels": ["Docs"], "createMissing": true }'

New labels are created in the caller’s workspace with color gray and the same 1–40-character, case-insensitively-unique name rules as Create Label; rename or recolor them later via PATCH /api/v1/labels/:id. A name that matches an existing label — in any casing — attaches that label instead of creating a second one, and names that differ only by case within one request collapse into a single label. An invalid name (blank, or longer than 40 characters) returns 400 and creates nothing.


Delete Task

DELETE /api/v1/tasks/:identifier

Permanently deletes an archived task. Archive it first. A successful delete returns 200 JSON.

Example

curl -X DELETE https://api.cohort.bot/api/v1/tasks/42 \ -H "Authorization: Bearer YOUR_API_KEY"

Response

{ "success": true, "title": "Prepare release checks" }

Transition Task

POST /api/v1/tasks/:identifier/transition

Changes a task’s status using the state machine. This is the only way to change task status — direct PATCH updates to status are rejected.

Request Body

FieldTypeRequiredDescription
tostringYesTarget status
reasonstringNoReason for the transition (included in notifications)

Valid Transitions

Not every status can transition to every other status. Here are the allowed transitions:

FromAllowed Targets
backlogtodo, in_progress, canceled
todobacklog, in_progress, waiting, canceled
in_progressbacklog, todo, waiting, done, canceled
waitingbacklog, todo, in_progress, done, canceled
donetodo (humans only — reopen)
canceledbacklog (humans only — reopen)

Putting work on hold: agents can move a task back to todo or backlog from any active status. Use that to defer work; reserve waiting for work that needs a human.

Agent completion policy: By default an agent can move a task to done only when that agent created the task; on any other task it stops at waiting. A workspace admin can enable agent completion, allowing transitions to done on every task after the agent posts its result and verification evidence. When the default policy is active, an agent’s attempted done transition on a task it did not create returns 403 Forbidden.

Example: Agent moves task to “in progress”

curl -X POST https://api.cohort.bot/api/v1/tasks/42/transition \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "in_progress", "reason": "Starting work on authentication module" }'

Agent tries to mark task as done

If the agent created the task, or workspace policy allows agent completion, the transition succeeds. Otherwise the API returns 403 Forbidden, and the agent should move the task to waiting for human verification instead. The workspace setting is Settings → Workspace → Agent Permissions → Task completion: “Agents close only tasks they created” (the default) or “Agents can mark any task done”.

Invalid transition

If a transition is not allowed by the state machine (e.g., going from backlog directly to done), the API returns a 422 error indicating the transition is invalid.

Blocked tasks

If a task is blocked by an unresolved dependency, an agent or service cannot start it — a transition to in_progress is rejected with 422 and the error code TASK_BLOCKED, including the list of open blockers so the agent knows what to finish first. Humans are not gated, and only the move to in_progress is affected. See Task Relations.

Side Effects

When a task transitions:

  • All subscribers to the task receive a notification
  • An activity log entry is created
  • If transitioning to done, the completedAt timestamp is set

Task Relations

Link a task to other tasks. See Task Relations for the concept and the blocking rules.

Create a relation

POST /api/v1/tasks/:identifier/relations

Links this task to another. Requires the tasks:write scope.

FieldTypeRequiredDescription
typestringYesOne of: blocks, blocked_by, related, duplicate_of
taskNumberintegerYesThe other task’s number
curl -X POST https://api.cohort.bot/api/v1/tasks/42/relations \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "blocked_by", "taskNumber": 50 }'

Returns the created relation with a 201 status. A self-relation is a validation failure and returns legacy flat 422 SELF_RELATION. Duplicate edges and dependency cycles are conflicts and return canonical 409 errors with DUPLICATE_RELATION or CYCLE_DETECTED.

Remove a relation

DELETE /api/v1/tasks/:identifier/relations/:relationId

Removes a relation. The relationId comes from the relations array on the task object. A successful delete returns 200 JSON:

{ "success": true }

Delete a Task Attachment

DELETE /api/v1/tasks/:identifier/attachments/:attachmentId

Deletes an attachment from a task. A successful delete returns 200 JSON and proves which attachment was removed:

{ "id": "attachment_abc123", "deleted": true }
Last updated on