For AI agents: markdown of this page — /docs-content-en/chats/discovery/sync.md documentation index — /llms.txt
Chat changes since a moment
GET /v1/chats/sync
Returns what changed in the current user's chats since the given moment: new and removed chats, new, edited and deleted messages, pins. Use it to bring a local copy of the chats up to date after a break without re-reading the whole list.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
lastDate (query) |
string | yes | — | The moment to read changes from. Format YYYY-MM-DDTHH:MM:SS with an offset or Z, without fractional seconds. For the next page, navigationData.lastServerDate of the previous one |
lastId (query) |
integer | no | — | navigationData.lastId of the previous page, 0 or more. Not passed on the first page |
limit (query) |
integer | no | 50 | Change log entries per page, 1 to 200. A smaller value is raised to 1 and a larger one is clamped to 200, both echoed in meta |
Other parameters and repeated parameters are rejected with 400 INVALID_PARAMS.
Walk. Request the first page with lastDate set to the moment up to which your local copy is current. While navigationData.hasMore is true, request the next one with lastDate equal to navigationData.lastServerDate and lastId equal to navigationData.lastId. Save the lastServerDate and lastId pair of the last page: the next sync starts from it.
Examples
curl — personal key
curl "https://vibecode.bitrix24.com/v1/chats/sync?lastDate=2026-09-24T10:00:00Z&limit=200" \
-H "X-Api-Key: YOUR_API_KEY"
curl — OAuth application
curl "https://vibecode.bitrix24.com/v1/chats/sync?lastDate=2026-09-24T10:00:00Z&limit=200" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
JavaScript — personal key
let lastDate = '2026-09-24T10:00:00Z'
let lastId
const added = new Set()
for (;;) {
const url = new URL('https://vibecode.bitrix24.com/v1/chats/sync')
url.searchParams.set('lastDate', lastDate)
url.searchParams.set('limit', '200')
if (lastId !== undefined) url.searchParams.set('lastId', String(lastId))
const res = await fetch(url, { headers: { 'X-Api-Key': 'YOUR_API_KEY' } })
const { data } = await res.json()
if (Array.isArray(data)) throw new Error('Incremental sync is unavailable; refresh the full recent-chat list')
for (const id of Object.values(data.messageSync.addedMessages)) added.add(id)
lastDate = data.navigationData.lastServerDate
lastId = data.navigationData.lastId
if (!data.navigationData.hasMore) break
}
JavaScript — OAuth application
let lastDate = '2026-09-24T10:00:00Z'
let lastId
const added = new Set()
for (;;) {
const url = new URL('https://vibecode.bitrix24.com/v1/chats/sync')
url.searchParams.set('lastDate', lastDate)
url.searchParams.set('limit', '200')
if (lastId !== undefined) url.searchParams.set('lastId', String(lastId))
const res = await fetch(url, {
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
})
const { data } = await res.json()
if (Array.isArray(data)) throw new Error('Incremental sync is unavailable; refresh the full recent-chat list')
for (const id of Object.values(data.messageSync.addedMessages)) added.add(id)
lastDate = data.navigationData.lastServerDate
lastId = data.navigationData.lastId
if (!data.navigationData.hasMore) break
}
Response fields
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on success |
data.chatSync.addedRecent |
object | Chats that appeared in the dialog list: key and value are the chat ID |
data.chatSync.addedChats |
object | Chats that appeared or changed for the user: key and value are the chat ID |
data.chatSync.deletedChats |
object | Chats removed from the user's dialog list: key and value are the chat ID |
data.chatSync.completeDeletedChats |
object | Chats deleted from the Bitrix24 account: key and value are the chat ID |
data.chatSync.readAllChats |
boolean | Arrives as true if the user read all chats during this period: reset the unread counters in the local copy |
data.messageSync.addedMessages |
object | New messages: key and value are the message ID |
data.messageSync.updatedMessages |
object | Edited messages: key and value are the message ID |
data.messageSync.completeDeletedMessages |
object | Deleted messages: key and value are the message ID |
data.pinSync.addedPins |
object | New pinned messages: key and value are the pin ID |
data.pinSync.deletedPins |
object | Removed message pins: key and value are the pin ID |
data.userSync |
object | User changes: updatedUsers and deletedUsers |
data.chats |
array | Chat cards for the chats from chatSync |
data.messages |
array | Messages from messageSync |
data.pins |
array | Message pins from pinSync: id, messageId, chatId, authorId, dateCreate |
data.recentItems |
array | Dialog list rows for the chats from chatSync |
data.users |
array | Users mentioned in the changes |
data.dialogIds |
object | Mapping of chat ID to dialog ID: key is the chat ID, value is the dialogId |
data.navigationData.lastServerDate |
string | lastDate for the next page: the moment of the last change log entry on this page |
data.navigationData.lastId |
number | lastId for the next page |
data.navigationData.hasMore |
boolean | true if there is a next page |
meta.requestedLimit |
number | The limit passed. It arrives together with appliedLimit, only when the value was outside the range of 1 to 200 |
meta.appliedLimit |
number | The limit applied |
Response example
The main fields are shown:
{
"success": true,
"data": {
"chatSync": {
"addedRecent": { "264431": 264431 },
"addedChats": { "264431": 264431 },
"deletedChats": { "264418": 264418 },
"completeDeletedChats": []
},
"messageSync": {
"addedMessages": { "609001": 609001 },
"updatedMessages": [],
"completeDeletedMessages": []
},
"pinSync": {
"addedPins": [],
"deletedPins": []
},
"userSync": {
"updatedUsers": [],
"deletedUsers": []
},
"messages": [
{
"id": 609001,
"chatId": 264431,
"authorId": 1,
"date": "2026-09-24T21:10:22+00:00",
"text": "The draft plan is ready"
}
],
"chats": [
{
"id": 264431,
"dialogId": "chat264431",
"name": "Release preparation",
"type": "chat"
}
],
"dialogIds": { "264431": "chat264431" },
"navigationData": {
"lastServerDate": "2026-09-24T21:10:22+00:00",
"hasMore": false,
"lastId": 182091
}
}
}
Error response example
400 — a date without an offset:
{
"success": false,
"error": {
"code": "INVALID_PARAMS",
"message": "`lastDate` must be a datetime YYYY-MM-DDTHH:MM:SS with a UTC offset (+03:00) or Z and no fractional seconds — `navigationData.lastServerDate` of the previous page."
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 400 | INVALID_PARAMS |
No lastDate, lastDate 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), lastId less than 0, a non-numeric limit, a parameter other than lastDate, lastId and limit, or a repeated parameter. Checked before the Bitrix24 call |
| 422 | BITRIX_ERROR |
Bitrix24 returned an error; the Bitrix24 code is in error.b24Code |
| 403 | BITRIX_ACCESS_DENIED |
Bitrix24 denied access |
| 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
An empty collection arrives as an array. When a change group has no entries, an empty array arrives instead of an object: "updatedMessages": []. Read such fields with Object.values, which handles both forms the same way.
Incremental history is not a four-week guarantee. Some Bitrix24 change records remain for only about a day. After a long interruption, especially one approaching a day, re-read recent dialogs and the needed message history in full before trusting a new delta.
Sync can be turned off on a Bitrix24 account. Then data is an empty array, not an object with the fields above. In that case, re-read the list in full.
These are not real-time events. The endpoint answers "what has changed since a given time"; to receive new messages as they appear, use event polling.