## Изменения чатов с момента

`GET /v1/chats/sync`

Возвращает, что изменилось в чатах текущего пользователя начиная с указанного момента: новые и удалённые чаты, новые, изменённые и удалённые сообщения, закрепы. Подходит, чтобы догнать локальную копию чатов после перерыва, не перечитывая весь список.

## Параметры

| Параметр | Тип | Обяз. | По умолч. | Описание |
|----------|-----|:-----:|-----------|---------|
| `lastDate` (query) | string | да | — | С какого момента читать изменения. Формат `YYYY-MM-DDTHH:MM:SS` со смещением или `Z`, без долей секунды. Для следующей страницы — `navigationData.lastServerDate` предыдущей |
| `lastId` (query) | integer | нет | — | `navigationData.lastId` предыдущей страницы, не меньше `0`. На первой странице не передаётся |
| `limit` (query) | integer | нет | 50 | Записей журнала изменений на странице, от 1 до 200. Меньшее значение поднимается до 1, большее срезается до 200, оба раза с эхом в `meta` |

Другие параметры и повторы отклоняются с `400 INVALID_PARAMS`.

**Обход.** Первую страницу запрашивайте с `lastDate` — моментом, до которого копия актуальна. Пока `navigationData.hasMore` равен `true`, запрашивайте следующую с `lastDate`, равным `navigationData.lastServerDate`, и `lastId`, равным `navigationData.lastId`. Пару `lastServerDate` и `lastId` последней страницы сохраните — с неё начнётся следующая синхронизация.

## Примеры

### curl — личный ключ

```bash
curl "https://vibecode.bitrix24.tech/v1/chats/sync?lastDate=2026-09-24T10:00:00Z&limit=200" \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth-приложение

```bash
curl "https://vibecode.bitrix24.tech/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 — личный ключ

```javascript
let lastDate = '2026-09-24T10:00:00Z'
let lastId
const added = new Set()
for (;;) {
  const url = new URL('https://vibecode.bitrix24.tech/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-приложение

```javascript
let lastDate = '2026-09-24T10:00:00Z'
let lastId
const added = new Set()
for (;;) {
  const url = new URL('https://vibecode.bitrix24.tech/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
}
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|---------|
| `success` | boolean | Всегда `true` при успехе |
| `data.chatSync.addedRecent` | object | Чаты, появившиеся в списке диалогов: ключ и значение — ID чата |
| `data.chatSync.addedChats` | object | Чаты, которые появились или изменились у пользователя: ключ и значение — ID чата |
| `data.chatSync.deletedChats` | object | Чаты, убранные из списка диалогов пользователя: ключ и значение — ID чата |
| `data.chatSync.completeDeletedChats` | object | Чаты, удалённые с портала: ключ и значение — ID чата |
| `data.chatSync.readAllChats` | boolean | Приходит со значением `true`, если за это время пользователь [прочитал все чаты](/docs/chats/management/read-all): сбросьте счётчики непрочитанного в локальной копии |
| `data.messageSync.addedMessages` | object | Новые сообщения: ключ и значение — ID сообщения |
| `data.messageSync.updatedMessages` | object | Изменённые сообщения: ключ и значение — ID сообщения |
| `data.messageSync.completeDeletedMessages` | object | Удалённые сообщения: ключ и значение — ID сообщения |
| `data.pinSync.addedPins` | object | Новые закреплённые сообщения: ключ и значение — ID закрепа |
| `data.pinSync.deletedPins` | object | Снятые закрепы сообщений: ключ и значение — ID закрепа |
| `data.userSync` | object | Изменения пользователей: `updatedUsers` и `deletedUsers` |
| `data.chats` | array | Карточки чатов из `chatSync` |
| `data.messages` | array | Сообщения из `messageSync` |
| `data.pins` | array | Закрепы сообщений из `pinSync`: `id`, `messageId`, `chatId`, `authorId`, `dateCreate` |
| `data.recentItems` | array | Строки списка диалогов для чатов из `chatSync` |
| `data.users` | array | Пользователи, упомянутые в изменениях |
| `data.dialogIds` | object | Соответствие ID чата и ID диалога: ключ — ID чата, значение — `dialogId` |
| `data.navigationData.lastServerDate` | string | `lastDate` для следующей страницы — момент последней записи журнала на этой странице |
| `data.navigationData.lastId` | number | `lastId` для следующей страницы |
| `data.navigationData.hasMore` | boolean | `true` — есть следующая страница |
| `meta.requestedLimit` | number | Переданный `limit`. Приходит вместе с `appliedLimit`, только когда значение вышло за диапазон от 1 до 200 |
| `meta.appliedLimit` | number | Применённый `limit` — только если он был приведён к границе |

## Пример ответа

Показаны основные поля:

```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": "Черновик плана готов"
      }
    ],
    "chats": [
      {
        "id": 264431,
        "dialogId": "chat264431",
        "name": "Подготовка релиза",
        "type": "chat"
      }
    ],
    "dialogIds": { "264431": "chat264431" },
    "navigationData": {
      "lastServerDate": "2026-09-24T21:10:22+00:00",
      "hasMore": false,
      "lastId": 182091
    }
  }
}
```

## Пример ответа при ошибке

400 — дата без смещения:

```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."
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 400 | `INVALID_PARAMS` | Нет `lastDate`, `lastDate` не в формате `YYYY-MM-DDTHH:MM:SS` со смещением или `Z` либо с несуществующей датой или временем (`2026-02-30`, `24:00`), `lastId` меньше `0`, нечисловой `limit`, параметр вне `lastDate`, `lastId`, `limit` или повтор параметра. Проверяется до обращения к Битрикс24 |
| 422 | `BITRIX_ERROR` | Битрикс24 вернул ошибку, код портала — в `error.b24Code` |
| 403 | `BITRIX_ACCESS_DENIED` | Битрикс24 отказал в доступе |
| 403 | `SCOPE_DENIED` | API-ключ не имеет скоупа `im` |
| 401 | `TOKEN_MISSING` | API-ключ не имеет настроенных токенов Битрикс24 |
| 502 | `BITRIX_UNAVAILABLE` | Битрикс24 недоступен или вернул ошибку сервера |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

**Пустая коллекция приходит массивом.** Когда в группе изменений нет записей, вместо объекта приходит пустой массив: `"updatedMessages": []`. Читайте такие поля через `Object.values`, он одинаково работает с обоими видами.

**Инкрементальный журнал не гарантирует четыре недели истории.** Часть записей Битрикс24 хранится лишь около суток. После долгого перерыва, особенно близкого к суткам, перечитайте [последние диалоги](/docs/chats/discovery/recent) и нужную историю сообщений целиком, прежде чем доверять новой дельте.

**Синхронизация может быть выключена на портале.** Тогда `data` — пустой массив, а не объект с полями выше. В этом случае перечитывайте список целиком.

**Это не события в реальном времени.** Эндпоинт отвечает на вопрос «что изменилось с момента», а новые сообщения по мере появления приходят через [опрос событий](/docs/chats/events/poll).

## Смотрите также

- [Последние диалоги](/docs/chats/discovery/recent)
- [Получить события](/docs/chats/events/poll)
- [Поиск чатов](/docs/chats/discovery)
