## 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

```bash
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

```bash
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

```javascript
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

```javascript
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](/docs/chats/management/read-all) 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:

```json
{
  "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:

```json
{
  "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](/docs/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](/docs/chats/discovery/recent) 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](/docs/chats/events/poll).

## See also

- [Recent dialogs](/docs/chats/discovery/recent)
- [Get events](/docs/chats/events/poll)
- [Chat discovery](/docs/chats/discovery)
