Releases

Per-endpoint reference for the Releases resource. A release is a record bundling tasks within a single project, with a version, RC label, status / QA-status pair, and customizable status settings stored as JSON on the row. Tasks can be attached to and detached from a release via dedicated sub-resources. 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 require Authorization: Bearer $API_KEY with an API key bound to your org. The key's user becomes the release's created_by on create and the added_by on task attach.

Envelope

Successful responses are { success: true, data: { ... } }. Errors return { success: false, error: { code, message } } with appropriate HTTP status. Per-endpoint response shapes are documented below.

Single-product releases. Each release belongs to exactly one product. If you need to ship features across multiple products, create one release per product and link them via shared RC label / version in your workflow.

Endpoints at a glance

ActionMethod & PathSummary
List releasesGET /releasesList releases, optionally filtered by project, product, or status.
Create releasePOST /releasesCreate a new release for a project.
Get releaseGET /releases/{id}Get a release with its current task list.
Update releasePUT /releases/{id}Update one or more fields on a release.
Delete releaseDELETE /releases/{id}Delete a release and detach all its tasks.
Attach taskPOST /releases/{id}/tasksAdd a task to a release.
Detach taskDELETE /releases/{id}/tasks/{taskId}Remove a task from a release.
Search tasksGET /releases/{id}/tasks/searchSearch tasks by title within a release's project.

GET /releases

List releases for the org. By default, shipped / production / done / released releases are hidden — pass show_done=true to include them.

Query parameters

FieldTypeRequiredDescription
project_idintegernoFilter to releases of a single project.
product_idintegernoFilter to releases of a single product.
statusstringnoFilter to a single status (e.g. in_progress).
show_donebooleannoWhen true, include shipped/production/done/released releases. Defaults to false.

Request

curl -sS "$BASE_URL/releases?project_id=42" \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

FieldTypeDescription
data.releases[]arrayMatching release records.
data.releases[].idintegerRelease id.
data.releases[].org_idintegerOwning org id.
data.releases[].project_idintegerProject id.
data.releases[].product_idinteger|nullProduct id (nullable).
data.releases[].namestringRelease name.
data.releases[].versionstring|nullVersion label (e.g. "2.4").
data.releases[].rc_numberstring|nullRC iteration label.
data.releases[].statusstringRelease status (free text; default seeded values are not_started/in_progress/passed/failed/shipped).
data.releases[].qa_statusstringQA status (not_started/in_progress/passed/failed by default).
data.releases[].commentsstring|nullFree-form notes.
data.releases[].project_namestring|nullProject display name (joined from projects).
data.releases[].product_namestring|nullProduct display name (joined from project_products).
data.releases[].task_countintegerNumber of tasks attached to the release.
data.releases[].created_atstringMySQL datetime the release was created.
data.releases[].updated_atstringMySQL datetime the release was last updated.
{
  "success": true,
  "data": {
    "releases": [
      {
        "id": 17,
        "org_id": 1,
        "project_id": 42,
        "product_id": 7,
        "name": "v2.4 Feature Push",
        "version": "2.4",
        "rc_number": "1",
        "status": "in_progress",
        "qa_status": "not_started",
        "comments": "RC1 — covers cards through Friday",
        "project_name": "Web App",
        "product_name": "Tasklife",
        "task_count": 12,
        "created_at": "2026-07-22 14:08:11",
        "updated_at": "2026-07-26 09:31:02"
      }
    ]
  }
}

POST /releases

Create a new release. The authenticated user (from the API key) is recorded as created_by.

Body parameters

FieldTypeRequiredDefaultDescription
project_idintegernomost-recent project in orgProject id. A release is product-scoped; project_id is only used to populate the create-task modal's column-options on the edit page. If omitted, the API falls back to the caller's most-recent project in the org. May be null on a row with no project.
namestringyes—Human-readable release name.
product_idintegernonullProduct id. Must belong to the org if set.
versionstringnonullVersion label (e.g. "2.4").
rc_numberstringnonullRC iteration label. Free-form; not auto-incremented.
statusstringnonot_startedInitial release status.
qa_statusstringnonot_startedInitial QA status.
commentsstringnonullFree-form notes.
status_settingsobjectnoseeded defaultsOverride the customizable status settings stored as JSON on the row. Shape: { "statuses": [...], "qa_statuses": [...] }.

Request

curl -sS -X POST "$BASE_URL/releases" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": 42,
    "product_id": 7,
    "name": "v2.4 Feature Push",
    "version": "2.4",
    "rc_number": "1",
    "comments": "RC1 — covers cards through Friday"
  }'

Response 201 Created

{
  "success": true,
  "data": {
    "id": 17
  }
}

Errors

StatusWhenWhat to do
400 MISSING_FIELDSname missing / empty.Include name in the body.
404 PROJECT_NOT_FOUNDproject_id supplied but doesn't exist or isn't in this org.Drop project_id to use the most-recent fallback, or use GET /projects to find a valid project id.
404 PRODUCT_NOT_FOUNDproduct_id set but doesn't belong to this org.Drop product_id or pick a valid one.

GET /releases/{id}

Get a single release with its full task list.

Path parameters

FieldTypeRequiredDescription
idintegeryesRelease id.

Request

curl -sS "$BASE_URL/releases/17" \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

FieldTypeDescription
data.releaseobjectThe release record (same fields as the list endpoint).
data.release.tasks[]arrayTasks attached to this release, newest-first.
data.release.tasks[].idintegerTask id.
data.release.tasks[].titlestringTask title.
data.release.tasks[].task_statusstringTask's own column-level status (independent of release status).
data.release.tasks[].project_idintegerProject the task lives in.
data.release.tasks[].project_column_idinteger|nullColumn the task sits in.
data.release.tasks[].column_namestring|nullColumn display name (e.g. "In Progress").
{
  "success": true,
  "data": {
    "release": {
      "id": 17,
      "org_id": 1,
      "project_id": 42,
      "product_id": 7,
      "name": "v2.4 Feature Push",
      "version": "2.4",
      "rc_number": "1",
      "status": "in_progress",
      "qa_status": "in_progress",
      "comments": "RC1 — covers cards through Friday",
      "status_settings": "{\"statuses\":[\"not_started\",\"in_progress\",\"passed\",\"failed\",\"shipped\"],\"qa_statuses\":[\"not_started\",\"in_progress\",\"passed\",\"failed\"]}",
      "created_by": 8,
      "created_at": "2026-07-22 14:08:11",
      "updated_at": "2026-07-26 09:31:02",
      "project_name": "Web App",
      "product_name": "Tasklife",
      "tasks": [
        {
          "id": 1284,
          "title": "Add OAuth device grant endpoint",
          "task_status": "Done",
          "project_id": 42,
          "project_column_id": 91,
          "column_name": "Done"
        },
        {
          "id": 1301,
          "title": "Auto-bump APP_VERSION on CSS/JS edits",
          "task_status": "In Progress",
          "project_id": 42,
          "project_column_id": 90,
          "column_name": "In Progress"
        }
      ]
    }
  }
}

Errors

StatusWhen
404 NOT_FOUNDRelease id doesn't exist or belongs to a different org.

PUT /releases/{id}

Update one or more fields on a release. Only the fields you include in the body are changed.

Path parameters

FieldTypeRequiredDescription
idintegeryesRelease id.

Body parameters

All optional. At least one field is required.

FieldTypeDescription
namestringRelease name. Cannot be empty.
product_idintegerProduct id. Pass 0 to clear.
versionstringVersion label.
rc_numberstringRC iteration label.
statusstringRelease status.
qa_statusstringQA status.
commentsstringFree-form notes.
status_settingsobjectReplace the customizable status settings JSON. Shape: { statuses: [...], qa_statuses: [...] }.

Request

curl -sS -X PUT "$BASE_URL/releases/17" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rc_number": "2",
    "status": "in_progress",
    "comments": "RC2 — addressing feedback from RC1"
  }'

Response 200 OK

{
  "success": true
}

Errors

StatusWhen
400 NO_FIELDSBody had no updatable fields.
400 EMPTY_NAMEname sent as empty string.
404 NOT_FOUNDRelease doesn't exist in this org.
404 PRODUCT_NOT_FOUNDproduct_id set to a non-org product.

DELETE /releases/{id}

Delete a release. Cascades the release_tasks join rows and clears the release_id back-reference on every attached task so tasks aren't orphaned to a deleted id.

Path parameters

FieldTypeRequiredDescription
idintegeryesRelease id.

Request

curl -sS -X DELETE "$BASE_URL/releases/17" \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

{ "success": true }

Errors

StatusWhen
404 NOT_FOUNDRelease doesn't exist in this org.

POST /releases/{id}/tasks

Attach a task to a release. The task must belong to the same project as the release. The task's own release_id field is set as a back-reference, so the task shows up under the release in the task UI too. Idempotent — re-adding an already-attached task is a no-op.

Path parameters

FieldTypeRequiredDescription
idintegeryesRelease id.

Body parameters

FieldTypeRequiredDescription
task_idintegeryesTask id. Must belong to the release's project.

Request

curl -sS -X POST "$BASE_URL/releases/17/tasks" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "task_id": 1301 }'

Response 200 OK

{ "success": true }

Errors

StatusWhen
400 MISSING_FIELDStask_id missing or non-numeric.
400 TASK_NOT_IN_PROJECTTask is in a different project than the release.
404 NOT_FOUNDRelease id doesn't exist in this org.
404 TASK_NOT_FOUNDTask id doesn't exist or isn't in this org.

DELETE /releases/{id}/tasks/{taskId}

Detach a task from a release. Removes the release_tasks join row and clears the task's own release_id back-reference. Idempotent — no error if the task wasn't attached.

Path parameters

FieldTypeRequiredDescription
idintegeryesRelease id.
taskIdintegeryesTask id to detach.

Request

curl -sS -X DELETE "$BASE_URL/releases/17/tasks/1301" \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

{ "success": true }

Errors

StatusWhen
404 NOT_FOUNDRelease doesn't exist in this org. (Detaching an unattached task from a real release is a 200 no-op.)

GET /releases/{id}/tasks/search

Search tasks by title within a release's project. Used by the in-app autocomplete picker; returns up to 20 matches, ordered by title ascending. Useful when you have a release id and want to find task ids to attach.

Path parameters

FieldTypeRequiredDescription
idintegeryesRelease id. The release's project becomes the search scope.

Query parameters

FieldTypeRequiredDescription
qstringyesSearch string. Must be at least 2 characters. Substring match against task title.

Request

curl -sS "$BASE_URL/releases/17/tasks/search?q=oauth" \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

FieldTypeDescription
data.tasks[]arrayUp to 20 matching tasks.
data.tasks[].idintegerTask id (use as task_id on attach).
data.tasks[].titlestringTask title.
data.tasks[].column_namestring|nullColumn display name.
{
  "success": true,
  "data": {
    "tasks": [
      {
        "id": 1284,
        "title": "Add OAuth device grant endpoint",
        "column_name": "Done"
      },
      {
        "id": 1411,
        "title": "OAuth token refresh bug on slow clocks",
        "column_name": "In Progress"
      }
    ]
  }
}

Errors

StatusWhen
400 QUERY_TOO_SHORTq is empty or shorter than 2 characters.
404 NOT_FOUNDRelease id doesn't exist in this org.