For AI agents: markdown of this page — /docs-content-en/entities/task-comments/list.md documentation index — /llms.txt
List comments
GET /v1/tasks/:taskId/comments
Returns the comments of a specific task. On the new task card the list is read from the chat; system messages are filtered out. On the old card it is read from the comments block inside the card.
Comments written before the Bitrix24 account moved to the new task card do not migrate into the chat, so a task that has a chat can still keep part of its comments in the legacy store. When the chat is read to its end and yields no comment at all, the list reads the legacy store and answers from it; when the chat yielded nothing and could not be read to its end either, the legacy store is still read, but the chat is not listed in meta.sources. meta.sources names the stores whose contents this response learned, while meta.truncated reports whether what was read is complete: together they tell you whether an empty data means that the task has no comments or that only one of the two stores was heard from.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
taskId (path) |
number | yes | — | Parent task ID |
limit (query) |
number | 50 |
Page size (up to 200) | |
offset (query) |
number | 0 |
How many matching comments to skip before returning results. Honored on every read path, including an answer from the legacy store; a request without filter and with a sort by ID used to ignore it and return the same page at any offset, while an answer from the legacy store used to arrive as whatever Bitrix24 returned in one call |
|
sort (query) |
string | id:desc |
Sorting: field ID, AUTHOR_ID or POST_DATE and direction asc or desc — ?sort=post_date:desc. The field name is case-insensitive; the camelCase names id, authorId, authorName, createdAt, authorEmail are accepted too. Sorting by AUTHOR_NAME and AUTHOR_EMAIL is accepted on the old card only; on the new one it returns 400 UNSUPPORTED_SORT_FIELD |
|
filter (query) |
object | — | Filter by the fields ID, AUTHOR_ID, POST_DATE. The camelCase names id, authorId, authorName, createdAt are accepted too. Passed as JSON: ?filter={"AUTHOR_ID":1}. The field name may carry a !, >, >=, < or <= prefix — ?filter={">=POST_DATE":"2026-05-01T00:00:00Z"}. The value for POST_DATE is an ISO 8601 date in UTC; several fields are combined with "and". Filtering by AUTHOR_NAME is accepted on the old card only; on the new one it returns 400 UNSUPPORTED_FILTER_FIELD. Important: pass the whole filter in a single form — either bracket notation or one JSON object. A mixed envelope (?filter[NAME]=x&filter=) and a bracket condition deeper than two levels are rejected with 400 INVALID_FILTER: half of the conditions are irrecoverably lost while the query string is parsed. An empty ?filter= means "no filter". |
Examples
curl — personal key
curl "https://vibecode.bitrix24.com/v1/tasks/289/comments?limit=20&sort=id:desc" \
-H "X-Api-Key: YOUR_API_KEY"
curl — OAuth application
curl "https://vibecode.bitrix24.com/v1/tasks/289/comments?limit=20&sort=id:desc" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
JavaScript — personal key
const url = 'https://vibecode.bitrix24.com/v1/tasks/289/comments?limit=20&sort=id:desc'
const res = await fetch(url, {
headers: {
'X-Api-Key': 'YOUR_API_KEY',
},
})
const { success, data, meta } = await res.json()
console.log(`Received ${data.length} comments`)
JavaScript — OAuth application
const url = 'https://vibecode.bitrix24.com/v1/tasks/289/comments?limit=20&sort=id:desc'
const res = await fetch(url, {
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
})
const { success, data, meta } = await res.json()
Response fields
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on success |
data |
array | Array of comments |
data[].id |
number | Comment identifier |
data[].taskId |
number | Parent task ID |
data[].authorId |
number | Author. Profile: GET /v1/users/:authorId |
data[].message |
string | Comment text (supports BB-code) |
data[].createdAt |
datetime | Creation date (UTC ISO 8601) |
data[].attachments |
array | Attached files. The key is always present; with no files it is an empty list. Each element: fileId, downloadUrl, plus name and size when Bitrix24 returns them. Downloading — Download an attachment |
meta.total |
number | Number of comments. On the new card for a request with filter, a sort other than ID, or a non-zero offset — the exact count of the matches within the scanned window. Without those — the exact count when meta.hasMore is false; on a full page of comments, an "at least" estimate equal to data.length + 1. In an answer from the legacy store it is the counter Bitrix24 returned for THIS call (scoped by filter when one is given); it may return no counter of its own, and the number then degenerates into the row count of the single page received. In neither case is it a task-wide counter: that store returns at most 50 records per call and offers no navigation. It may exceed the length of data; it cannot reach past that one call. Read it together with meta.truncated |
meta.hasMore |
boolean | Whether there are more comments beyond limit — within the set the answer reached. On the chat walk that set is the chat ONLY: pre-migration comments in the legacy store are not part of it, so hasMore: false there means the chat ended, not that the task ran out of comments. In an answer from the legacy store the set is limited to ONE task.commentitem.getlist call (at most 50 records, no navigation): hasMore: false then means "nothing left to page inside the page we got", not "the comments ended". ⚠️ So make the TRIPLE your exit condition: page while hasMore is true; the answer is complete only when no meta.truncated sits beside it AND meta.sources equals ["chat", "legacy"] |
meta.truncated |
boolean | Comes with the value true only: what was read is incomplete, comments remained beyond the edge of what the answer reached. BOTH paths send it, and meta.total beside it means different things. The chat walk (new card) stopped before the history ended — the scan window was exhausted, the walk broke off, or part of the history is hidden by the Bitrix24 account plan; there meta.total counts only the ones inside it. Most often it is a request with filter or a sort other than ID, but a plain request enters the same walk when a page of system notifications leaves the slice short. The legacy store returned a full page: task.commentitem.getlist is called once and offers no navigation, so a filled page is the only available sign that more may sit behind it; there meta.total is what Bitrix24 returned for THIS call, or simply the row count of the page received when it returned no counter of its own; it is not a task-wide counter. A store that was not read at all is NOT reported by this flag — meta.sources reports it, by not naming it. The absence of the field only says that the stores that WERE read were read in full — on its own it does not certify the answer complete: an answer is complete when the set is ["chat", "legacy"] AND the field is absent. ⚠️ On a task with a chat the legacy store is read only when the chat returned NO comments at all, so an answer with ["chat"] may be missing pre-migration comments that sit in the legacy store |
meta.sources |
array | The stores whose contents this response learned: ["chat"] — the task chat only, ["legacy"] — the legacy store only, ["chat", "legacy"] — both, [] — neither. A store is listed when it returned comments, or when it was read to the end and proved empty. A store the response learned nothing from is not listed: one that refused, one whose method is missing, or one whose walk broke off before collecting a single comment. ⚠️ An ABSENT store is not that case: when a task has no chat allotted, chat comments cannot exist, the knowledge is complete, and chat IS listed. Only a fresh check proves it: within the five-minute cache the same answer honestly says ["legacy"], because a chat may have been allotted meanwhile. ⚠️ Being listed does NOT mean "read in full": the chat is listed together with truncated: true when its walk broke off after collecting at least one comment. meta.sources says nothing about completeness — that is what meta.truncated reports |
Response example
{
"success": true,
"data": [
{
"id": 36559,
"taskId": 289,
"authorId": 1,
"message": "Added a draft of the report to the comments — please take a look.",
"createdAt": "2026-05-13T12:30:58.000Z",
"attachments": []
},
{
"id": 36557,
"taskId": 289,
"authorId": 79,
"message": "[USER=99]Alex[/USER], done, thanks.",
"createdAt": "2026-05-12T15:11:04.000Z",
"attachments": []
}
],
"meta": {
"total": 2,
"hasMore": false,
"sources": ["chat"]
}
}
An example of a comment with a non-empty attachments — Get comment.
Error response example
400 — taskId is not a positive integer:
{
"success": false,
"error": {
"code": "INVALID_PARAMS",
"message": "taskId must be a positive integer"
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 400 | INVALID_PARAMS |
taskId is invalid (not a positive integer) |
| 400 | INVALID_SORT_FIELD |
Sort by an unsupported field or direction. Supported: ID, AUTHOR_ID, AUTHOR_NAME, AUTHOR_EMAIL, POST_DATE and their camelCase names, with direction asc or desc. On the new card AUTHOR_NAME and AUTHOR_EMAIL respond with UNSUPPORTED_SORT_FIELD |
| 400 | UNSUPPORTED_SORT_FIELD |
New card only: sort by AUTHOR_NAME or AUTHOR_EMAIL. Sort by ID, AUTHOR_ID or POST_DATE instead |
| 400 | INVALID_FILTER |
The filter parameter is not valid JSON, or it names one field twice in different spellings of the name or of the equality sign, for example {"AUTHOR_ID":1,"authorId":2} or {"AUTHOR_ID":1,"=authorId":2}. New card only: filter is a scalar or an empty array instead of an object (0, false, "", []); the value of ID or AUTHOR_ID is not a number; the value of POST_DATE does not parse as a date; or a field value is an object or an array instead of a scalar. A non-empty array or a non-empty string in filter yields UNKNOWN_FILTER_FIELD — its indexes are read as field names. An ambiguous envelope is rejected as well: the bracket form and JSON in one request (in either order), or a bracket condition deeper than two levels — half of the conditions are irrecoverably lost during parsing, so the request is refused rather than half-applied. |
| 400 | UNKNOWN_FILTER_FIELD |
Filter by an unsupported field. Supported: ID, AUTHOR_ID, AUTHOR_NAME, POST_DATE and their camelCase names. On the new card AUTHOR_NAME responds with UNSUPPORTED_FILTER_FIELD |
| 400 | UNSUPPORTED_FILTER_FIELD |
New card only: filter by AUTHOR_NAME. Filter by ID, AUTHOR_ID or POST_DATE instead; look up an employee's identifier by name via GET /v1/users |
| 422 | BITRIX_ERROR |
No task with this taskId. The error text comes from Bitrix24 verbatim, with ERROR_CORE in the b24Code field |
| 403 | SCOPE_DENIED |
The API key does not have the task scope |
| 401 | TOKEN_MISSING |
The API key has no configured tokens |
Full list of common API errors — Errors.
Known specifics
Scan window when filtering. On the new card a request with filter or a sort other than ID scans up to 2000 of the newest task chat messages, system notifications included, and picks the matching comments out of them. If the task history is longer, or is limited by the Bitrix24 account plan, the response carries meta.truncated: true: comments that are not in the response remained beyond the window edge, and meta.total counts only the ones inside it. This is not a sign of a next page — offset does not move the window. Narrow the selection with a POST_DATE or AUTHOR_ID filter. For the same reason ?sort=post_date:asc on such a task returns the oldest comments within the window, not the oldest comments of the task.
Filter and sort by author — via AUTHOR_ID. The fields AUTHOR_NAME and AUTHOR_EMAIL are accepted on the old card and return 400 on the new one, so a request by author name does not work on every Bitrix24 account. To make the selection independent of how the Bitrix24 account is configured, find the employee via GET /v1/users and filter or sort by AUTHOR_ID.
The list reads the chat whenever the task has one. When a task has a chat, the response is assembled from its messages. Comments written before the Bitrix24 account moved to the new card were never migrated into the chat, so when the chat holds no comment of its own the list reads the legacy comment block of the old card and answers from it — meta.sources names the stores it actually read. Getting a comment by identifier and downloading an attachment check both stores, but they do NOT guarantee a hit: the lookup goes chat-first, and chat identifiers and old-card identifiers live in separate spaces that overlap numerically, so on a task holding both stores the same number can match a different chat comment and the request never reaches the old card. See "A matching number means nothing" in Download an attachment; take a reliable id from a response of the same store rather than from somewhere else. If you know the id, use Get comment instead of relying on the list.
System notifications and the end of the list. System notifications about task creation, deadline change and assignee change are filtered out on the API side. When a slice consists only of them, the service reads further into the task history instead of answering an empty page that invites more paging: meta.hasMore: false with meta.total: 0 means the source that was read really holds no comments — that is, system notifications do not explain the empty answer. meta.sources tells you whether the task itself has none — only ["chat", "legacy"] is conclusive, see the next item. Such a task used to answer {"data": [], "total": 1, "hasMore": true} at every offset, so a while (hasMore) offset += limit loop never ended. Paginate by meta.hasMore and the contents of data.
Read an empty data through meta.sources together with meta.truncated. A conclusive "the task has no comments" comes from the set ["chat", "legacy"] with no meta.truncated: the contents of both stores are known and both are empty. The same set with truncated: true means not everything was read — including when the legacy store returned a full page and more comments may sit behind it. Any other set means some store was not read, and an empty data says nothing about it. ["legacy"] — the chat did not answer: it refused (no im scope, the method is unavailable), the form of its reply could not be parsed, or the scan broke off. A task that has NO chat yields this set only within the five-minute cache, where the absence of a chat is indistinguishable from an unparsed reply; on a FRESH check its chat store is provably empty and the answer carries the full set. ["chat"] — the legacy store was not read: the chat returned at least one comment, in which case the follow-up read never starts, or that read failed. [] — neither answered. The status stays 200 in every case: the endpoint returns what it learned and names it, instead of failing a request that worked or passing silence off as an answer.