Пагинация, фильтры и синхронизация
Параметры выборки
Обычные списки используют filter[имя]=значение, sort, page[size], page[cursor]. Допустимый набор свой у каждого метода. Например:
curl --fail-with-body --silent --show-error --globoff --get \
--header "Authorization: Bearer $SERVICEAPP_TOKEN" \
--data-urlencode 'filter[q]=Демонстрационный' \
--data-urlencode 'page[size]=50' \
--data-urlencode 'sort=id' \
"$API/customers" | jq .
Массивы, когда они разрешены, передаются повторяемыми параметрами с []: filter[equipment_ids][]=101&filter[equipment_ids][]=102. Строка со значениями через запятую не заменяет массив. Булевы значения параметров запроса используйте как true/false. По умолчанию страница содержит 50 записей, допускается 1–100. q ограничен 200 символами. Обычно массив ограничен 100 уникальными ID; точный лимит указан у операции.
У GET-отчётов /maintenance-reports/* параметры передаются на верхнем уровне: from, to, equipment_ids[], workshop_ids[], customer_id и дополнительные параметры конкретного отчёта. Эти отчёты не поддерживают filter, sort и page. Некоторые специальные GET возвращают агрегат или сохранённый список внутри data; они используют собственную схему, а не обязательную пагинацию.
Сортировка обычного списка — id или -id. Только явно указанные ресурсы дополнительно поддерживают updated_at,id / -updated_at,-id. У остатков ключом является product_id внутри склада. include, произвольная сортировка, offset и номер страницы не входят в контракт. Неизвестный параметр даёт 422 UNKNOWN_QUERY_PARAMETER, неверное значение — VALIDATION_FAILED.
Обход страниц
У курсорного списка есть data, links={self,next,prev}, meta={per_page,has_more,next_cursor,server_time}. Пустой список — 200 с data=[]. Родительские карточки внутри data могут содержать свои links.edit/portal/public; они не связаны с внешними links пагинации.
Переходите по относительному links.next на том же домене организации. Завершайте обход при has_more=false, next_cursor=null и next=null. Не разбирайте и не изменяйте содержимое курсора. Он привязан к подключению, URL родителя, фильтрам, порядку, размеру страницы и условиям доступа.
# NEXT_PATH берётся ровно из links.next предыдущей страницы.
export NEXT_PATH='<RELATIVE_LINKS_NEXT>'
curl --fail-with-body --silent --show-error --globoff \
--header "Authorization: Bearer $SERVICEAPP_TOKEN" \
"$SERVICEAPP_ORIGIN$NEXT_PATH" | jq .
Перед следованием ссылке проверьте, что это относительный путь /api/v1/ без другого домена. Повреждённый/несовместимый курсор — 422 INVALID_CURSOR; после 24 часов — 409 CURSOR_EXPIRED. После смены доступа начните обход заново. Курсор не сохраняет право читать ранее доступные записи.
Периоды
Отчёт требует обе границы from/to, максимум 366 календарных дней. DATE-границы включительны и не меняют день из-за часового пояса. Временные интервалы используют нижнюю включительную и верхнюю исключительную границу. Конкретное применение к календарным дням и временным событиям описано у ресурса.
У журнала API-операций период по умолчанию — последние 30 дней, у доступности сотрудников — ближайшие 30 дней. Ответ сообщает применённый период. Для отчётов периода по умолчанию нет.
Выборка изменённых записей
Только ресурсы, у которых перечислены filter[updated_since] и filter[updated_before], поддерживают такую выборку. Нижняя граница включительна (>=), верхняя исключительна (<). Само наличие поля updated_at в ответе не означает наличие этих фильтров.
curl --fail-with-body --silent --show-error --globoff --get \
--header "Authorization: Bearer $SERVICEAPP_TOKEN" \
--data-urlencode 'filter[updated_since]=2026-11-04T08:55:00Z' \
--data-urlencode 'filter[updated_before]=2026-11-04T09:00:00Z' \
--data-urlencode 'sort=updated_at,id' \
--data-urlencode 'page[size]=100' \
"$API/customers" | jq .
updated_at имеет секундную точность и не является моментом завершения транзакции. Повторное состояние с той же версией нужно применять, если данные изменились. Ни максимальная дата выгрузки, ни meta.server_time не доказывают полноту всех изменений до этой даты. Полные ограничения.
Рекомендуемая процедура синхронизации
- До полной выгрузки включите нужные уведомления о событиях и начните сохранять в постоянном хранилище event_id. Запомните
server_timeпервой страницы и выгрузите доступные записи поid, следуя next. - Принимайте событие как повод перечитать актуальную карточку. Дедуплицируйте event_id, выполняйте чтения последовательно для одного ресурса. Не заменяйте новый GET старым телом события; событие содержит ссылки на ресурс, а не полную карточку.
- Для ресурсов с updated_since выбирайте интервал от прошлого маркера минус перекрытие до
server_timeначала нового прохода. Начальное рекомендуемое перекрытие — 5 минут. Обходите все страницы сsort=updated_at,idи повторно применяйте состояние по(type,id), даже при прежней версии. Продвигайте маркер только после сохранения всех страниц; при сбое повторите весь интервал. - Обрабатывайте удаление, архивирование и восстановление через события и разрешённые архивные выборки. Для customers
filter[deleted]=trueозначает только удалённых; требуютсяcustomers:read,customers:deleteи оба права сотрудника. Для products/operationsfilter[archived]=trueвыбирает архивные записи и требует соответствующего права удаления. Неудалённые записи выбираются без фильтра или с false. - Делайте полную сверку доступных ID не реже раза в сутки, а также после потери событий, смены доступа и истечения курсора. Пропавший ID перепроверьте отдельным GET. 404 означает текущее отсутствие доступа или записи; это не доказательство физического удаления и не основание безусловно стирать данные внешней системы.
Эта процедура обеспечивает сходимость к доступному текущему состоянию. Она не является транзакционным снимком или журналом каждого промежуточного значения. Долгая транзакция может выйти за интервал перекрытия; нужны события и полная сверка. Глобальный порядок событий не гарантируется. Позднее deleted-событие не должно стирать уже восстановленную карточку без проверки её текущего состояния.