API reference

Public REST API for integrations and scripts. Base URL: https://taskid.app/v1/api

Plugging in an AI assistant (Claude, ChatGPT)? MCP connector → is easier and doesn't require crafting HTTP requests by hand.

Overview

The API accepts and returns JSON. Request bodies use application/json; charset=utf-8.

Dates are strings in ISO-8601 UTC, without milliseconds: 2026-04-18T12:34:56Z.

Identifiers are UUID v4 strings in canonical form.

The API works in tandem with client-side background sync. A task created through the API appears in the iOS/web interface after the next sync cycle (usually within a couple of seconds).

Authentication

Every request requires the header:

Authorization: Bearer tkd_xxxxxxxxxxxxxxxxxxxx

Tokens are created on the API tokens page.

Scopes
  • read - only GET requests are allowed.
  • write - both GET and POST for tasks, notes, lists and groups.
  • write_notifications - an isolated scope that only allows sending notifications via POST /v1/api/notifications. Gives no access to tasks, notes or lists - convenient for monitoring and alert integrations.

When creating a token you can restrict it to specific lists - then requests to other lists return 403.

Errors

Errors return standard HTTP codes and a JSON body like:

{ "message": "list is not accessible by this token" }
Code When
400Malformed request body (missing title, invalid date).
401No Authorization header, or token not found / revoked / expired.
403Missing scope (write for POST) or list not accessible to the token.
404List does not exist or has been deleted.
429Rate limit exceeded (applies to notification sending). The Retry-After and X-RateLimit-* headers indicate how many seconds to wait before retrying.
500Server error - retry later.

Groups

List groups

GET /v1/api/groups

A group is a container for regular lists. It contains no tasks or records itself, but lets you bundle several lists under one umbrella. Tokens with limited access only see groups that contain at least one of their allowed lists.

Required scope
read
Query parameters
None. The endpoint returns all visible groups as a single array - no pagination.
Example
curl -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/groups
200 OK response
{ "groups": Group[] }

Create a group

POST /v1/api/groups
Required scope
write (token must have "All lists" access)
Request body
FieldTypeDescription
namestring *Group name. Required, non-empty.
colorstring | nullHEX color or theme token name. Optional.
iconstring | nullIcon name (SF Symbol). Optional.
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Work", "icon": "briefcase" }' \
  https://taskid.app/v1/api/groups
201 Created response
{ "group": Group }

Get a group

GET /v1/api/groups/:id
Required scope
read
200 OK response
{ "group": Group }

Edit a group

PATCH /v1/api/groups/:id
Required scope
write (all_lists)
Request body - all fields optional, only those present are updated
FieldTypeDescription
namestringNew name. Empty string is not allowed.
colorstring | nullHEX or theme token name. null clears it.
iconstring | nullIcon name (SF Symbol). null clears it.
archivedbooleanArchive / unarchive.
Example
curl -X PATCH \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Work projects" }' \
  https://taskid.app/v1/api/groups/GROUP_ID
200 OK response
{ "group": Group }

Delete a group

DELETE /v1/api/groups/:id

Soft delete. Lists that belonged to the group remain - after sync they appear at the root (parent_id still points to the deleted group). Requires scope=write (all_lists).

204 No Content response
Empty body.

Lists

List lists

GET /v1/api/lists

Returns all lists the token has access to. Archived lists are included too - see the archived flag.

Required scope
read
Query parameters
None. There's no pagination - the entire visible list set is returned as a single array.
Request example
curl -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/lists
200 OK response
{
  "lists": [
    {
      "id":        "6f8a6f4e-9b0e-4a82-9f6b-2a3f1e4a5d1a",
      "parent_id": null,
      "name":      "Work",
      "color":     "#0a84ff",
      "icon":      "briefcase",
      "archived":  false,
      "is_owner":  true,
      "is_shared": false
    }
  ]
}
Shared vs personal list markers

Lists can be shared (the "Share" button on the site). The response contains both your own and shared ones; two boolean fields help tell them apart:

  • is_owner - true: you own the list; false: you're a member of someone else's. Only the owner can edit the list itself (name, color, icon, archive, delete, attach to a group) - a member attempt returns 403. The contents (tasks and notes) can be edited by both.
  • is_shared - true: the list has members (shared), regardless of your role; false: strictly personal.

In shared lists, the completed_user_id / deleted_user_id fields on tasks show who actually closed/deleted the record (not its owner).

Create a list

POST /v1/api/lists

Creates a new list for the token owner.

Required scope
write
This method is only available to tokens with "All lists" access. Tokens bound to specific lists cannot create new ones - that would violate their access boundary.
Request body
FieldTypeDescription
namestring *List name. Required.
colorstring | nullHEX color or theme token name.
iconstring | nullIcon name (SF Symbol).
parent_idUUID | nullUUID of the group to place the list into immediately. Groups are created via /v1/api/groups.
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Shopping", "color": "#0a84ff" }' \
  https://taskid.app/v1/api/lists
201 Created response
{
  "list": {
    "id":        "6f8a6f4e-...",
    "parent_id": null,
    "name":      "Shopping",
    "color":     "#0a84ff",
    "icon":      null,
    "archived":  false
  }
}

Get a list

GET /v1/api/lists/:id

Returns a single list by its identifier.

Required scope
read
Example
curl -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/lists/LIST_ID
200 OK response
{ "list": List }

Edit a list

PATCH /v1/api/lists/:id

Partial update: include only the fields you want to change.

Required scope
write
Request body
FieldTypeDescription
namestringNew name. Empty string is not allowed.
colorstring | nullHEX or theme token name. null clears it.
iconstring | nullIcon name (SF Symbol). null clears it.
archivedbooleanArchive / unarchive.
Attaching a list to a group is done by a separate method POST /v1/api/lists/:id/attach.
Example
curl -X PATCH \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Weekly shopping" }' \
  https://taskid.app/v1/api/lists/LIST_ID
200 OK response
{ "list": List }

Attach to a group

POST /v1/api/lists/:id/attach

Attaches an existing list to a group, or detaches it. In the URL - the list id, in the body - only parent_id. The list itself is not edited - only its group membership changes.

Required scope
write
Request body
{
  "parent_id": UUID | null   // null - move to root
}
Example: attach
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "parent_id": "GROUP_ID" }' \
  https://taskid.app/v1/api/lists/LIST_ID/attach
Example: detach
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "parent_id": null }' \
  https://taskid.app/v1/api/lists/LIST_ID/attach
200 OK response
{ "list": List }

Delete a list

DELETE /v1/api/lists/:id

Soft delete of a list (deleted_at = now). Tasks in the list also stop showing on clients after sync.

Required scope
write
Example
curl -X DELETE \
  -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/lists/LIST_ID
204 No Content response
Empty body.

Tasks

List tasks

GET /v1/api/lists/:id/tasks

Returns tasks from a specific list.

Required scope
read
Query parameters
ParameterDefaultValues
completed00 - hide completed, 1 - only completed, all - everything.
limit1001..500
offset0≥ 0
Example
curl -H "Authorization: Bearer tkd_xxx" \
  "https://taskid.app/v1/api/lists/6f8a6f4e-.../tasks?completed=0&limit=50"
200 OK response
{
  "list":   { "id": "...", "name": "Work" },
  "total":  42,
  "limit":  50,
  "offset": 0,
  "tasks":  [ Task, Task, ... ]
}

total is the number of tasks matching the filter before applying limit/offset.

Files attached to a task

Every task (as well as every note and notification) has a files array - attachments. If nothing is attached the array is empty.

"files": [
  {
    "id":     "9f1c...",
    "name":   "contract.pdf",
    "size":   184320,                                 // bytes
    "url":    "https://taskid.app/files/aa/bb/cc/9f1c....pdf",
    "source": "upload"                                // 'upload' | 'link'
  },
  {
    "id":     "1a2b...",
    "name":   "Status page",
    "size":   0,                                      // 0 if HEAD didn't return Content-Length
    "url":    "https://status.example.com/incidents/42",
    "source": "link"
  }
]
  • source: "upload" - a file actually uploaded to our storage. url points to taskid.app and downloads via a plain GET without headers (capability token is baked into the path). Counts against the user's file quota.
  • source: "link" - an external HTTPS URL (our server doesn't download or store anything). url points to the third-party resource as-is. Doesn't count against the quota; size may be 0 if the resource server didn't return Content-Length in the HEAD response on attach.
  • Deleted/detached files and links are not returned in the array.
  • Attach / detach - see "Attach files and links to a task" and "Detach a file/link from a task". The semantics for notes and notifications are identical.

Create a task

POST /v1/api/lists/:id/tasks

Creates a task in the given list.

Required scope
write
Request body
FieldTypeDescription
titlestring *Task title. Required.
notestring | nullNote (multi-line text).
due_atstring | nullISO-8601 UTC, the deadline timestamp.
due_has_timeboolean"Date has time" flag. If false, only the date is shown in the UI.
flaggedboolean"Important" flag. Defaults to false.
priorityinteger0 (none), 1 (low), 2 (med), 3 (high). Defaults to 0.
parent_idUUID | nullUUID of a parent task - the new task is then created as its subtask. The parent must be in the same list and must not itself be a subtask (single-level hierarchy). To attach an already existing task to another, see POST /v1/api/tasks/:id/attach.
Example: a regular task
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Call the doctor",
    "due_at": "2026-04-20T08:00:00Z",
    "due_has_time": true,
    "flagged": true,
    "priority": 2
  }' \
  https://taskid.app/v1/api/lists/LIST_ID/tasks
Example: a subtask straight away
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Buy milk", "parent_id": "PARENT_TASK_UUID" }' \
  https://taskid.app/v1/api/lists/LIST_ID/tasks
201 Created response
{
  "task": {
    "id": "3c6b...-...-...",
    "list_id": "6f8a6f4e-...",
    "title": "Call the doctor",
    "note": null,
    "due_at": "2026-04-20T08:00:00Z",
    "due_has_time": true,
    "completed_at": null,
    "flagged": true,
    "priority": 2,
    "sort_order": 12000,
    "files": [],
    "version": "2026-04-18T12:34:56Z",
    "updated_at": "2026-04-18T12:34:56Z"
  }
}
Want to attach files right away?
The body of POST /v1/api/lists/:id/tasks doesn't attach files. Do it in two steps: 1) create the task, 2) upload files via POST /v1/api/files and attach them with POST /v1/api/tasks/:id/files.

Get a task

GET /v1/api/tasks/:id

Returns a single task by its identifier.

Required scope
read
Example
curl -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/tasks/TASK_ID
200 OK response
{ "task": Task }

404 if the task is not found or belongs to a list inaccessible to the token.

Edit a task

PATCH /v1/api/tasks/:id

Partial update: only the fields you want to change need to be present. The rest of the task fields stay as they are.

Required scope
write
Field semantics: field present - update; value null - clear (for nullable fields); field missing - leave alone.
When the same task is edited from multiple devices at once, LWW applies - the later client_updated_at wins.
Request body
FieldTypeDescription
titlestringTitle. Empty string is not allowed.
notestring | nullNote. null clears it.
due_atstring | nullISO-8601 UTC or null to clear the date.
due_has_timebooleanWhether to take the time into account.
completedbooleantrue → completed_at = now, false → clear.
flaggedboolean"Important" flag.
priorityinteger0, 1, 2 or 3.
in_my_daybooleantrue adds the task to "My Day" for today, false removes it.
Example
curl -X PATCH \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "title": "New name", "flagged": true }' \
  https://taskid.app/v1/api/tasks/TASK_ID
200 OK response
{ "task": Task }

The full task field structure is in the "Data types" section.

PATCH does not change files
This endpoint only changes text fields, the date, the flag and the priority. To attach a file to a task - POST /v1/api/tasks/:id/files, to detach - DELETE /v1/api/tasks/:id/files/:fileId.

Delete a task

DELETE /v1/api/tasks/:id

Soft delete: the task stays in the database with deleted_at set - this is required for correct sync with clients. For the user, the task disappears from every view.

Required scope
write
Example
curl -X DELETE \
  -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/tasks/TASK_ID
204 No Content response
Empty body.

Mark complete

POST /v1/api/tasks/:id/complete

A convenient alias for the common case: sets completed_at = now. No request body needed. Idempotent: an already-completed task stays as it is.

Required scope
write
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/tasks/TASK_ID/complete
200 OK response
{ "task": Task }

Reopen

POST /v1/api/tasks/:id/reopen

Clears the completion flag: completed_at = null. Idempotent.

Required scope
write
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/tasks/TASK_ID/reopen
200 OK response
{ "task": Task }

Attach to a parent task

POST /v1/api/tasks/:id/attach

Makes the existing task :id a subtask of the task identified by parent_id, or - if parent_id: null - detaches it from its parent (back to a top-level task). The task stays in the same list.

Required scope
write
Request body
{
  "parent_id": UUID | null   // null - detach
}
Constraints
  • the parent must be in the same list as the task;
  • the parent itself must not be a subtask (single-level hierarchy);
  • the task :id must not have subtasks of its own;
  • a task cannot be made a subtask of itself.
Example: attach
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "parent_id": "PARENT_TASK_ID" }' \
  https://taskid.app/v1/api/tasks/CHILD_TASK_ID/attach
Example: detach
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "parent_id": null }' \
  https://taskid.app/v1/api/tasks/CHILD_TASK_ID/attach
200 OK response
{ "task": Task }

Attach files and links to a task

POST /v1/api/tasks/:id/files

Attaches (a) previously uploaded files by id and/or (b) external HTTPS links to a task. At least one of the two arrays must be non-empty; you can send both at once.

Files: upload them to the server first - see "Upload a file" - and save the resulting id. The server pulls the name/size/download URL from the database. Links: just an external URL (image, dashboard, log) - our server doesn't download or cache anything, the size/MIME is best-effort via a HEAD request.

Required scope
write
Request body
FieldTypeDescription
file_idsUUID[]Array of previously uploaded file ids. Each id is the result of POST /v1/api/files. The files must belong to the calling user. Optional if links is provided.
links{url, name?}[]Array of external links. Each has a required url field (http/https) and an optional name. If the name isn't provided the server takes it from the Content-Disposition HEAD response or from the last URL segment. Optional if file_ids is provided.
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "file_ids": ["9f1c-...", "5b2e-..."],
    "links": [
      { "url": "https://grafana.example.com/d/api", "name": "Dashboard" },
      { "url": "https://status.example.com/incidents/42" }
    ]
  }' \
  https://taskid.app/v1/api/tasks/TASK_ID/files
What the server returns

The full updated task with the attached files and links already in the files array (see "Data types" - one unified format with a source marker):

{
  "task": {
    "id":     "TASK_ID",
    "title":  "Contractor agreement",
    "note":   null,
    "files": [
      {
        "id":     "9f1c-...",
        "name":   "contract.pdf",
        "size":   184320,
        "url":    "https://taskid.app/files/aa/bb/cc/9f1c....pdf",
        "source": "upload"
      },
      {
        "id":     "1a2b-...",
        "name":   "Dashboard",
        "size":   0,
        "url":    "https://grafana.example.com/d/api",
        "source": "link"
      }
    ],
    ...                            // remaining task fields - see "Data types"
  }
}

Idempotent over file_ids - if the file is already attached, calling again leaves it as is. For links there's no url deduplication: you can intentionally attach the same URL twice with different names, each link gets its own id.

Both arrays empty - 400. At least one must contain elements.

Links don't count against the file quota - they have no uploaded content. Only source: "upload" files do.

Detach a file/link from a task

DELETE /v1/api/tasks/:id/files/:fileId

Removes a file or link from the task's files array. :fileId is the value of the attachment's id field (the same for upload and link, distinguished by source).

If the detached source: "upload" file was uploaded by you, it's also marked for deletion and physically removed shortly after - the quota is freed immediately. If another participant in a shared list uploaded the file, for you it simply disappears from the task; the original owner stays. For source: "link" nothing is physically deleted - the link just stops being attached.

Required scope
write
Example
curl -X DELETE \
  -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/tasks/TASK_ID/files/FILE_ID
What the server returns

The full updated task - the file is no longer in the files array:

{
  "task": {
    "id":    "TASK_ID",
    "title": "Contractor agreement",
    "files": [],         // or an array with the remaining files
    ...
  }
}

If no such file is attached to the task (or it was already detached) - 404 is returned.

Notes

List notes of a list

GET /v1/api/lists/:id/info

Notes are a separate entity inside a list: title and text only, no date, importance or completion status. They don't show up in task endpoints or system views.

Required scope
read
Query parameters
  • limit - 1..500, default 100
  • offset - which note to start from, default 0
Example
curl -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/lists/LIST_ID/info
200 OK response
{
  "list":    { "id": UUID, "name": string },
  "total":   number,
  "limit":   number,
  "offset":  number,
  "records": InfoRecord[]
}
Files attached to a note

Notes have the same files array as tasks: id, name, size, url (absolute, no Authorization required). Detached/deleted files are not returned. To attach/detach a file - see "Attach files to a note" and "Detach a file from a note".

Create a note

POST /v1/api/lists/:id/info
Required scope
write
Request body
FieldTypeDescription
titlestring *Note title. Required, non-empty.
notestring | nullMulti-line note text. Optional. This is the only content field besides the title - notes have no content, type, date, importance or completion status fields.
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Wi-Fi password", "note": "Secret1234" }' \
  https://taskid.app/v1/api/lists/LIST_ID/info
What the server returns
{
  "record": {
    "id":         "INFO_ID",
    "list_id":    "LIST_ID",
    "title":      "Wi-Fi password",
    "note":       "Secret1234",
    "sort_order": 12000,
    "files":      [],
    "version":    "2026-04-18T12:34:56Z",
    "updated_at": "2026-04-18T12:34:56Z"
  }
}
Want to attach files right away?
The body of POST /v1/api/lists/:id/info doesn't attach files. Do it in two steps: 1) create the note, 2) upload files via POST /v1/api/files and attach them with POST /v1/api/info/:id/files.

Get a note

GET /v1/api/info/:id
Required scope
read
200 OK response
{ "record": InfoRecord }

Edit a note

PATCH /v1/api/info/:id
Required scope
write
Request body - all fields optional, only those present are updated
FieldTypeDescription
titlestringNew title. Empty string is not allowed.
notestring | nullNote text. null clears it.
What the server returns
{ "record": InfoRecord }   // the full note after the changes

The field structure is in the "Data types" section.

PATCH does not change files
This endpoint only changes the note's title and text. To attach a file - POST /v1/api/info/:id/files, to detach - DELETE /v1/api/info/:id/files/:fileId.

Delete a note

DELETE /v1/api/info/:id

Soft delete - same semantics as for tasks.

Required scope
write
204 No Content response
Empty body.

Attach files and links to a note

POST /v1/api/info/:id/files

Attaches uploaded files and/or external HTTPS links to a note. Semantics and contract are identical to "Attach files and links to a task": takes file_ids and links (at least one non-empty), responds with the updated note with the files field.

Required scope
write
Request body
{
  "file_ids": ["UUID", ...],            // optional
  "links":    [{ "url": "...", "name": "..." }, ...]  // optional
}
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "file_ids": ["9f1c-..."],
    "links": [{ "url": "https://wiki.example.com/wifi", "name": "Wiki" }]
  }' \
  https://taskid.app/v1/api/info/INFO_ID/files
What the server returns

The full updated note - the attached files and links are already visible in the files array:

{
  "record": {
    "id":    "INFO_ID",
    "title": "Wi-Fi password",
    "note":  "Secret1234",
    "files": [
      {
        "id":     "9f1c-...",
        "name":   "router-photo.jpg",
        "size":   92210,
        "url":    "https://taskid.app/files/aa/bb/cc/9f1c....jpg",
        "source": "upload"
      },
      {
        "id":     "1a2b-...",
        "name":   "Wiki",
        "size":   0,
        "url":    "https://wiki.example.com/wifi",
        "source": "link"
      }
    ],
    ...
  }
}

Detach a file/link from a note

DELETE /v1/api/info/:id/files/:fileId

Removes a file or link from the note's files array. Behavior and conditions - see "Detach a file/link from a task": for source: "upload" the quota is freed, for source: "link" nothing is physically removed.

Required scope
write
Example
curl -X DELETE \
  -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/info/INFO_ID/files/FILE_ID
What the server returns

The full updated note - this file is no longer in the files array:

{
  "record": {
    "id":    "INFO_ID",
    "title": "Wi-Fi password",
    "files": [],         // or the remaining files
    ...
  }
}

If no such file is attached to the note - 404 is returned.

Notifications

Send a notification

POST /v1/api/notifications

Creates a "notification" record and sends a visible push banner with the title and text to the user's devices. A drop-in replacement for a Telegram bot when delivering monitoring alerts, server events and any other external messages that need to land on a phone quickly.

The notification is physically stored in the same table as tasks and notes but has its own type (kind=2). On the client it shows up in the system "Notifications" list as well as in the specified user list. Any notification can be turned into a task with a single tap.

Required scope
write_notifications
Headers
HeaderDescription
AuthorizationBearer tkd_... with scope=write_notifications.
Content-Typeapplication/json
Idempotency-KeyOptional. If the integration retries on network failures, pass the same key - a repeated request will return the already-created notification with status 200, without a duplicate. The key is kept for 24 hours.
Request body
FieldTypeDescription
titlestring *Notification title. Required, non-empty. Shown as the bold line in the push banner and in the list.
messagestring | nullNotification text. Any URLs inside are rendered clickable by the client - you can drop links to a dashboard, ticket or log here. Optional.
list_idstring (uuid) | nullUUID of the list to attach the notification to. If omitted, the notification goes to "Inbox" (no list). For tokens with restricted list access this field is required.
priorityinteger 0..3Severity. The client uses it for the icon/color: 0=info, 1=low, 2=warn, 3=error. Defaults to 0.
file_idsUUID[]Optional. Attach previously uploaded files to the created notification. See "Upload a file". Requires the token to also have scope=write (over files), but this specific POST is also accepted with scope=write_notifications when the array is empty/missing.
links{url, name?}[]Optional. Attach external HTTPS links (dashboard, log, screenshot). Don't count against the file quota. Format same as in "Attach files and links to a task".
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: alert-12345" \
  -d '{
    "title":    "Prod is down",
    "message":  "503 on api.example.com",
    "priority": 3,
    "links": [
      { "url": "https://grafana.example.com/d/api", "name": "Dashboard" },
      { "url": "https://status.example.com/incidents/42" }
    ]
  }' \
  https://taskid.app/v1/api/notifications
What the server returns
{
  "notification": {
    "id":           "NOTIF_ID",
    "list_id":      null,
    "title":        "Prod is down",
    "note":         "503 on api.example.com",
    "priority":     3,
    "completed_at": null,
    "sort_order":   12000,
    "files": [
      {
        "id":     "1a2b-...",
        "name":   "Dashboard",
        "size":   0,
        "url":    "https://grafana.example.com/d/api",
        "source": "link"
      }
    ],
    "version":      "2026-04-18T12:34:56Z",
    "updated_at":   "2026-04-18T12:34:56Z",
    "date_create":  "2026-04-18T12:34:56Z"
  }
}

HTTP 201 on creation. If the request is repeated with the same Idempotency-Key - HTTP 200 with the same body (no new record is created).

Rate limit
Up to 60 notifications per minute per token. When exceeded the server returns HTTP 429 with a Retry-After header - integrate exponential backoff. The limit protects against a flood (a broken monitor → thousands of alerts a minute).
Behavior on iOS
The push arrives as a visible banner - it shows up even if the app isn't running or was force-killed. Tapping the banner opens the notification in the app; a single button can turn it into a task. The notification is also synced to all the user's devices (iPhone, iPad, Mac, Android) and stored in the cloud.

Attach files and links to a notification

POST /v1/api/notifications/:id/files

Adds files and/or external links to an already-existing notification. Useful when the integration sends an alert with the first request (the text arrives on the phone instantly) and uploads screenshots/dumps/dashboard links right after, without waiting for the upload to finish.

Semantics and request body are identical to "Attach files and links to a task". Returns the updated notification (a notification object, not task).

Required scope
write

Not to be confused with write_notifications - that one only allows creating notifications (POST /notifications). Attaching files needs full write.

Request body
{
  "file_ids": ["UUID", ...],            // optional
  "links":    [{ "url": "...", "name": "..." }, ...]  // optional
}
Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "links": [
      { "url": "https://logs.example.com/alert/12345", "name": "Incident logs" }
    ]
  }' \
  https://taskid.app/v1/api/notifications/NOTIF_ID/files

Detach a file/link from a notification

DELETE /v1/api/notifications/:id/files/:fileId

Removes a file or link from the notification's files array. Behavior and conditions - see "Detach a file/link from a task": for source: "upload" the quota is freed, for source: "link" nothing is physically removed.

Required scope
write
Example
curl -X DELETE \
  -H "Authorization: Bearer tkd_xxx" \
  https://taskid.app/v1/api/notifications/NOTIF_ID/files/FILE_ID

Files

Upload a file

POST /v1/api/files

Uploads one or more files to the server. The endpoint doesn't attach anything to tasks/notes/notifications on its own - it only returns an id that you can later use to attach the file via /v1/api/tasks/:id/files, /v1/api/info/:id/files or /v1/api/notifications/:id/files.

If your content is already available via an HTTPS URL (an image on a CDN, a dashboard link, a screenshot in S3) you don't need to upload it to us. Use the links field in the attach endpoints - the URL is stored as-is and the file quota is not touched.

Required scope
write
Request body

multipart/form-data. The form field is files (multiple values allowed) or file (a single one). Both are accepted.

Example
curl -X POST \
  -H "Authorization: Bearer tkd_xxx" \
  -F "files=@/path/to/contract.pdf" \
  -F "files=@/path/to/photo.jpg" \
  https://taskid.app/v1/api/files
201 Created response
{
  "files": [
    { "id": "9f1c-...", "name": "contract.pdf", "size": 184320 },
    { "id": "5b2e-...", "name": "photo.jpg",    "size":  92210 }
  ]
}

No download URL is returned at this step - you'll only need it once the file is attached to a task or note; there it comes back as an absolute URL inside the files array.

Errors
  • 400 - body is not multipart or no files / empty file.
  • 413 - maximum request or single-file size exceeded.
  • 507 - user's storage quota exceeded.

Data types

List
{
  "id":        UUID,
  "parent_id": UUID | null,   // UUID of the group this list belongs to
  "name":      string,
  "color":     string | null, // HEX or theme token name
  "icon":      string | null, // SF Symbol
  "archived":  boolean,
  "is_owner":  boolean,       // you own the list
  "is_shared": boolean        // the list has members
}
Details on is_owner and is_shared - in the "Shared lists" section.
Group
{
  "id":       UUID,
  "name":     string,
  "color":    string | null,
  "icon":     string | null,
  "archived": boolean
}
Task
{
  "id":            UUID,
  "list_id":       UUID | null,
  "parent_id":     UUID | null,   // for subtasks
  "title":         string,
  "note":          string | null,
  "due_at":        string | null, // ISO-8601 UTC
  "due_has_time":  boolean,
  "completed_at":  string | null, // ISO-8601 UTC
  "flagged":       boolean,
  "priority":      0 | 1 | 2 | 3,
  "rrule_freq":     0 | 1 | 2 | 3 | 4 | 5, // 0=none, HOURLY/DAILY/WEEKLY/MONTHLY/YEARLY
  "rrule_interval": number,  // >=1
  "rrule_byday":    number,  // bitmask Mon=1..Sun=64
  "rrule_bymonth":  number,  // bitmask Jan=1..Dec=2048
  "rrule_bysetpos": number,  // 1..4 or -1, 0=unset
  "rrule_end_kind": 0 | 1 | 2, // 0=never, 1=until date, 2=after N
  "rrule_until":    string | null, // ISO-8601 UTC
  "rrule_count":    number,
  "rrule_done":     number,
  "rrule_anchor":   0 | 1, // 0=schedule, 1=completion
  "in_my_day":     boolean,
  "in_my_day_date": string | null, // YYYY-MM-DD
  "sort_order":    number,
  "collapsed":     0 | 1,          // 1 = subtasks collapsed
  "files":         FileRef[],      // see "Attached files"
  "version":       string,    // ISO-8601 UTC, server-updated
  "updated_at":    string,    // ISO-8601 UTC
  "date_create":   string,    // ISO-8601 UTC
  "completed_user_id": UUID | null, // who closed it (relevant for shared lists)
  "deleted_user_id":   UUID | null  // who deleted it
}
InfoRecord
{
  "id":         UUID,
  "list_id":    UUID,        // always attached to a specific list
  "title":      string,
  "note":       string | null,
  "sort_order": number,
  "files":      FileRef[],   // see "Attached files"
  "version":    string,      // ISO-8601 UTC
  "updated_at": string       // ISO-8601 UTC
}
FileRef
{
  "id":   UUID,
  "name": string,    // original file name
  "size": number,    // size in bytes
  "url":  string     // absolute URL (https://taskid.app/files/...)
}
Notes have no content, type, deadline, completion status, flags, priority or "My Day" fields - by design. The content lives only in title and note. Notes don't show up in task endpoints or system views.
Questions and feedback - reach the support account.