Comments
Post, list, and delete comments on tasks and subtasks.
A comment is a message on a task or subtask. The API lets an external system record what happened on its side — a customer reply, a change of plan, a support outcome — directly in the task's conversation, and mention people so they get notified.
Comments created through the API are published as the person who owns the API key. There is no way to post on someone else's behalf, and the key never sees more than its owner sees in the app: a restricted project the owner cannot open stays invisible here too.
Only tasks and subtasks are addressable through this collection. The app also supports comments on projects and clients; those are not part of the API, and a comment belonging to one returns 404 here.
Endpoints
| Method | Path | Notes |
|---|---|---|
GET | /api/v1/comments?taskId=… | List a task's or subtask's comments, oldest first, cursor-paginated. |
POST | /api/v1/comments | Post a comment. Accepts Idempotency-Key. |
DELETE | /api/v1/comments/{id} | Delete a comment. |
Editing a comment, replying to one, and attaching images are app-only for now.
Comment model
{
"id": "c1…",
"taskId": "t1…",
"subtaskId": null,
"parentCommentId": null,
"text": "Client approved the scope.",
"contentHtml": "<p>Client approved the scope.</p>",
"author": { "id": "u1…", "name": "Ana", "image": null },
"mentionedUsers": [{ "id": "u2…", "name": "Maria", "image": null }],
"mentionedTasks": [{ "id": "t2…", "subject": "Draft the estimate" }],
"imageCount": 0,
"createdAt": "2026-07-21T12:00:00.000Z",
"editedAt": null,
"replies": []
}| Field | Notes |
|---|---|
text | The comment as plain text — the easiest field to read back. |
contentHtml | The stored rich text, exactly as the app renders it. |
mentionedUsers | People who were mentioned and notified. |
mentionedTasks | Tasks linked from inside the comment. Read-only — the API does not create task mentions. |
imageCount | How many images the comment carries. Read-only — images are app-only. |
replies | Replies to this comment, one level deep. Read-only — the API does not create replies. |
Listing comments
GET /api/v1/comments?taskId=t1…&limit=50Pass exactly one of taskId or subtaskId. The response holds root comments only, each with its replies nested:
{
"data": [ /* comments */ ],
"pagination": { "nextCursor": "c9…" }
}Pass nextCursor back as ?cursor= for the next page. nextCursor is null on the last page. See Pagination.
Posting a comment
POST /api/v1/comments
{
"taskId": "t1…",
"text": "Client approved the scope,\nbut asked to revisit the deadline.",
"mentionedUserIds": ["u2…", "u3…"]
}| Field | Type | Notes |
|---|---|---|
taskId / subtaskId | UUID | Exactly one, required. |
text | string | 1–10 000 chars. Plain text. Line breaks are preserved; a blank line starts a new paragraph. |
mentionedUserIds | UUID[] | Up to 50 people to mention: organization members, or active contacts of the task's client. Optional. |
clientVisible | boolean | Shares the comment with the client portal. Defaults to false. Only a task shown in the client portal accepts true. |
Response: 201 Created with the comment.
text is plain text, always. Any markup you send (<b>, <script>, …) is shown literally, never interpreted. Sending formatted content is not supported in this version.
How mentions behave
Mentioning someone through the API works exactly like mentioning them in the app — they get the in-app and email notification and land on the task. Two cases behave differently, and both are deliberate:
The person can see the task
They are notified, and the comment ends with their mention badge. They appear in mentionedUsers.
The person is in the organization but cannot see this task
The comment is still published — the call does not fail. That person is not notified and does not appear in mentionedUsers. Instead, the comment gains a closing line naming them, visible to everyone who reads the task:
John Doe was mentioned but does not have access to this task.
The line is written in the language of the API key's owner, and it stays in the comment as written — it records what was true when the comment was posted, so it does not change if that person later gains access.
The person is neither a member nor a contact of the task's client
This is treated as an integration error. Nothing is created and the call returns 422 listing the ids at fault:
{
"error": "One or more mentioned people are not members of this organization or contacts of the task's client.",
"details": { "mentionedUserIds": ["bad-id-1", "bad-id-2"] }
}Fix the ids and send the request again.
Mentioning the key's own owner never sends a notification — the same rule the app follows.
Client contacts
A contact of the task's client can be mentioned too, but only on a comment posted with clientVisible: true. The contact appears in mentionedUsers. Mentioning a contact on an internal comment is rejected with 400, and nothing is created:
{ "error": "Only a comment visible to the client can mention a client contact." }A visible comment emails the task's contacts, like a visible comment posted in the app: the contacts it mentions, and the contacts who already take part in the task's conversation.
Deleting a comment
DELETE /api/v1/comments/{id}Returns { "data": { "success": true } }.
Permission matches the app exactly: the comment's author, an organization owner or admin, or the manager of the comment's project. Anyone else gets 403.
A comment that already has replies cannot be deleted — remove the replies first. The attempt returns 400 and the comment stays put.
Errors
| Status | When |
|---|---|
400 | The comment has replies, a visible comment is posted on a task not shown in the client portal, or an internal comment mentions a client contact. |
401 | Missing or invalid API key. |
403 | The key's owner cannot see the task's project, or may not delete this comment. |
404 | The task, subtask, or comment does not exist in this organization — or the comment belongs to a project or client. |
422 | Invalid body, or a mentioned person is neither a member of the organization nor an active contact of the task's client. |