Tasks
Per-endpoint reference for the Tasks resource. Covers full task CRUD plus a dedicated status-move endpoint. Each section below shows the exact path, every parameter, a full request example, and the response shape with a worked example.
Quickstart
Base URL
https://api.tasklife.com/v1/
Auth (all endpoints)
Every request must include a bearer token in the Authorization header. The token is an API key bound to a specific organization — endpoints return only data scoped to that org.
Authorization: Bearer ***
Content-Type: application/json
Response envelope
All successful responses share a common envelope. Error responses are described in Errors below.
{
"success": true,
"data": { /* endpoint-specific payload */ }
}
Note: Tasks endpoints nest the single-task object under data.task.<field> for get/create/update/move, and the list under data.tasks[].<field> for list. The move-status and full update endpoints both return the same envelope shape as the get endpoint. Each card below shows the actual response shape — trust that.
Errors
Failed responses always have success: false and an error object with a stable code and a human-readable message:
{
"success": false,
"error": {
"code": "API_ERROR",
"message": "Column/status is required"
}
}
| HTTP status | When | What to do |
|---|---|---|
400 | Request body failed validation: missing title or status_id, non-existent status_id for the project's column set, malformed due_date, assignee not in the project's team (team-gated projects), etc. | Read error.message; fix the offending field. |
401 | Missing or invalid Authorization header. | Check the API key; confirm the header is present. |
403 | API key is valid but the user lacks permission, or the API key's org does not match the task's org. | Confirm the key's org matches the resource's org. |
404 | Task doesn't exist, is soft-deleted, or isn't visible to this org. | Verify the ID and that the task wasn't deleted. |
409 | Conflict on the range-patch endpoint — VERSION_MISMATCH or HASH_MISMATCH. | Re-fetch the task and retry the patch with the current version and lines[].hash values. |
422 | Patch operation was malformed (MISSING_VERSION, MISSING_OPERATIONS, INVALID_RANGE, INVALID_OPERATION). | Read error.message; fix the patch payload. |
500 | Unexpected server error. | Retry with backoff; report if persistent. |
Tasks
Tasks belong to a project via a column (a.k.a. status): every task is pinned to exactly one project_column_id (returned as project_column_id + column_name). Tasks have a title, a long-form Markdown description, an optional due_date, an optional assigned_org_user_id, and (on installs that opt in) a priority and a parent_task_id for sub-tasks. Each task also surfaces a denormalised task_label (e.g. WEB-42) derived from the owning project's task_prefix.
⚠️ Tasks-specific gotchas
due_datemust beYYYY-MM-DD— the column is a MySQLDATEand is stored verbatim. Sending"2026-07-03T19:48:02Z"(ISO 8601 datetime) or any other format silently produces a0000-00-00or zero-date error. Pass a plain"2026-07-03"string.status_idmust reference a column in the task's project — the service probesproject_columns.idjoined againstprojects.org_id. Astatus_idthat exists in another org (or a deleted column) returns400 — Invalid column or column not in org. There is no separatecolumn_idalias on the wire: the router translatesstatus_id→column_idbefore the service sees it.POST /v1/tasksrequiresstatus_id, not justtitle—TasksService::createTask()throws400 — Column/status is requiredwhen neitherstatus_idnorcolumn_idis provided. Even if you intend the task to land in the default "To Do" column, you must look up that column's id (viaGET /v1/projects/{id}/columns) and send it explicitly.- Move-status endpoint exists separate from full
PUT—PUT /v1/tasks/{id}/statustakes onlystatus_idand bypasses the rest ofupdateTask()'s field-by-field validation. Use it for kanban drag-and-drop. The fullPUT /v1/tasks/{id}route is for bulk field edits and additionally routesstatus_idthrough the full update path (history log, realtime publish, webhooks). - Assignee must belong to the project's team — on team-gated projects (
projects.team_id IS NOT NULL),assigned_org_user_id(and its aliasassigned_to) is rejected with400 — Assignee is not a member of the team associated with this projectif the org user is not in the project's team. This validation runs on both create and update. For QA-type tasks (task_type_id = 5) the assignee must additionally be an active org user. - Setting
assigned_toresetsis_read— on installs that have theis_readcolumn, the update path always setsis_read = 0when the assignee changes. The API never exposes a way to mark a task as read directly; reads are tracked server-side when the assignee fetches the task viaGET /v1/tasks/{id}. - List endpoint has no
limit/offset— the router does not passlimitoroffsettoTasksService::listTasks(). The only supported filters areproject_id,status_id(a.k.a.column_id),assigned_to, and free-textq/search(matchestitle+descriptionviaLIKE %...%, or aPREFIX-Nshort-id). Results are always ordered bycreated_at DESC. Plan pagination client-side (e.g. paginate by last-seenid). - Older installs omit some fields — the service probes
SHOW COLUMNS FROM tasksforpriority,parent_task_id,is_read, andtask_versionsat runtime. When absent, the corresponding fields are silently dropped from the response — not returned asnull. The lifecycle prompt fields (lifecycle_*) come fromproject_columnsand may also be absent on older installs.
Endpoints at a glance
| Action | Method + Path | Summary |
|---|---|---|
| List tasks | GET /tasks | List every task in the API key's org, filterable by project, status, assignee, or free-text q. No pagination. |
| Create a task | POST /tasks | Create a task. Required: title and status_id. Returns 201 with the full task. |
| Get a task | GET /tasks/{id} | Fetch a single task including description, project/column metadata, and last-comment summary. |
| Update a task | PUT /tasks/{id} or PATCH /tasks/{id} | Update one or more fields. status_id is routed through the full update path (history + realtime + webhooks). |
| Move task status | PUT /tasks/{id}/status | Move a task to a different column. Body: {"status_id": NUMBER}. Lightweight — bypasses field-by-field validation. |
| Delete a task | DELETE /tasks/{id} | Permanently delete a task (also clears its versions). Returns {"success": true}. |
Heads up: the dedicated range-patch endpoint (PATCH /v1/tasks/{id} with baseVersion + ops[] for conflict-safe description edits) is implemented at the router layer and the service has patchTask() backing it, but it lives outside the six endpoints documented above and is exercised by the editor UI rather than external integrations. The GET /v1/tasks/{id}/versions and GET /v1/tasks/{id}/history sub-resources are also reachable but not documented here.
GET /tasks
List every task that belongs to the API key's org, ordered by created_at DESC. Optional filters narrow by project_id, status_id (a.k.a. column_id), assigned_to, or free-text q. There is no pagination — the router does not pass limit / offset.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
project_id |
integer |
no | Restrict to tasks whose column belongs to this project. |
status_id |
integer |
no | Restrict to tasks in this column. Alias: column_id. status_id takes precedence when both are present. |
assigned_to |
integer |
no | Restrict to tasks assigned to this org_user_id. |
q / search |
string |
no | Free-text filter. If the value matches PREFIX-N (e.g. WEB-42) it is parsed as a short-id lookup (exact task_prefix + task.id). Otherwise it does a case-insensitive LIKE %q% against title + description. |
Request example
curl -sS https://api.tasklife.com/v1/tasks?project_id=142&status_id=502 \
-H "Authorization: Bearer ***"
Response 200 OK
The array is nested under data.tasks[]:
| Field | Type | Description |
|---|---|---|
data.tasks[].id | integer | Task id. |
data.tasks[].title | string | Task title. |
data.tasks[].description | string | Markdown description (may be ""). |
data.tasks[].due_date | string | null | Due date as YYYY-MM-DD string, or null. |
data.tasks[].priority | string | null | Priority label. Present only when the tasks.priority column exists. |
data.tasks[].task_type_id | integer | null | Task type id (project-specific). null when the project doesn't use task types. |
data.tasks[].parent_task_id | integer | null | Parent task id for sub-tasks. Present only when the tasks.parent_task_id column exists. |
data.tasks[].child_count | integer | Number of sub-tasks whose parent_task_id = this.id. Present only with parent_task_id; always 0 otherwise. |
data.tasks[].has_children | boolean | Convenience: child_count > 0. |
data.tasks[].project_column_id | integer | Owning column id. |
data.tasks[].column_name | string | Display name of the column (e.g. In Progress). |
data.tasks[].project_id | integer | Owning project id. |
data.tasks[].project_name | string | Display name of the project. |
data.tasks[].task_prefix | string | null | Project's task_prefix (e.g. WEB), or null. |
data.tasks[].task_label | string | Computed label: PREFIX-id when task_prefix is set, otherwise #id. |
data.tasks[].lifecycle_get_prompt | string | Prompt template used by the agent when fetching work from this column. Empty string when absent. |
data.tasks[].lifecycle_put_prompt | string | Prompt template used by the agent when reporting work back. Empty string when absent. |
data.tasks[].lifecycle_start_target_column_id | integer | null | Column id the agent moves tasks into on start. |
data.tasks[].lifecycle_blocked_target_column_id | integer | null | Column id the agent moves tasks into when blocked. |
data.tasks[].lifecycle_complete_target_column_id | integer | null | Column id the agent moves tasks into on complete. |
data.tasks[].lifecycle_has_issues_target_column_id | integer | null | Column id the agent moves tasks into on has issues. |
data.tasks[].target_columns | object | Convenience map mirroring the four lifecycle target ids: { dev_start, dev_blocked, dev_complete, qa_issues }. |
data.tasks[].assigned_org_user_id | integer | null | Assignee org user id, or null. |
data.tasks[].assignee_name | string | null | Assignee display name. |
data.tasks[].assignee_email | string | null | Assignee email. |
data.tasks[].created_by_user_id | integer | User id who created the task. |
data.tasks[].created_via | string | Origin channel (e.g. "api", "ui"). |
data.tasks[].creator_name | string | null | Creator display name. |
data.tasks[].creator_email | string | null | Creator email. |
{
"success": true,
"data": {
"tasks": [
{
"id": 8412,
"title": "Migrate billing flow to Stripe",
"description": "Replace outdated invoicing with Stripe subscriptions.\n\nOwner: @alex",
"due_date": "2026-07-21",
"priority": "High",
"task_type_id": null,
"parent_task_id": null,
"child_count": 2,
"has_children": true,
"project_column_id": 502,
"column_name": "In Progress",
"project_id": 142,
"project_name": "Website Relaunch",
"task_prefix": "WEB",
"task_label": "WEB-8412",
"lifecycle_get_prompt": "",
"lifecycle_put_prompt": "",
"lifecycle_start_target_column_id": null,
"lifecycle_blocked_target_column_id": null,
"lifecycle_complete_target_column_id": null,
"lifecycle_has_issues_target_column_id": null,
"target_columns": {
"dev_start": null,
"dev_blocked": null,
"dev_complete": null,
"qa_issues": null
},
"assigned_org_user_id": 318,
"assignee_name": "Alex Rivera",
"assignee_email": "[email protected]",
"created_by_user_id": 42,
"created_via": "api",
"creator_name": "Priya Shah",
"creator_email": "[email protected]"
},
{
"id": 8409,
"title": "Write launch announcement blog post",
"description": "",
"due_date": null,
"priority": "Medium",
"task_type_id": null,
"parent_task_id": null,
"child_count": 0,
"has_children": false,
"project_column_id": 501,
"column_name": "To Do",
"project_id": 142,
"project_name": "Website Relaunch",
"task_prefix": "WEB",
"task_label": "WEB-8409",
"lifecycle_get_prompt": "",
"lifecycle_put_prompt": "",
"lifecycle_start_target_column_id": null,
"lifecycle_blocked_target_column_id": null,
"lifecycle_complete_target_column_id": null,
"lifecycle_has_issues_target_column_id": null,
"target_columns": {
"dev_start": null,
"dev_blocked": null,
"dev_complete": null,
"qa_issues": null
},
"assigned_org_user_id": null,
"assignee_name": null,
"assignee_email": null,
"created_by_user_id": 42,
"created_via": "ui",
"creator_name": "Priya Shah",
"creator_email": "[email protected]"
}
]
}
}
POST /tasks
Create a new task pinned to the column given by status_id. The response is the full task object (same envelope as GET /tasks/{id}) with the freshly-assigned id and computed task_label.
title AND status_id. The service throws 400 — Column/status is required when status_id (or column_id) is missing, even if you intended the task to land in the default column. Look up the column id first via GET /v1/projects/{id}/columns.
Body parameters
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
title |
string |
yes | — | Task title. Cannot be empty (returns 400). |
status_id |
integer |
yes | — | Column id the task lives in. Must belong to this org (router maps status_id → column_id). Returns 400 — Invalid column or column not in org otherwise. |
column_id |
integer |
no | — | Alias for status_id. Ignored if status_id is also present. |
project_id |
integer |
no | derived from column | Project id. Not strictly required by the router or service — the project is inferred from status_id's owning column. Sent in the body mostly for client convenience / logging. |
description |
string |
no | "" |
Markdown description. No practical length cap. |
due_date |
string |
no | null |
Due date as YYYY-MM-DD (e.g. "2026-07-21"). ISO 8601 datetimes are rejected by the DATE column. |
assigned_to |
integer |
no | null |
Assignee org_user_id. Aliases: assigned_org_user_id, assignee_org_user_id, assignee_id. On team-gated projects the assignee must be a member of the project's team. For QA tasks (task_type_id = 5) the assignee must be active. |
priority |
string |
no | null |
Priority label. Stored only when the tasks.priority column exists. Trimmed; empty string becomes null. |
task_type_id |
integer |
conditional | project default (when task_types_enabled) |
Required only when the target project has task_types_enabled = 1 and no default type is configured. Otherwise ignored. Must be an active task type enabled for this project. |
parent_task_id |
integer |
no | null |
Parent task id (sub-task). Stored only when the tasks.parent_task_id column exists. Must reference a task in this org. |
Request example
curl -sS -X POST https://api.tasklife.com/v1/tasks \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{
"title": "Migrate billing flow to Stripe",
"status_id": 502,
"project_id": 142,
"description": "Replace outdated invoicing with Stripe subscriptions.",
"due_date": "2026-07-21",
"assigned_to": 318,
"priority": "High"
}'
Response 201 Created
The created task is nested under data.task:
| Field | Type | Description |
|---|---|---|
data.task.id | integer | New task id. |
data.task.title | string | Echo of the title. |
data.task.description | string | Echo of the description. |
data.task.due_date | string | null | Echo of the due date. |
data.task.project_column_id | integer | Column id the task was pinned to. |
data.task.project_id | integer | Resolved project id. |
data.task.task_label | string | Computed label, e.g. WEB-8412. |
data.task.<field> | mixed | All other fields match the get response shape: assignee, priority, task_type_id, parent_task_id, lifecycle_*, target_columns, comment_count, etc. |
{
"success": true,
"data": {
"task": {
"id": 8412,
"title": "Migrate billing flow to Stripe",
"description": "Replace outdated invoicing with Stripe subscriptions.",
"due_date": "2026-07-21",
"priority": "High",
"task_type_id": null,
"parent_task_id": null,
"child_count": 0,
"has_children": false,
"project_column_id": 502,
"column_name": "In Progress",
"project_id": 142,
"project_name": "Website Relaunch",
"task_prefix": "WEB",
"task_label": "WEB-8412",
"lifecycle_get_prompt": "",
"lifecycle_put_prompt": "",
"lifecycle_start_target_column_id": null,
"lifecycle_blocked_target_column_id": null,
"lifecycle_complete_target_column_id": null,
"lifecycle_has_issues_target_column_id": null,
"target_columns": {
"dev_start": null,
"dev_blocked": null,
"dev_complete": null,
"qa_issues": null
},
"assigned_org_user_id": 318,
"assignee_name": "Alex Rivera",
"assignee_email": "[email protected]",
"created_by_user_id": 42,
"created_via": "api",
"creator_name": "Priya Shah",
"creator_email": "[email protected]",
"comment_count": 0,
"last_comment_id": null,
"last_comment_at": null,
"last_comment_by_user_id": null,
"last_comment_by": null,
"last_comment_body_markdown": null,
"version": 1,
"etag": "W/\"1_e0\"",
"media": [],
"attachments": []
}
}
}
Errors
{
"success": false,
"error": {
"code": "API_ERROR",
"message": "Column/status is required"
}
}
GET /tasks/{id}
Fetch a single task by id, including its full description, project/column metadata, last-comment summary, media attachments, and current version + etag for conflict-safe patches. Returns 404 if the task doesn't exist or belongs to a different org.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Task id. |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
lines | string ("true") | no | Pass ?lines=true to include a lines array on the response (one entry per description line with n, start, end, hash) — needed for editor patch operations. Default: omitted. |
Request example
curl -sS https://api.tasklife.com/v1/tasks/8412 \
-H "Authorization: Bearer ***"
Response 200 OK
The task is nested under data.task:
| Field | Type | Description |
|---|---|---|
data.task.id | integer | Task id. |
data.task.title | string | Task title. |
data.task.description | string | Markdown description. |
data.task.due_date | string | null | Due date YYYY-MM-DD or null. |
data.task.priority | string | null | Priority label. Present only when tasks.priority column exists. |
data.task.task_type_id | integer | null | Task type id, or null. |
data.task.parent_task_id | integer | null | Parent task id for sub-tasks. Present only with tasks.parent_task_id column. |
data.task.child_count | integer | Number of direct sub-tasks. |
data.task.has_children | boolean | Convenience: child_count > 0. |
data.task.is_read | integer (0 or 1) | Read receipt. Present only with tasks.is_read column. Server-side: the service automatically flips this to 1 when the assignee fetches the task. |
data.task.project_column_id | integer | Owning column id. |
data.task.column_name | string | Display name of the column. |
data.task.project_id | integer | Owning project id. |
data.task.project_name | string | Owning project display name. |
data.task.task_prefix | string | null | Project's task_prefix or null. |
data.task.task_label | string | Computed label: PREFIX-id or #id. |
data.task.lifecycle_get_prompt | string | Prompt template used when the agent fetches work from this column. |
data.task.lifecycle_put_prompt | string | Prompt template used when the agent reports work back. |
data.task.lifecycle_start_target_column_id | integer | null | Column id for start transitions. |
data.task.lifecycle_blocked_target_column_id | integer | null | Column id for blocked transitions. |
data.task.lifecycle_complete_target_column_id | integer | null | Column id for complete transitions. |
data.task.lifecycle_has_issues_target_column_id | integer | null | Column id for has issues transitions. |
data.task.target_columns | object | Convenience map: { dev_start, dev_blocked, dev_complete, qa_issues }. |
data.task.assigned_org_user_id | integer | null | Assignee org user id, or null. |
data.task.assignee_name | string | null | Assignee display name. |
data.task.assignee_email | string | null | Assignee email. |
data.task.created_by_user_id | integer | User id who created the task. |
data.task.created_via | string | Origin channel ("api", "ui", etc.). |
data.task.creator_name | string | null | Creator display name. |
data.task.creator_email | string | null | Creator email. |
data.task.comment_count | integer | Number of non-deleted comments on this task. |
data.task.last_comment_id | integer | null | Id of the most recent non-deleted comment, or null. |
data.task.last_comment_at | string | null | Timestamp of the most recent non-deleted comment. |
data.task.last_comment_by_user_id | integer | null | User id of the most recent commenter, or null. |
data.task.last_comment_by | string | null | Display name of the most recent commenter, or null. |
data.task.last_comment_body_markdown | string | null | Body of the most recent comment, or null. |
data.task.version | integer | Current description version (1 on a fresh task; increments on every patch). Pass to PATCH as baseVersion for conflict-safe updates. |
data.task.etag | string | Weak ETag in the form W/"<version>_<6-char-content-hash>". |
data.task.media | array | Attached media records (uploads). |
data.task.attachments | array | Alias of media. |
{
"success": true,
"data": {
"task": {
"id": 8412,
"title": "Migrate billing flow to Stripe",
"description": "Replace outdated invoicing with Stripe subscriptions.\n\nOwner: @alex",
"due_date": "2026-07-21",
"priority": "High",
"task_type_id": null,
"parent_task_id": null,
"child_count": 2,
"has_children": true,
"is_read": 0,
"project_column_id": 502,
"column_name": "In Progress",
"project_id": 142,
"project_name": "Website Relaunch",
"task_prefix": "WEB",
"task_label": "WEB-8412",
"lifecycle_get_prompt": "",
"lifecycle_put_prompt": "",
"lifecycle_start_target_column_id": null,
"lifecycle_blocked_target_column_id": null,
"lifecycle_complete_target_column_id": null,
"lifecycle_has_issues_target_column_id": null,
"target_columns": {
"dev_start": null,
"dev_blocked": null,
"dev_complete": null,
"qa_issues": null
},
"assigned_org_user_id": 318,
"assignee_name": "Alex Rivera",
"assignee_email": "[email protected]",
"created_by_user_id": 42,
"created_via": "api",
"creator_name": "Priya Shah",
"creator_email": "[email protected]",
"comment_count": 3,
"last_comment_id": 9123,
"last_comment_at": "2026-07-02T18:11:09Z",
"last_comment_by_user_id": 42,
"last_comment_by": "Priya Shah",
"last_comment_body_markdown": "Looks good \u2014 ship it.",
"version": 4,
"etag": "W/\"4_a1b2c3\"",
"media": [],
"attachments": []
}
}
}
Errors
{
"success": false,
"error": {
"code": "API_ERROR",
"message": "Task not found"
}
}
PUT /tasks/{id} (PATCH is also accepted)
Update one or more fields on a task. Omitted fields are unchanged. Returns the updated task (same envelope as GET /tasks/{id}). Sending status_id here routes through the full update path — the service writes a status_change history row and emits a realtime task-moved event. For pure kanban drag-and-drop prefer PUT /tasks/{id}/status.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Task id. |
Body parameters
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
title |
string |
no | — | New task title. |
description |
string |
no | — | New Markdown description. For conflict-safe line-level edits use the dedicated PATCH range-patch endpoint instead. |
due_date |
string | null |
no | — | New due date as YYYY-MM-DD, or null to clear. |
status_id |
integer |
no | — | Move the task to this column. Router maps status_id → column_id. Must reference a column in this org. |
column_id |
integer |
no | — | Alias for status_id. Ignored if status_id is also present. |
assigned_to |
integer | null |
no | — | New assignee org_user_id, or null to unassign. Aliases: assigned_org_user_id, assignee_org_user_id, assignee_id. Setting the assignee resets is_read = 0 on installs that have the column. Triggers a task.assigned webhook outbox event when the assignee actually changes. |
priority |
string | null |
no | — | New priority. Stored only when tasks.priority column exists. Empty string becomes null. |
task_type_id |
integer | null |
no | project default (when task_types_enabled) |
New task type. Empty string clears the field (sets it to null). When the project has task_types_enabled = 1 and you don't supply a value, the project default is applied. |
parent_task_id |
integer | null |
no | — | New parent task id, or empty/null to detach. Stored only with tasks.parent_task_id column. Cannot equal the task's own id (returns 400). |
Request example
curl -sS -X PUT https://api.tasklife.com/v1/tasks/8412 \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{
"title": "Migrate billing flow to Stripe (incl. proration)",
"status_id": 503,
"assigned_to": 407,
"priority": "Critical"
}'
Response 200 OK
Returns the updated task nested under data.task — identical envelope shape to GET /tasks/{id}:
| Field | Type | Description |
|---|---|---|
data.task.<field> | mixed | Same fields as the get response. project_column_id reflects the new column when status_id was sent. |
{
"success": true,
"data": {
"task": {
"id": 8412,
"title": "Migrate billing flow to Stripe (incl. proration)",
"description": "Replace outdated invoicing with Stripe subscriptions.\n\nOwner: @alex",
"due_date": "2026-07-21",
"priority": "Critical",
"task_type_id": null,
"parent_task_id": null,
"child_count": 2,
"has_children": true,
"is_read": 0,
"project_column_id": 503,
"column_name": "In Review",
"project_id": 142,
"project_name": "Website Relaunch",
"task_prefix": "WEB",
"task_label": "WEB-8412",
"lifecycle_get_prompt": "",
"lifecycle_put_prompt": "",
"lifecycle_start_target_column_id": null,
"lifecycle_blocked_target_column_id": null,
"lifecycle_complete_target_column_id": null,
"lifecycle_has_issues_target_column_id": null,
"target_columns": {
"dev_start": null,
"dev_blocked": null,
"dev_complete": null,
"qa_issues": null
},
"assigned_org_user_id": 407,
"assignee_name": "Jordan Park",
"assignee_email": "[email protected]",
"created_by_user_id": 42,
"created_via": "api",
"creator_name": "Priya Shah",
"creator_email": "[email protected]",
"comment_count": 3,
"last_comment_id": 9123,
"last_comment_at": "2026-07-02T18:11:09Z",
"last_comment_by_user_id": 42,
"last_comment_by": "Priya Shah",
"last_comment_body_markdown": "Looks good \u2014 ship it.",
"version": 4,
"etag": "W/\"4_a1b2c3\"",
"media": [],
"attachments": []
}
}
}
Errors
{
"success": false,
"error": {
"code": "API_ERROR",
"message": "Assignee is not a member of the team associated with this project"
}
}
PUT /tasks/{id}/status (PATCH is also accepted)
Move a task to a different column. Lightweight — the router passes only status_id (or its alias column_id) to the service, so no other fields are validated or persisted. Prefer this for kanban drag-and-drop because it skips the field-by-field validation that runs on full PUT /tasks/{id}.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Task id. |
Body parameters
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
status_id |
integer |
yes | — | Target column id. Must belong to this org; the service probes project_columns → projects.org_id. |
column_id |
integer |
no | — | Alias for status_id. Ignored if status_id is also present. |
Request example
curl -sS -X PUT https://api.tasklife.com/v1/tasks/8412/status \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{
"status_id": 503
}'
Response 200 OK
Returns the updated task nested under data.task — same envelope as the get/update endpoints:
| Field | Type | Description |
|---|---|---|
data.task.id | integer | Task id (unchanged). |
data.task.project_column_id | integer | New column id. |
data.task.column_name | string | Display name of the new column. |
data.task.<field> | mixed | All other fields from the get response, unchanged. |
{
"success": true,
"data": {
"task": {
"id": 8412,
"title": "Migrate billing flow to Stripe",
"description": "Replace outdated invoicing with Stripe subscriptions.\n\nOwner: @alex",
"due_date": "2026-07-21",
"priority": "High",
"task_type_id": null,
"parent_task_id": null,
"child_count": 2,
"has_children": true,
"project_column_id": 503,
"column_name": "In Review",
"project_id": 142,
"project_name": "Website Relaunch",
"task_prefix": "WEB",
"task_label": "WEB-8412",
"lifecycle_get_prompt": "",
"lifecycle_put_prompt": "",
"lifecycle_start_target_column_id": null,
"lifecycle_blocked_target_column_id": null,
"lifecycle_complete_target_column_id": null,
"lifecycle_has_issues_target_column_id": null,
"target_columns": {
"dev_start": null,
"dev_blocked": null,
"dev_complete": null,
"qa_issues": null
},
"assigned_org_user_id": 318,
"assignee_name": "Alex Rivera",
"assignee_email": "[email protected]",
"created_by_user_id": 42,
"created_via": "api",
"creator_name": "Priya Shah",
"creator_email": "[email protected]",
"comment_count": 3,
"last_comment_id": 9123,
"last_comment_at": "2026-07-02T18:11:09Z",
"last_comment_by_user_id": 42,
"last_comment_by": "Priya Shah",
"last_comment_body_markdown": "Looks good \u2014 ship it.",
"version": 4,
"etag": "W/\"4_a1b2c3\"",
"media": [],
"attachments": []
}
}
}
Errors
{
"success": false,
"error": {
"code": "API_ERROR",
"message": "status_id is required"
}
}
DELETE /tasks/{id}
Permanently delete a task and its versions. Returns {"success": true} on success. There is no soft-delete here — the row is removed from tasks (and task_versions if the table exists). Returns 404 if the task doesn't exist or isn't in this org.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Task id. |
Request example
curl -sS -X DELETE https://api.tasklife.com/v1/tasks/8412 \
-H "Authorization: Bearer ***"
Response 200 OK
The response carries success: true but no data key — the resource is simply gone.
| Field | Type | Description |
|---|---|---|
success | boolean | Always true on success. No data envelope is returned. |
{
"success": true
}
{
"success": false,
"error": {
"code": "API_ERROR",
"message": "Task not found"
}
}