Tasklife API Reference

Per-endpoint reference for the public API. Each section shows the exact path, every parameter (with type, required/default, and meaning), 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 <your_api_key>
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: a few endpoints nest the result one level deeper than you'd expect (e.g. data.page.page.<field> for Page create/get). Each endpoint below shows its actual response shape — trust that, not this generic envelope.

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": "content must be a JSON string (got array)"
  }
}
HTTP statusWhenWhat to do
400Request body failed validation (missing required field, wrong type, etc.).Read error.message; fix the offending field.
401Missing or invalid Authorization header.Check the API key; confirm the header is present.
403API key is valid but the user lacks permission (org mismatch, admin-gated action, etc.).Confirm the key's org matches the resource's org; check user role.
404Resource doesn't exist, is soft-deleted, or isn't visible to this org.Verify the ID and that the resource wasn't deleted.
409Conflict (duplicate slug, replay attempt, etc.).Read error.message for the specific conflict.
422Request is well-formed but semantically invalid (e.g. nested reply to a reply).Read error.message; restructure the request.
500Unexpected server error.Retry with backoff; report if persistent.

Pages (CMS)

Create, read, update, and delete CMS pages. Pages have a hierarchical parent_id tree, store body content as Markdown, and support soft-delete via deleted_at.

Endpoints at a glance

ActionMethod + PathSummary
Create a pagePOST /pagesCreate a new page. Returns the new id and slug.
Get a pageGET /pages/{id}Fetch a single page including its full body, metadata, children, and media.
List pagesGET /pagesList pages in the org, optionally filtered by parent, status, or visibility.
Search pagesGET /search?q=…Full-text search across title + body.
Update a pagePUT /pages/{id}Replace fields on a page. Body can replace or append (see append flag).
Append contentPOST /pages/{id}/appendAppend a string to the page body using a configurable separator.
Get page treeGET /pages/treeFull hierarchical tree of pages (parent → children → grandchildren).
Get version historyGET /pages/{id}/versionsList previous versions of a page (snapshotted on update).
Page diff (what's new)GET /pages/{id}/diffWord-level diff of the visible text between two versions (MCP: get_page_diff).
Transfer to another orgPOST /pages/{id}/transferMove a page subtree (versions, comments, media) into another org you belong to; dry_run preview (MCP: transfer_page).
Delete a pageDELETE /pages/{id}Soft-delete a page (sets deleted_at); children are cascaded.

POST /pages

Create a new page.

Body parameters

FieldTypeRequiredDefaultDescription
title string yes — 1–255 chars. Becomes the page record name and contributes to the slug.
content string no "" Markdown body. Must be a JSON string (see warning above). No practical length cap.
parent_id integer no null (root) Parent page id, or 0 / "0" / null / empty for root.

Request example

curl -sS -X POST https://api.tasklife.com/v1/pages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title":     "My Page",
    "content":   "# Heading\nSome **markdown** body.",
    "parent_id": 0
  }'

Response 201 Created

The new page is nested under data.page.page:

FieldTypeDescription
data.page.page.idintegerNew page id. Use this for follow-up updates, deletes, appends, etc.
data.page.page.titlestringEcho of the title you sent.
data.page.page.slugstringURL-safe slug derived from the title.
{
  "success": true,
  "data": {
    "page": {
      "page": {
        "id":    776,
        "title": "My Page",
        "slug":  "my-page"
      }
    }
  }
}

GET /pages/{id}

Fetch a single page by id.

Path parameters

FieldTypeRequiredDescription
idintegeryesPage id.

Request example

curl -sS https://api.tasklife.com/v1/pages/776 \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

The page is nested under data.page.page:

FieldTypeDescription
idintegerPage id.
parent_idinteger | nullParent page id, or null for root pages.
titlestringPage title.
contentstringMarkdown body (may be empty).
slugstringURL-safe slug.
statusstring"active" or "inactive".
visibilitystring"public" or "private".
versionintegerCurrent version number (incremented on each update).
author_namestringDisplay name of the page's creator.
created_atstring (ISO 8601 datetime)Page creation timestamp.
updated_atstring (ISO 8601 datetime)Last update timestamp.
childrenarrayDirect child pages (nested). Each child follows the same shape recursively.
mediaarrayMedia attachments referenced in the page body.
attachmentsarrayFile attachments on the page.
{
  "success": true,
  "data": {
    "page": {
      "page": {
        "id":          776,
        "parent_id":   589,
        "title":       "My Page",
        "content":     "# Heading\nSome **markdown**.",
        "slug":        "my-page",
        "status":      "active",
        "visibility":  "private",
        "version":     1,
        "author_name": "Sal Iozzia",
        "created_at":  "2026-06-15T14:32:11Z",
        "updated_at":  "2026-06-15T14:32:11Z",
        "children":    [],
        "media":       [],
        "attachments": []
      }
    }
  }
}

GET /pages

List pages visible to the API key's org. Soft-deleted pages are excluded.

Query parameters

FieldTypeRequiredDescription
parent_idintegernoFilter to direct children of this page id.
statusstringno"active" or "inactive".
visibilitystringno"public" or "private".

Request example

curl -sS "https://api.tasklife.com/v1/pages?parent_id=589" \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

{
  "success": true,
  "data": {
    "pages": [
      {
        "id":         776,
        "title":      "My Page",
        "slug":       "my-page",
        "parent_id":  589,
        "status":     "active",
        "visibility": "private",
        "created_at": "2026-06-15T14:32:11Z",
        "updated_at": "2026-06-15T14:32:11Z"
      }
    ],
    "pagination": { "limit": 50, "offset": 0, "total": 1 }
  }
}

PUT /pages/{id}

Update fields on a page. Any subset of fields may be sent; omitted fields are unchanged. The previous version is snapshotted on every successful update.

Path parameters

FieldTypeRequiredDescription
idintegeryesPage id.

Body parameters

FieldTypeRequiredDefaultDescription
titlestringno—1–255 chars.
contentstringno—Markdown body. Must be a JSON string (see warning above).
statusstringno—"active" or "inactive".
visibilitystringno—"public" or "private".
parent_idintegerno—Move the page under a new parent.
append boolean no false When true, content is concatenated to the existing body using separator between them. When false / omitted, content replaces the body entirely.
separator string no "\n\n" Inserted between existing and new content when append=true. Ignored when append=false.

Request examples

Replace body:

curl -sS -X PUT https://api.tasklife.com/v1/pages/776 \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title":   "My Page (renamed)",
    "content": "# New body"
  }'

Append to body:

curl -sS -X PUT https://api.tasklife.com/v1/pages/776 \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content":   "\n## Appended section",
    "append":    true,
    "separator": ""
  }'

Response 200 OK

Update is the odd endpoint out — it does not return the updated page. The response is just { success: true } nested under data.page:

FieldTypeDescription
data.page.successbooleanAlways true on success. To read the new state, call GET /pages/{id}.
{
  "success": true,
  "data": {
    "page": { "success": true }
  }
}

POST /pages/{id}/append

Append a string to the page body. Equivalent to PUT /pages/{id} with append: true, but with its own dedicated route for callers that only want to append.

Path parameters

FieldTypeRequiredDescription
idintegeryesPage id.

Body parameters

FieldTypeRequiredDefaultDescription
content string yes — String to append. Must be a JSON string (see warning above).
separator string no "\n\n" Inserted between the existing body and the new content.

Request example

curl -sS -X POST https://api.tasklife.com/v1/pages/776/append \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content":   "## Appended section",
    "separator": "\n\n"
  }'

Response 200 OK

{
  "success": true,
  "data": { "page": { "id": 776 }, "success": true }
}

GET /pages/tree

Return the full page hierarchy as a nested tree. Each node carries the same shape as a single page response, with children recursively populated.

Request example

curl -sS https://api.tasklife.com/v1/pages/tree \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

{
  "success": true,
  "data": {
    "tree": [
      {
        "id": 589, "title": "Engineering", "slug": "engineering", "parent_id": null,
        "children": [
          { "id": 776, "title": "My Page", "slug": "my-page", "parent_id": 589, "children": [] }
        ]
      }
    ]
  }
}

GET /pages/{id}/versions

List the version history of a page. Every successful PUT writes a snapshot before applying the change.

Path parameters

FieldTypeRequiredDescription
idintegeryesPage id.

Request example

curl -sS https://api.tasklife.com/v1/pages/776/versions \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

{
  "success": true,
  "data": {
    "versions": [
      { "version": 2, "title": "My Page",            "content": "# New body",       "created_by": 1, "created_at": "2026-06-15T15:00:00Z" },
      { "version": 1, "title": "My Page",            "content": "# Original body",  "created_by": 1, "created_at": "2026-06-15T14:32:11Z" }
    ]
  }
}

GET /pages/{id}/diff

"What's new" diff, the API/MCP equivalent of the Pages View changes button. Compares the visible text (markdown/HTML stripped) of two versions. Version N is the page state before the write recorded at that version's at/by; the live page is current (latest snapshot + 1). MCP tool: get_page_diff (page_id, from_version, to_version, since).

Query parameters

FieldTypeRequiredDescription
fromintegernoBaseline version number. Default: the previous version (newest snapshot below to).
tointeger | "current"noDefault current (the live page).
sinceISO-8601noAlternative to from: baseline = the page as it was at that time. Not combinable with from.

Request example

curl -sS "https://api.tasklife.com/v1/pages/832/diff?from=3" \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK

{
  "success": true,
  "data": { "diff": {
    "page_id": 832, "title": "My Page", "current_version": 4,
    "from": { "version": 3, "current": false, "title": "My Page", "at": "2026-10-01T15:00:00Z", "by": 1, "by_name": "Sal" },
    "to":   { "version": 4, "current": true,  "title": "My Page", "at": "2026-10-01T16:00:00Z", "by": 1, "by_name": "Sal" },
    "summary": { "additions": 6, "removals": 2, "title_changed": false, "words_before": 120, "words_after": 124 },
    "mode": "word", "too_large": false, "truncated": false,
    "changes": [
      { "type": "equal", "text": "... last words of unchanged context" },
      { "type": "del",   "text": "old words" },
      { "type": "add",   "text": "new words" }
    ],
    "unified": "@@ -10,4 +10,4 @@\n context\n-old words\n+new words\n"
  } }
}

Flags: too_large (content over the size cap; only title + word counts returned), truncated (segments capped), no_baseline (no earlier version yet). mode is word, block (large-content fallback) or none.

POST /pages/{id}/transfer

Moves the page and its whole subtree (every descendant via parent_id, including trashed ones) from the org your key is bound to (the source) into target_org_id. Page ids are kept, so version history, comments and media travel with the pages. Media used only by the moved pages is moved; media also used elsewhere in the source org is copied (same file, new id) and the moved content is repointed. The root becomes top-level in the target org, or a child of target_parent_id. Colliding slugs get a -2, -3… suffix; team_id and the Pages-home flag are cleared. Source-org task–page links and section-index pointers to the moved pages are removed. An audit entry is written in both orgs. MCP tool: transfer_page. Tasks are not transferred (yet).

Authorization: the platform user behind the key must have an active membership in both the source and the target org. Otherwise the call fails with a generic 403 FORBIDDEN. Read-only keys may only use dry_run.

Body

FieldTypeRequiredDescription
target_org_idintegeryesDestination org (must differ from the key's org).
target_parent_idinteger | nullnoActive page in the target org to nest the root under. Omit/null = top-level. Invalid ids return 400.
dry_runbooleannotrue = preview only, nothing changes. Recommended first.

Request example (dry run)

curl -sS -X POST "https://api.tasklife.com/v1/pages/837/transfer" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"target_org_id": 7, "dry_run": true}'

Response 200 OK

{
  "success": true,
  "data": {
    "dry_run": true, "transferred": false,
    "source_org_id": 1, "target_org_id": 7, "target_parent_id": null,
    "root": { "id": 837, "title": "Ser.vi", "parent_id": null },
    "pages": [
      { "id": 837, "title": "Ser.vi", "depth": 0, "parent_id": null, "new_parent_id": null,
        "child_count": 2, "version_count": 12, "comment_count": 1, "media_count": 3,
        "trashed": false, "slug": "ser-vi", "new_slug": "ser-vi" }
    ],
    "totals": { "pages": 3, "descendants": 2, "trashed_pages": 0, "versions": 20, "comments": 1,
                "media": 3, "media_moved": 3, "media_copied": 0, "task_links_removed": 0, "section_index_removed": 0 },
    "media": [ { "id": 41, "file_name": "logo.png", "action": "move" } ],
    "slug_renames": [],
    "warnings": [],
    "tasks": "not_supported: task transfer is a follow-up; only pages are moved"
  }
}

Without dry_run the same payload is returned with transferred: true and a result object (pages_moved, comments_moved, media_moved, media_copied, audit_ids, …). Errors: 400 bad/missing target_org_id or invalid target_parent_id; 403 not a member of both orgs; 404 page not in the key's org; 409 tree changed mid-transfer (nothing moved).

DELETE /pages/{id}

Soft-delete a page. Sets deleted_at and removes the page from list/tree/search/get. Children are cascaded (also soft-deleted).

Path parameters

FieldTypeRequiredDescription
idintegeryesPage id.

Request example

curl -sS -X DELETE https://api.tasklife.com/v1/pages/776 \
  -H "Authorization: Bearer $API_KEY"

Response 200 OK / 204 No Content

{ "success": true }

Other sections

These will be added next, following the same per-endpoint layout used for Pages:

  • Projects & columns
  • Tasks
  • Comments
  • Teams
  • Agents & credentials