Projects
Per-endpoint reference for the Projects resource. Covers project CRUD plus the kanban-style columns (statuses) inside each project. 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 <yo...ode>
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: Projects endpoints nest results under data.project.<field> for single-project responses (get/create/update) and under data.projects[0].<field> for list. Column endpoints nest under data.column.<field> or data.columns[...]. Each endpoint below shows its 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": "Task prefix must be 2-4 uppercase letters (A-Z)"
}
}
| HTTP status | When | What to do |
|---|---|---|
400 | Request body failed validation (missing required field, wrong type, empty name, invalid task_prefix, etc.). | Read error.message; fix the offending field. |
403 | API key is valid but the user lacks permission (e.g. only org admins may set agent_team_active). | Confirm the user has org-admin role and the API key's org matches the resource's org. |
404 | Project or column doesn't exist, or isn't visible to this org. | Verify the ID and that the resource hasn't been deleted. |
409 | task_prefix is already in use by another project in the same org. | Read error.message and choose a different task_prefix. |
Projects
Kanban-style projects. A project has a name, a unique short task_prefix used in task IDs (optional), a description (optional), a list of priority labels, and a set of named columns that tasks move through. New projects are seeded with three default columns: To Do, In Progress, Done.
⚠️ Projects-specific gotchas
task_prefixmust be unique within the org — validated onPOST /v1/projectsandPUT /v1/projects/{id}byassertTaskPrefixUnique(). Format: 2–4 uppercase letters (e.g.WEB,API). A second request with the same prefix in the same org returns409.agent_team_activerequires org admin — setting this field onPUT /v1/projects/{id}is gated byassertOrgAdminForProjectMutation(). Non-admin requests are rejected with403: Admin access required to update agent_team_active.
Note: older installations may also lack the description, task_priorities, default_task_priority, and agent_team_active columns (the service probes with SHOW COLUMNS at runtime). When those columns are absent the corresponding fields are simply not returned — not an error.
Endpoints at a glance
| Action | Method + Path | Summary |
|---|---|---|
| List projects | GET /projects | List all projects for the API key's org, sorted by name. |
| Create a project | POST /projects | Create a project and seed it with three default columns. Returns 201 with the full project + columns. |
| Get a project | GET /projects/{id} | Fetch a single project including its columns array. |
| Update a project | PUT /projects/{id} or PATCH /projects/{id} | Update one or more fields on a project. agent_team_active is admin-gated. |
| List columns | GET /projects/{id}/columns | List the columns (statuses) for a project, ordered by sort_order. |
| Create a column | POST /projects/{id}/columns | Add a column to a project. Returns 201 with the created column. |
| Update a column | PUT /projects/{id}/columns/{columnId} or PATCH ... | Rename, reorder, or update lifecycle config on a column. |
| Delete a column | DELETE /projects/{id}/columns/{columnId} | Delete a column. Returns 400 if the column still has tasks. |
Heads up: there is no DELETE /v1/projects/{id} endpoint — projects themselves cannot be deleted via the API (only their columns can). The routing block at external-api/v1/index.php returns 404 for any unhandled projects path.
GET /projects
List every project that belongs to the API key's org. Results are sorted alphabetically by name. The returned projects do not include a columns array — call GET /projects/{id} to fetch a project with its columns.
Request example
curl -sS https://api.tasklife.com/v1/projects \
-H "Authorization: Bearer $API_KEY"
Response 200 OK
The array is nested under data.projects[]:
| Field | Type | Description |
|---|---|---|
data.projects[].id | integer | Project id. Use this for follow-up get/update/columns calls. |
data.projects[].name | string | Project display name. |
data.projects[].task_prefix | string | null | 2–4 uppercase letters used to prefix task IDs (e.g. WEB-12). null if not set. |
data.projects[].description | string | Free-form description. Present only on installations that have the description column; older installs omit it. |
data.projects[].agent_team_active | integer (0 or 1) | Whether the AI agent team is active for this project. 1 = on, 0 = off. Present only when the column exists; defaults to 1 when missing. |
data.projects[].task_priorities | array<string> | Available priority labels in order. Defaults to ["High", "Medium", "Low"] when task_priorities_json column is absent. |
data.projects[].default_task_priority | string | Priority applied to new tasks by default. Must be one of task_priorities. |
data.projects[].created_at | string (ISO 8601 datetime) | Project creation timestamp. |
data.projects[].updated_at | string (ISO 8601 datetime) | Last update timestamp. |
{
"success": true,
"data": {
"projects": [
{
"id": 42,
"name": "Website Relaunch",
"task_prefix": "WEB",
"description": "Q2 marketing site redesign & launch.",
"agent_team_active": 1,
"task_priorities": ["Critical", "High", "Medium", "Low"],
"default_task_priority": "Medium",
"created_at": "2026-04-12T18:21:04Z",
"updated_at": "2026-06-29T14:08:51Z"
},
{
"id": 87,
"name": "Mobile App",
"task_prefix": "MOB",
"description": "iOS + Android client work.",
"agent_team_active": 0,
"task_priorities": ["High", "Medium", "Low"],
"default_task_priority": "High",
"created_at": "2026-05-02T09:11:32Z",
"updated_at": "2026-06-15T22:00:09Z"
}
]
}
}
POST /projects
Create a new project. The service seeds the project with three default columns (To Do / In Progress / Done) and returns the full project including those columns.
Body parameters
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
yes | — | Project display name. Cannot be empty. |
description |
string |
no | "" |
Free-form description. Ignored on installations that lack the description column. |
task_prefix |
string |
no | null |
2–4 uppercase letters (A–Z). Must be unique within the org — duplicates return 409. |
task_priorities |
array<string> |
no | ["High","Medium","Low"] |
Available priority labels, in display order. Duplicates and blank entries are stripped. Stored as JSON; ignored if task_priorities_json column is absent. |
default_task_priority |
string |
no | first entry of task_priorities |
Priority applied to new tasks by default. Must be one of task_priorities; otherwise it falls back to the first entry. |
Request example
curl -sS -X POST https://api.tasklife.com/v1/projects \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Website Relaunch",
"description": "Q2 marketing site redesign & launch.",
"task_prefix": "WEB",
"task_priorities": ["Critical", "High", "Medium", "Low"],
"default_task_priority": "Medium"
}'
Response 201 Created
The created project (with its seeded columns) is nested under data.project:
| Field | Type | Description |
|---|---|---|
data.project.id | integer | New project id. |
data.project.name | string | Echo of the name you sent. |
data.project.task_prefix | string | null | Echo of the prefix (uppercased) or null. |
data.project.description | string | Echo of the description (may be absent on older installs). |
data.project.agent_team_active | integer (0 or 1) | Agent team toggle (defaults to 1; field may be absent on older installs). |
data.project.task_priorities | array<string> | Stored priority labels as a clean array. |
data.project.default_task_priority | string | Resolved default priority. |
data.project.created_at | string (ISO 8601 datetime) | Creation timestamp. |
data.project.updated_at | string (ISO 8601 datetime) | Update timestamp (== created_at on create). |
data.project.columns | array | Default columns seeded in order: To Do, In Progress, Done. Each entry follows the column shape documented under List columns. |
{
"success": true,
"data": {
"project": {
"id": 142,
"name": "Website Relaunch",
"task_prefix": "WEB",
"description": "Q2 marketing site redesign & launch.",
"agent_team_active": 1,
"task_priorities": ["Critical", "High", "Medium", "Low"],
"default_task_priority": "Medium",
"created_at": "2026-07-03T19:48:02Z",
"updated_at": "2026-07-03T19:48:02Z",
"columns": [
{ "id": 501, "project_id": 142, "name": "To Do", "sort_order": 1, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" },
{ "id": 502, "project_id": 142, "name": "In Progress", "sort_order": 2, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" },
{ "id": 503, "project_id": 142, "name": "Done", "sort_order": 3, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" }
]
}
}
}
GET /projects/{id}
Fetch a single project by id, including its columns array ordered by sort_order. Returns 404 if the project doesn't exist or belongs to a different org.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Project id. |
Request example
curl -sS https://api.tasklife.com/v1/projects/142 \
-H "Authorization: Bearer $API_KEY"
Response 200 OK
The project is nested under data.project:
| Field | Type | Description |
|---|---|---|
data.project.id | integer | Project id. |
data.project.name | string | Project display name. |
data.project.task_prefix | string | null | 2–4 letter prefix or null. |
data.project.description | string | Description (omitted on installs missing the column). |
data.project.agent_team_active | integer (0 or 1) | Agent team toggle. |
data.project.task_priorities | array<string> | Available priority labels. |
data.project.default_task_priority | string | Default priority for new tasks. |
data.project.created_at | string (ISO 8601 datetime) | Creation timestamp. |
data.project.updated_at | string (ISO 8601 datetime) | Last update timestamp. |
data.project.columns | array | Columns in sort_order order. Each entry: { id, project_id, name, sort_order, lifecycle_enabled, lifecycle_start_target_column_id, lifecycle_blocked_target_column_id, lifecycle_complete_target_column_id, lifecycle_has_issues_target_column_id, lifecycle_get_prompt, lifecycle_put_prompt }. Lifecycle fields are present only when the lifecycle_* columns exist in project_columns. |
{
"success": true,
"data": {
"project": {
"id": 142,
"name": "Website Relaunch",
"task_prefix": "WEB",
"description": "Q2 marketing site redesign & launch.",
"agent_team_active": 1,
"task_priorities": ["Critical", "High", "Medium", "Low"],
"default_task_priority": "Medium",
"created_at": "2026-07-03T19:48:02Z",
"updated_at": "2026-07-03T19:48:02Z",
"columns": [
{ "id": 501, "project_id": 142, "name": "To Do", "sort_order": 1, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" },
{ "id": 502, "project_id": 142, "name": "In Progress", "sort_order": 2, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" },
{ "id": 503, "project_id": 142, "name": "Done", "sort_order": 3, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" }
]
}
}
}
PUT /projects/{id} (PATCH is also accepted)
Update one or more fields on a project. Omitted fields are unchanged. The request returns the updated project (same envelope as GET /projects/{id}).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Project id. |
Body parameters
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
no | — | New project name. Trimmed; cannot be empty (returns 400 otherwise). |
description |
string |
no | — | New description. Ignored on installs that lack the column. |
task_prefix |
string |
no | — | 2–4 uppercase letters. Must be unique within the org (excluding this project); returns 409 on conflict. |
task_priorities |
array<string> |
no | — | Replace the priority label set. Stored as a cleaned JSON array. |
default_task_priority |
string |
no | — | New default priority. Must match one of the (current) task_priorities; otherwise falls back to the first entry. |
agent_team_active |
integer (0 or 1) |
no | — | Toggle the AI agent team for this project. Admin-only: callers without org-admin role receive 403. Boolean true/false and the strings "1"/"0"/"true"/"yes"/"on"/"active" are also accepted. |
Request example
curl -sS -X PUT https://api.tasklife.com/v1/projects/142 \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Website Relaunch (Q2)",
"task_prefix": "WEB",
"agent_team_active": 1
}'
Response 200 OK
Returns the updated project nested under data.project — identical envelope shape to GET /projects/{id}:
| Field | Type | Description |
|---|---|---|
data.project.<field> | mixed | Same fields as the get response. updated_at is bumped; columns reflects the current column list (unchanged). |
{
"success": true,
"data": {
"project": {
"id": 142,
"name": "Website Relaunch (Q2)",
"task_prefix": "WEB",
"description": "Q2 marketing site redesign & launch.",
"agent_team_active": 1,
"task_priorities": ["Critical", "High", "Medium", "Low"],
"default_task_priority": "Medium",
"created_at": "2026-07-03T19:48:02Z",
"updated_at": "2026-07-03T20:15:11Z",
"columns": [
{ "id": 501, "project_id": 142, "name": "To Do", "sort_order": 1, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" },
{ "id": 502, "project_id": 142, "name": "In Progress", "sort_order": 2, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" },
{ "id": 503, "project_id": 142, "name": "Done", "sort_order": 3, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" }
]
}
}
}
GET /projects/{id}/columns
List the columns (statuses) for a project, ordered by sort_order (then id as a tiebreaker).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Project id. |
Request example
curl -sS https://api.tasklife.com/v1/projects/142/columns \
-H "Authorization: Bearer $API_KEY"
Response 200 OK
The array is nested under data.columns[]:
| Field | Type | Description |
|---|---|---|
data.columns[].id | integer | Column id. |
data.columns[].project_id | integer | Owning project id. |
data.columns[].name | string | Column display name. |
data.columns[].sort_order | integer | Display order (1-based). Tasks move left-to-right. |
data.columns[].lifecycle_enabled | integer (0 or 1) | Whether AI agent lifecycle automation is enabled for this column. Present only when the project_columns table has the lifecycle columns; defaults to 0 on older installs. |
data.columns[].lifecycle_start_target_column_id | integer | null | Column id to move tasks into when an agent "starts" work. |
data.columns[].lifecycle_blocked_target_column_id | integer | null | Column id to move tasks into when an agent reports blocked. |
data.columns[].lifecycle_complete_target_column_id | integer | null | Column id to move tasks into when an agent completes work. |
data.columns[].lifecycle_has_issues_target_column_id | integer | null | Column id to move tasks into when an agent reports issues. |
data.columns[].lifecycle_get_prompt | string | Prompt template used by the agent when fetching work from this column. |
data.columns[].lifecycle_put_prompt | string | Prompt template used by the agent when reporting work back from this column. |
{
"success": true,
"data": {
"columns": [
{ "id": 501, "project_id": 142, "name": "To Do", "sort_order": 1, "lifecycle_enabled": 1, "lifecycle_start_target_column_id": 502, "lifecycle_blocked_target_column_id": 504, "lifecycle_complete_target_column_id": 503, "lifecycle_has_issues_target_column_id": 504, "lifecycle_get_prompt": "Read the task and start.", "lifecycle_put_prompt": "Summarize what you started." },
{ "id": 502, "project_id": 142, "name": "In Progress", "sort_order": 2, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" },
{ "id": 503, "project_id": 142, "name": "Done", "sort_order": 3, "lifecycle_enabled": 0, "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, "lifecycle_get_prompt": "", "lifecycle_put_prompt": "" }
]
}
}
POST /projects/{id}/columns
Add a new column to a project. Required: name. If sort_order is omitted, the new column is appended at MAX(sort_order) + 1.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Project id. |
Body parameters
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
yes | — | Column display name. Trimmed; cannot be empty (returns 400). |
sort_order |
integer |
no | MAX(sort_order) + 1 |
Display order. Non-numeric values return 400. |
lifecycle_enabled |
integer (0 or 1) |
no | 0 |
Toggle AI lifecycle automation for this column. Stored only when the lifecycle columns exist. |
lifecycle_start_target_column_id |
integer | null |
no | null |
Column id to move tasks into on start. Must belong to this project. |
lifecycle_blocked_target_column_id |
integer | null |
no | null |
Column id to move tasks into when blocked. Must belong to this project. |
lifecycle_complete_target_column_id |
integer | null |
no | null |
Column id to move tasks into on complete. Must belong to this project. |
lifecycle_has_issues_target_column_id |
integer | null |
no | null |
Column id to move tasks into on has issues. Must belong to this project. |
lifecycle_get_prompt |
string |
no | "" |
Prompt template used by the agent when fetching work from this column. |
lifecycle_put_prompt |
string |
no | "" |
Prompt template used by the agent when reporting work back from this column. |
Request example
curl -sS -X POST https://api.tasklife.com/v1/projects/142/columns \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "In Review",
"sort_order": 4,
"lifecycle_enabled": 1,
"lifecycle_complete_target_column_id": 503,
"lifecycle_has_issues_target_column_id": 504,
"lifecycle_get_prompt": "Fetch the task assigned to you.",
"lifecycle_put_prompt": "Report progress or completion."
}'
Response 201 Created
The created column is nested under data.column:
| Field | Type | Description |
|---|---|---|
data.column.id | integer | New column id. |
data.column.project_id | integer | Owning project id. |
data.column.name | string | Echo of the column name. |
data.column.sort_order | integer | Resolved sort order. |
data.column.<lifecycle_*> | mixed | Echo of the lifecycle fields (present only on installs with the lifecycle columns). |
{
"success": true,
"data": {
"column": {
"id": 504,
"project_id": 142,
"name": "In Review",
"sort_order": 4,
"lifecycle_enabled": 1,
"lifecycle_start_target_column_id": null,
"lifecycle_blocked_target_column_id": null,
"lifecycle_complete_target_column_id": 503,
"lifecycle_has_issues_target_column_id": 504,
"lifecycle_get_prompt": "Fetch the task assigned to you.",
"lifecycle_put_prompt": "Report progress or completion."
}
}
}
PUT /projects/{id}/columns/{columnId} (PATCH is also accepted)
Update fields on a column. Omitted fields are unchanged. Same lifecycle fields as POST. Returns the updated column nested under data.column.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Project id. |
columnId | integer | yes | Column id. |
Body parameters
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
no | — | Renamed column. Trimmed; cannot be empty (returns 400). |
sort_order |
integer |
no | — | New display order. Non-numeric values return 400. |
lifecycle_enabled |
integer (0 or 1) |
no | — | Toggle AI lifecycle automation. |
lifecycle_start_target_column_id |
integer | null |
no | — | Column id for start transitions. Must belong to this project; sending null clears the field. |
lifecycle_blocked_target_column_id |
integer | null |
no | — | Column id for blocked transitions. |
lifecycle_complete_target_column_id |
integer | null |
no | — | Column id for complete transitions. |
lifecycle_has_issues_target_column_id |
integer | null |
no | — | Column id for has issues transitions. |
lifecycle_get_prompt |
string |
no | — | Prompt template used by the agent when fetching work from this column. |
lifecycle_put_prompt |
string |
no | — | Prompt template used by the agent when reporting work back from this column. |
Request example
curl -sS -X PUT https://api.tasklife.com/v1/projects/142/columns/504 \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Code Review",
"lifecycle_enabled": 0
}'
Response 200 OK
| Field | Type | Description |
|---|---|---|
data.column.id | integer | Column id (unchanged). |
data.column.project_id | integer | Project id (unchanged). |
data.column.<field> | mixed | Reflects the merged state (changed fields + preserved fields). |
{
"success": true,
"data": {
"column": {
"id": 504,
"project_id": 142,
"name": "Code Review",
"sort_order": 4,
"lifecycle_enabled": 0,
"lifecycle_start_target_column_id": null,
"lifecycle_blocked_target_column_id": null,
"lifecycle_complete_target_column_id": 503,
"lifecycle_has_issues_target_column_id": 504,
"lifecycle_get_prompt": "Fetch the task assigned to you.",
"lifecycle_put_prompt": "Report progress or completion."
}
}
}
DELETE /projects/{id}/columns/{columnId}
Permanently delete a column. Returns 400 when the column still has tasks — move or delete the tasks first. Returns 404 if the column doesn't exist or isn't in this project.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Project id. |
columnId | integer | yes | Column id. |
Request example
curl -sS -X DELETE https://api.tasklife.com/v1/projects/142/columns/504 \
-H "Authorization: Bearer $API_KEY"
Response 200 OK
| Field | Type | Description |
|---|---|---|
data.deleted | boolean | Always true on success. |
{
"success": true,
"data": { "deleted": true }
}
{
"success": false,
"error": {
"code": "API_ERROR",
"message": "Cannot delete column with tasks. Move tasks first."
}
}