Пагинация
API Пачки использует cursor-based пагинацию для всех методов, возвращающих списки. Это обеспечивает стабильную работу при добавлении и удалении записей.
Две группы методов с пагинацией
В API существует две группы методов, возвращающих разную структуру meta — алгоритмы обхода и условия конца данных у них отличаются. Перед интеграцией убедитесь, что вы знаете, к какой группе относится используемый метод.
- Списочные методы — возвращают
meta.paginateс четырьмя полями:next_page,prev_page,has_next,has_prev. - Методы поиска — возвращают
metaс полемtotalиpaginate.next_page(только курсор «вперёд», безprev_page/has_next/has_prev). Отдельно стоит GETСписок сотрудников с заданным параметромquery: в егоmeta.paginateесть толькоnext_pageиhas_next.
Параметры запроса
| Параметр | Тип | Описание |
|---|---|---|
limit | integer | Количество записей на странице. Допустимые значения зависят от метода: большинство списочных методов — 1–50, GETСписок прочитавших сообщение — до 300, GETПоиск чатов — до 100, GETПоиск сотрудников и GETПоиск сообщений — до 200. Целое число вне допустимых значений заменяется ближайшей границей, а любое другое значение возвращает 400 с кодом invalid и limit в key. |
cursor | string | Курсор для получения страницы (значение meta.paginate.next_page или meta.paginate.prev_page из предыдущего ответа) |
Списочные методы
Списочные методы возвращают объект meta.paginate с четырьмя полями:
| Поле | Тип | Описание |
|---|---|---|
next_page | string | Курсор на следующую страницу (вперёд по сортировке). Используйте для обхода всех записей. |
prev_page | string | Курсор на предыдущую страницу (назад по сортировке). Используйте для polling новых записей «сверху» списка. |
has_next | boolean | Есть ли ещё данные на следующей странице. На последней странице — false. |
has_prev | boolean | Есть ли ещё данные на предыдущей странице. На первом запросе без курсора — false. |
К этой группе относятся: GETСписок сотрудников (без query), GETСписок чатов, GETСписок участников чата, GETСписок сообщений чата, GETСписок реакций, GETСписок прочитавших сообщение, GETСписок тегов, GETСписок сотрудников тега, GETСписок напоминаний, GETЖурнал аудита событий, GETИстория событий.
next_page, prev_page, has_next, has_prev всегда присутствуют в ответе — даже когда data пустой. Курсоры никогда не бывают null. Признак конца данных — has_next: false (вперёд) или has_prev: false (назад), а не пустой data. Количество записей в data может быть меньше limit и на промежуточных страницах — не полагайтесь на длину массива. Курсор — непрозрачный токен: не парсите, не конструируйте вручную и не сохраняйте между сессиями. Всегда явно указывайте limit — не полагайтесь на значение по умолчанию.Сценарии и состояния meta.paginate
Поведение полей meta.paginate для типичных сценариев пагинации (на примере метода GETСписок чатов с DESC-сортировкой):
| Сценарий | Запрос | data | next_page | prev_page | has_next | has_prev |
|---|---|---|---|---|---|---|
| Первый запрос, есть данные | cursor=null, limit=50 | 50 записей | курсор от records.last | курсор от records.first | true | false |
| Первый запрос, БД пуста | cursor=null, limit=50 | 0 записей | пустой курсор (для polling) | пустой курсор (для polling) | false | false |
| Идём вперёд по страницам | cursor=<next_page> | 50 записей | новый forward-курсор | от records.first | true | true |
| Дошли до последней страницы | cursor=<next_page> | менее 50 записей | от records.last | от records.first | false | true |
| Polling старого хвоста (новых нет) | cursor=<next_page последней страницы> | 0 записей | тот же next_page (для следующего polling) | валидный backward-курсор | false | true |
Polling новых через prev_page (новых нет) | cursor=<prev_page> | 0 записей | валидный forward-курсор | тот же prev_page (для следующего polling) | true | false |
Polling новых через prev_page (пришло меньше limit) | cursor=<prev_page> | 3 записи | новый forward-курсор | от records.first | false | false |
Polling новых через prev_page (пришло ≥ limit) | cursor=<prev_page> | 50 записей | новый forward-курсор | от records.first | false | true (продолжайте polling) |
Курсоры next_page и prev_page остаются валидными даже на пустом ответе — можно безопасно повторять запрос с тем же курсором, не теряя точку отсчёта.
Методы поиска
Методы поиска используют отдельный формат meta — без prev_page и без флагов has_next/has_prev. Вместо них возвращается total — общее количество найденных результатов:
| Поле | Тип | Описание |
|---|---|---|
total | integer | Общее количество найденных результатов по запросу. |
paginate.next_page | string | Курсор на следующую страницу. Доступен только курсор «вперёд», без prev_page. |
К этой группе относятся:
prev_page, has_next, has_prev — поэтому polling новых результатов через prev_page здесь не работает. Конец данных определяйте по совпадению числа уже полученных записей с total или по пустому data в очередном ответе. Курсор next_page — непрозрачный токен, всегда указывайте limit явно.Список сотрудников с параметром query
GETСписок сотрудников с заданным параметром query ищет по сотрудникам и возвращает meta.paginate только с двумя полями: next_page и has_next. Полей prev_page, has_prev и total в ответе нет. Курсор next_page приходит и на последней странице, поэтому конец списка — has_next: false, как у списочных методов. Без query ответ соответствует группе «Списочные методы» с полным meta.paginate.
Обход всех записей
Передайте next_page в параметр cursor следующего запроса. Условие остановки зависит от группы метода: для списочных — has_next: false, для методов поиска — равенство числа полученных записей значению total.
Для списочных методов
Для методов поиска
Polling новых данных
Курсор prev_page доступен только в списочных методах и позволяет получать записи, появившиеся «перед» первой страницей, без полного рефетча. Это полезно для отслеживания новых сообщений в чате, новых чатов и других обновлений. В методах поиска prev_page отсутствует — там polling новых результатов через курсор не предусмотрен.
Алгоритм
- Выполните первый запрос без курсора и сохраните
prev_pageиз ответа. - Периодически делайте запрос с
cursor=prev_page. - Если
dataне пуст — появились новые записи. Обновитеprev_pageиз ответа. - Если
has_prevравенtrue— новых записей больше, чемlimit. Продолжайте запрашивать поprev_page, покаhas_prevне станетfalse.
Пока новых записей нет, ответ возвращает пустой data и тот же prev_page — можно безопасно повторять запрос с тем же курсором, не теряя точку отсчёта.
c1 < c2 < ... < c8 при сортировке DESC, первый запрос с prev_page вернёт ближайшие к anchor: [c5, c4, c3, c2, c1] (5 записей в порядке сортировки), а следующий запрос по обновлённому prev_page — оставшиеся [c8, c7, c6]. Внутри batch'а порядок корректный, но между batch'ами клиенту нужно мерджить с учётом сортировки.id. Если у записи на границе курсора обновится поле сортировки (например, last_message_at у первого чата из исходной выдачи), при polling по prev_page она вернётся как «новая» — у неё теперь больший last_message_at, чем зафиксированный в anchor курсора. Это не баг, а корректное поведение: бэкенд сообщает «эта запись изменила позицию», UI должен обновить превью/таймстамп и перерасставить элемент в списке. Всегда дедуплицируйте polling-результаты по id при мердже с уже отрендеренным списком.