For AI agents: markdown of this page — /docs-content-en/chats/discovery/project-recent.md documentation index — /llms.txt
Project chats
GET /v1/chats/projects/:projectId/recent
Returns the chats of one project from the current user's recent dialog list: the project's own chat and the chats nested in it. Use it for a project screen that shows the project's discussions next to its main chat.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
projectId (path) |
integer | yes | — | ID of the project chat, a positive integer. This is the chatId of the project row in the collab list |
limit (query) |
integer | no | 50 | Rows per page, 50 to 200. A smaller value is raised to 50 and a larger one is clamped to 200, and both adjustments are echoed in meta |
lastMessageDate (query) |
string | no | — | Cursor of the next page: the smallest dateLastActivity among the unpinned rows of the previous page. Format YYYY-MM-DDTHH:MM:SS with an offset or Z, without fractional seconds. Not passed on the first page |
Other parameters, including format and unreadOnly, and repeated parameters are rejected with 400 INVALID_PARAMS.
Pagination — as in recent dialogs in the v2 mode. The cursor of the next page is the smallest non-empty dateLastActivity among the unpinned rows; compare instants, not strings. The bound is inclusive: the row with the cursor date arrives again, so deduplicate by chatId. The list ends only at hasNextPage: false. If hasNextPage is true but there is no cursor or it equals the previous one, repeat the request with a larger limit, up to 200. Do not discard response rows: the cursor is computed over the whole response.
The first page modifies data. On the first read of a project, Bitrix24 creates this project's CoPilot chat for the key owner — once per project and only for a project member. For that reason a read-only key gets 403 WRITE_BLOCKED_READONLY_KEY on a request without lastMessageDate, before the Bitrix24 call. Requests with lastMessageDate only read and are available to such a key — see access rights.
Examples
curl — personal key
curl "https://vibecode.bitrix24.com/v1/chats/projects/53/recent?limit=50" \
-H "X-Api-Key: YOUR_API_KEY"
curl — OAuth application
curl "https://vibecode.bitrix24.com/v1/chats/projects/53/recent?limit=50" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
JavaScript — personal key
const rows = new Map()
let limit = 50
let cursor
for (;;) {
const url = new URL('https://vibecode.bitrix24.com/v1/chats/projects/53/recent')
url.searchParams.set('limit', String(limit))
if (cursor) url.searchParams.set('lastMessageDate', cursor)
const res = await fetch(url, { headers: { 'X-Api-Key': 'YOUR_API_KEY' } })
const { data } = await res.json()
for (const item of data.recentItems) rows.set(item.chatId, item)
if (!data.hasNextPage) break
const dates = data.recentItems.filter((i) => !i.pinned && i.dateLastActivity).map((i) => i.dateLastActivity)
const next = dates.length ? dates.reduce((a, b) => (Date.parse(a) <= Date.parse(b) ? a : b)) : undefined
if (!next || (cursor && Date.parse(next) >= Date.parse(cursor))) {
if (limit === 200) throw new Error('Recent-list cursor did not advance; restart with a full refresh')
limit = Math.min(limit * 2, 200)
continue
}
cursor = next
limit = 50
}
JavaScript — OAuth application
const rows = new Map()
let limit = 50
let cursor
for (;;) {
const url = new URL('https://vibecode.bitrix24.com/v1/chats/projects/53/recent')
url.searchParams.set('limit', String(limit))
if (cursor) url.searchParams.set('lastMessageDate', cursor)
const res = await fetch(url, {
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
})
const { data } = await res.json()
for (const item of data.recentItems) rows.set(item.chatId, item)
if (!data.hasNextPage) break
const dates = data.recentItems.filter((i) => !i.pinned && i.dateLastActivity).map((i) => i.dateLastActivity)
const next = dates.length ? dates.reduce((a, b) => (Date.parse(a) <= Date.parse(b) ? a : b)) : undefined
if (!next || (cursor && Date.parse(next) >= Date.parse(cursor))) {
if (limit === 200) throw new Error('Recent-list cursor did not advance; restart with a full refresh')
limit = Math.min(limit * 2, 200)
continue
}
cursor = next
limit = 50
}
Response fields
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on success |
data.recentItems |
array | List rows in the order Bitrix24 returns them. On the first page the row of the project itself arrives together with the nested chats |
data.recentItems[].dialogId |
string | Dialog ID, chatXXX |
data.recentItems[].chatId |
number | Chat ID, the deduplication key |
data.recentItems[].messageId |
number | ID of the last message |
data.recentItems[].pinned |
boolean | The chat is pinned for the current user |
data.recentItems[].unread |
boolean | The chat is manually marked as unread |
data.recentItems[].dateLastActivity |
string|null | Instant of the last activity. The smallest value among the unpinned rows is the cursor of the next page |
data.chats |
array | Chat cards of the rows: id, dialogId, name, the current user's role and other chat fields |
data.chats[].type |
string | Chat type. collab for the project's own chat; for nested chats, the type of the chat itself, for example chat or copilot |
data.chats[].parentChatId |
number | 0 for the project's own chat, projectId for nested chats |
data.chats[].entityType |
string | SONET_GROUP for the project's own chat |
data.chats[].entityId |
string | ID of the project group for the project's own chat |
data.messages |
array | The chats' last messages |
data.users |
array | Authors of the last messages |
data.files |
array | Files of the last messages |
data.recentConfigs |
array | The list sections each chat is counted in: chatId and sections |
data.hasNextPage |
boolean | false — the end of the list |
data.sectionMeta |
object | Project details. Present only on the first page; responses to requests with lastMessageDate do not have the field |
data.sectionMeta.fixedChatIds |
array | ID of the project's own chat. An empty array if projectId is not a project chat |
data.sectionMeta.collabInfo |
object|null | Project summary. null if projectId is not a project chat |
data.sectionMeta.collabInfo.collabId |
number | ID of the project group |
data.sectionMeta.collabInfo.guestCount |
number | Number of project guests |
data.sectionMeta.collabInfo.entities |
object | The project sections tasks, files and calendar. Each has counter — the counter, and url — the section address relative to the Bitrix24 account domain |
meta.requestedLimit |
number | The limit passed. Present together with appliedLimit only when the value was outside the range of 50 to 200 |
meta.appliedLimit |
number | The limit applied — only when it was clamped to a bound |
The data object has the same shape as in recent dialogs in the v2 mode. Its other collections — copilot, birthdayList, stickers, additionalMessages, messagesAutoDeleteConfigs, forwardSource — arrive unchanged.
Response example
The first page, with the main fields shown:
{
"success": true,
"data": {
"recentItems": [
{
"dialogId": "chat57",
"chatId": 57,
"messageId": 431,
"type": "chat",
"pinned": false,
"unread": false,
"dateUpdate": "2026-09-30T10:51:36+00:00",
"dateLastActivity": "2026-09-30T10:51:36+00:00"
},
{
"dialogId": "chat55",
"chatId": 55,
"messageId": 415,
"type": "chat",
"pinned": false,
"unread": false,
"dateUpdate": "2026-09-30T10:07:32+00:00",
"dateLastActivity": "2026-08-10T14:31:46+00:00"
},
{
"dialogId": "chat53",
"chatId": 53,
"messageId": 431,
"type": "chat",
"pinned": false,
"unread": false,
"dateUpdate": "2026-09-30T10:51:36+00:00",
"dateLastActivity": "2026-09-30T10:51:36+00:00"
}
],
"chats": [
{
"id": 57,
"dialogId": "chat57",
"name": "Terms negotiation",
"type": "chat",
"parentChatId": 53,
"entityType": "",
"entityId": "",
"owner": 1,
"role": "owner"
},
{
"id": 55,
"dialogId": "chat55",
"name": "Session #2",
"type": "copilot",
"parentChatId": 53,
"entityType": "SONET_PROJECT_COPILOT",
"entityId": "3",
"owner": 1,
"role": "owner"
},
{
"id": 53,
"dialogId": "chat53",
"name": "Partner program launch",
"type": "collab",
"parentChatId": 0,
"entityType": "SONET_GROUP",
"entityId": "3",
"owner": 1,
"role": "owner"
}
],
"recentConfigs": [
{ "chatId": 57, "sections": ["default", "chat", "collabDefault", "collabChat"] },
{ "chatId": 55, "sections": ["default", "copilot", "collabDefault"] },
{ "chatId": 53, "sections": ["default", "collab"] }
],
"hasNextPage": false,
"sectionMeta": {
"fixedChatIds": [53],
"collabInfo": {
"guestCount": 0,
"collabId": 3,
"entities": {
"tasks": { "counter": 0, "url": "/workgroups/group/3/tasks/" },
"files": { "counter": 0, "url": "/workgroups/group/3/disk/path/?c_element=files_button" },
"calendar": { "counter": 0, "url": "/workgroups/group/3/calendar/" }
}
}
}
}
}
Error response example
403 — the first page is requested with a read-only key:
{
"success": false,
"error": {
"code": "WRITE_BLOCKED_READONLY_KEY",
"message": "Key is in read-only mode. Switch the key to read and write in /keys to allow Bitrix24 data changes.",
"details": {
"method": "im.v2.Recent.load",
"keyName": "Reports dashboard",
"currentMode": "READONLY",
"switchUrl": "/keys"
}
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 400 | INVALID_PARAMS |
projectId is not a positive integer. Checked before the Bitrix24 call |
| 400 | INVALID_PARAMS |
A parameter other than limit and lastMessageDate, a repeated parameter, a non-numeric limit, or lastMessageDate not in the YYYY-MM-DDTHH:MM:SS format with an offset or Z, or with a nonexistent date or time (2026-02-30, 24:00). Checked before the Bitrix24 call |
| 403 | WRITE_BLOCKED_READONLY_KEY |
A request without lastMessageDate is sent with a read-only key. The key name and its mode are in error.details |
| 422 | BITRIX_ERROR |
Bitrix24 returned an error; the portal code is in error.b24Code. CHAT_NOT_FOUND — there is no chat with this projectId |
| 404 | ENTITY_NOT_FOUND |
Bitrix24 returned "not found"; the portal code is in error.b24Code |
| 403 | BITRIX_ACCESS_DENIED |
The key owner has no access to the project chat |
| 403 | SCOPE_DENIED |
The API key does not have the im scope |
| 401 | TOKEN_MISSING |
The API key has no Bitrix24 tokens configured |
| 502 | BITRIX_UNAVAILABLE |
Bitrix24 is unavailable or returned a server error |
Full list of common API errors — Errors.
Known specifics
A chat that is not a project is an empty list, not an error. The ID of a group chat not linked to a project returns 200 with empty collections and hasNextPage: false. Such a response does not reveal a mistake in projectId: a project is indicated by a non-empty sectionMeta.fixedChatIds on the first page.
The refusal for a read-only key does not depend on the project state. The first page is closed to such a key even when the project's CoPilot chat already exists.