CRM API: синхронизация
Cursor-пагинация
Обычные списки используют непрозрачный подписанный cursor:
GET /api/v1/crm/contacts?limit=50
GET /api/v1/crm/contacts?limit=50&cursor=<nextCursor>- стандартный
limit— 50, максимум — 100; - исключение —
/search: 20 по умолчанию, максимум 50; - следующую позицию берите только из
meta.nextCursor; - не разбирайте и не изменяйте cursor;
- сохраняйте сортировку и фильтры между страницами: cursor связан с tenant и набором фильтров.
Подмена cursor, его использование в другой организации или с другим фильтром приводит к
400 invalid_cursor.
externalId и upsert
externalId связывает объект APX Chat с записью внешней системы. Значение уникально в пространстве
(tenant, applicationId, resourceType, externalId): разные API-приложения могут использовать свои
идентификаторы независимо.
Для contacts, companies, deals и tasks действуют правила:
- если соответствия ещё нет, upsert создаёт объект без
expectedVersion; - если соответствие уже есть, требуется его текущий
expectedVersion; - отсутствие версии при обновлении возвращает
428 precondition_required; - несовпадение версии возвращает
409 version_conflictсdetails.currentVersion; - передача
expectedVersionпри создании нового соответствия возвращает422 validation_error; - повторное использование
externalIdудалённого объекта возвращает410 external_reference_deleted.
Храните вместе externalId, UUID APX Chat и последнюю известную version. Не используйте имя,
телефон или email как технический ключ синхронизации.
CAS и конкурентные изменения
Изменяемые ресурсы содержат числовую version. Для PATCH, DELETE, move, archive и restore
передавайте её как expectedVersion в теле или query — точное место указано в OpenAPI.
Опции дополнительных полей требуют сразу expectedVersion опции и
expectedDefinitionVersion определения. Assignments используют отдельную версию набора
customFieldsVersion; настройки до первого сохранения используют версию 0.
При 409 version_conflict перечитайте ресурс, объедините изменения и повторите запрос с новой
версией. Не подставляйте новую версию автоматически: это может затереть параллельные изменения.
Отсутствующее обязательное поле в обычном DTO даёт 400 validation_error; специальный
428 precondition_required используется для upsert существующего externalId.
Идемпотентность mutation-запросов
Каждый POST, PATCH и DELETE из mutation-строк таблиц требует заголовок длиной 1–200 символов:
Idempotency-Key: 9b897d29-f43f-4e9a-a1e1-ef9e74ad3fe3Ключ хранится 24 часа и изолирован по applicationId. Повтор того же method, path и JSON body
возвращает сохранённый статус и тело с Idempotency-Replayed: true. Тот же ключ с другим запросом
даёт 409 idempotency_conflict, а параллельный незавершённый запрос —
409 idempotency_in_progress. Отсутствующий или слишком длинный ключ даёт
422 validation_error.
После сетевого таймаута повторяйте ту же бизнес-операцию с тем же ключом. Для новой операции создавайте новый ключ.
Change feed
| Метод | Путь | Scope | Назначение |
|---|---|---|---|
GET | /changes/cursor | crm_changes_read | Текущая позиция ленты |
GET | /changes | crm_changes_read | Изменения после позиции |
Надёжный цикл синхронизации:
- Перед первоначальной загрузкой получите и сохраните позицию через
GET /changes/cursor. - Выполните полную загрузку нужных списков через их cursor-пагинацию.
- Читайте
GET /changes?after=<сохранённый cursor>&limit=50&resourceTypes=contact,company,deal. - Зафиксируйте страницу у себя и только после этого сохраните непустой
meta.nextCursor. - Продолжайте, пока
meta.hasMoreне станетfalse; при следующем poll используйте последнюю сохранённую позицию.
Лента возвращает только типы, для которых у приложения есть ресурсный read-scope. События идут по
возрастающей sequence, могут быть обработаны повторно после сбоя клиента и должны применяться
идемпотентно. Cursor и события хранятся 90 дней. Просроченная позиция возвращает
410 cursor_expired: выполните полную сверку и начните с нового cursor.
CRM webhooks
Подписка создаётся только в кабинете: Настройки → Webhooks. Публичного CRM-маршрута для её
управления нет. Выберите событие crm_change, HTTPS URL, HMAC secret и стабильное API-приложение.
У приложения должны быть crm_changes_read и read-scope конкретного ресурса. Изменение или отзыв
scopes до доставки применяется fail-closed.
У CRM webhook уже в payload v1 используется structured envelope. При выборе payload v2 форма и
проекция data сохраняются, но apiVersion становится "v2". Secret обязателен для обеих версий.
Тело события:
{
"id": "811793c8-d361-487b-b3f7-1d59b3fe3fc6",
"type": "crm.deal.updated",
"apiVersion": "v1",
"occurredAt": "2026-08-16T10:30:00.000Z",
"data": {
"resourceType": "deal",
"resourceId": "302c748c-04bd-4b15-bf12-e4c9c6767189",
"externalId": "order-1942",
"action": "updated",
"version": 8,
"changedFields": ["amount", "stageId"]
}
}data.action принимает created, updated, deleted, merged, anonymized, completed или
failed. resourceId и version могут быть null. externalId присутствует только если это
приложение создало соответствующий mapping.
Заголовки доставки:
X-APX-Event-Id: 811793c8-d361-487b-b3f7-1d59b3fe3fc6
X-APX-Event-Timestamp: 1786876200
X-APX-Signature: v1=<hex hmac-sha256>Подписывается точная строка <timestamp>.<rawBody>. Проверяйте исходные байты до JSON parsing,
допустимое отклонение timestamp и подпись сравнением с постоянным временем. Повторные попытки
сохраняют тот же event ID; доставка выполняется до 5 раз с экспоненциальной задержкой от 60 секунд.
Дедуплицируйте по X-APX-Event-Id, а пропуски восстанавливайте через change feed.