Тема
Bitrix24 REST — уроки для API и интеграций
Разбор архитектуры Bitrix24 REST API по официальной документации (b24-rest-docs, 2657 страниц, apidocs.bitrix24.ru). Цель — найти пробелы в наших спеках API-контракта, интеграций и обмена данными, которые Bitrix закрыл за 20 лет эксплуатации. Особо ценен REST v3 — их собственная «работа над ошибками» v1/v2.
Статус планирования (2026-07-14)
Все пункты разнесены по волнам ранбука TZ.md (репозиторий cms.rosveb.ru) и по фазам roadmap; API-конвенции подняты в стандарт студии — новая спека specs/api.md.
| Урок | Куда запланирован |
|---|---|
§1 Runtime-схема API, §2 опциональный total, §3 batch, §5 анти-эхо origin, rate-limit на токен | Волна 24 (новая) — до первой внешней интеграции |
| §4 Scope токенов (Sanctum abilities), §8 канон имён полей | Волна 21 (роли и безопасность) |
| §6 Компакция очереди (остатки/цены) | Фаза 3 — ТЗ cms/integrations-bus |
| §7 Thin/full payload вебхуков | Фаза 2–3 — ТЗ cms/webhooks-out |
| Operating-бюджет, admin-confirmation, placement | Фаза 5 (маркетплейс) |
| Уроки для docs-site (шаблон метода, MCP, llms.txt) | Вместе с dx-claude-code |
Примечание: §2 частично закрыт реализацией раньше спеки — волна 9 сразу сделала cursorPaginate + meta.next_cursor; остался только опциональный total.
Что у нас уже решено не хуже
| Механизм Bitrix | Наш аналог | Статус |
|---|---|---|
Конверт result/error/total/next | {data, meta, links} / {error:{code,message,fields}} | ✅ совпадает с их v3 |
Структурированные ошибки v3 (validation[]) | error.fields | ✅ |
select/filter/order списочных методов | spatie/query-builder (whitelisted) + sparse fieldsets | ✅ наш вариант безопаснее |
| Online-события без ретраев, без гарантий | cms/webhooks-out: HMAC + timestamp, ретраи с backoff, dead-letter, журнал | ✅ мы сильнее |
| EAV инфоблоков | JSONB + GIN + generated-колонки | ✅ отвергнуто осознанно (решения) |
feature.get (гейтинг по тарифу) | Laravel Pennant | ✅ |
Токен в query-параметре auth | Sanctum, заголовок Authorization | ✅ их вариант — антипаттерн |
Пробелы — что мы не продумали
1. OpenAPI не покроет runtime-типы контента ⚠️ самый серьёзный
Наш контракт: OpenAPI генерируется из кода (Scramble) в CI. Но типы контента и их поля у нас создаются клиентом в рантайме (JSONB-схемы, движок полей) — статическая генерация из кода их физически не увидит. SDK и песочница будут показывать пустоту именно там, где у клиента живут его сущности.
Bitrix решает двумя механизмами: метод {entity}.fields (machine-readable схема полей каждой сущности) и в v3 — rest.documentation.openapi, отдающий OpenAPI из самого API.
Решение: к статическому OpenAPI из CI добавить runtime-эндпоинт GET /api/v1/schema (и GET /api/v1/content-types/{type}/fields), который мёржит статическую спеку со схемами типов контента из FieldTypeRegistry. Движок полей уже умеет выдавать JSON-схему из декларации — нужен только эндпоинт. Дописать в phase0-api-contract.
2. Пагинация больших каталогов — нет keyset и отключения count
У Bitrix страница всегда 50, но два выстраданных механизма: start=-1 отключает SELECT COUNT(*) по фильтру, а полный обход делается keyset-пагинацией (filter[>ID]=last_id). Их замер: на 2.4 млн записей запрос падает с 49.9 с до 0.097 с.
Наш контракт описывает пагинацию через query-builder — по умолчанию это offset + count на каждую страницу. Для каталога в 100k+ SKU и обхода из 1С/маркетплейсов это станет узким местом.
Решение: зафиксировать в контракте: cursorPaginate() (в Laravel уже есть) как рекомендованный режим для обхода; meta.total — опциональный (по флагу запроса, а не всегда); в BFF/интеграционных выборках count запрещён линтером.
3. Batch — не решён вообще
В контракте ядра batch-эндпоинта нет — он упомянут только как свойство GraphQL-модуля. Это дыра для зоны 1 (острова), мобильных и интеграций: у Bitrix batch (до 50 команд, подстановка $result[...] между командами, halt, весь пакет = 1 хит rate-limiter'а) — самый используемый метод API вообще.
Решение — золотая середина: не клонировать их $result-подстановки (url-кодированный ад, в v3 они сами его переписали). Достаточно POST /api/v1/batch — массив независимых запросов, флаг halt, лимит ~25, весь пакет = 1 хит лимитера. Зависимые цепочки — не нужны: для них есть GraphQL-модуль и композитные BFF-эндпоинты. Записать решение (даже если «отложено до фазы N») в phase0-api-contract.
4. Scope-модель токенов — API-токен не должен равняться пользователю
Наш контракт: «API не имеет своей модели прав, использует Policy ядра». Этого мало: интеграционный токен должен уметь быть у́же прав владельца. Клиент, выдающий токен маркетплейс-синхронизатору, не должен отдавать ему право удалять страницы.
У Bitrix двухуровневая проверка: scope приложения × права пользователя, плюс admin-confirmation для чувствительных методов (401 METHOD_CONFIRM_WAITING до одобрения администратором).
Решение: Sanctum token abilities как обязательный слой — токен создаётся со scope-списком (catalog:read, leads:write…), итоговое право = ability ∩ Policy. Конвенцию имён scope и декларацию scope эндпоинтов в манифесте модуля — в контракт модуля. Admin-confirmation — идея на фазу 5 (маркетплейс), сейчас не нужна.
5. Анти-эхо в двусторонних синхронизациях
Сайт↔CRM↔1С — двусторонние обмены. Классический цикл: CRM меняет сделку → вебхук на сайт → сайт обновляет запись → событие → вебхук обратно в CRM → … У нас в data-exchange есть идемпотентность по event-id, но она ловит повтор того же события, а не эхо-цикл (каждый круг порождает новое событие).
Bitrix решает параметром auth_connector: источник изменения помечает свои вызовы ключом, и события, порождённые этим же коннектором, в его очередь не попадают.
Решение: поле origin (источник изменения) в payload канонических событий ядра и в контексте мутаций API; правило для webhooks-out — «не доставлять подписчику события, у которых origin = этот подписчик». Дописать в three-axes (payload событий) и в спеку cms/webhooks-out.
6. Компакция очереди для массовых синков
Offline-события Bitrix хранят не журнал, а последнее состояние: 1000 изменений одной сделки = 1 запись в очереди. Плюс двухфазный забор (process_id → confirm/error) для конкурентных консьюмеров.
Наш поток «остатки/цены → маркетплейсы» через классические вебхуки/outbox будет слать шквал устаревших состояний: при массовом пересчёте цен 50k товаров подписчик получит 50k доставок, из которых актуальна последняя по каждому товару.
Решение: в cms/integrations-bus — режим «compacted queue» (uniq по entity_type + entity_id, доставляется последнее состояние) для событий остатков/цен. Для лидов/заказов — обычный журнальный режим. Дописать в integrations.
7. Тонкие vs полные payload вебхуков
Bitrix шлёт «тонкие» события (только ID, подписчик дозапрашивает данные): payload не устаревает, ничего лишнего не утекает, semver payload'а сводится к semver API. Мы (spatie/webhook-server) по умолчанию шлём полный payload — удобнее подписчику, но данные едут наружу и версия payload'а становится отдельным контрактом.
Решение: режим на подписку: thin (id + тип + origin) по умолчанию для внешних подписчиков, full — осознанный выбор. Дописать в спеку cms/webhooks-out.
8. Канонические имена кастомных полей — зафиксировать до кода
Поучительная боль Bitrix: поле в БД UF_CRM_10_5186744711, в REST — ufCrm10_5186744711, преобразование неоднозначно, годы костылей и флаг useOriginalUfNames спустя 15 лет.
Решение: один канонический формат имени поля (snake_case, [a-z0-9_]{1,50}, без преобразований регистра между слоями) — как поле хранится в JSONB, так оно и называется в API, в Blade-токенах и в Filament. Валидация имени — в движке полей при регистрации. Дописать в phase0-field-engine.
Стоит рассмотреть (не пробел, но идея)
- Operating-бюджет вместо голого rate-limit. Bitrix лимитирует не число запросов, а накопленную стоимость:
time.operatingв каждом ответе, 480 с исполнения на пару «метод × приложение» за 10 минут → 429. Для нас на старте избыточно, но два элемента дёшевы уже сейчас: rate-limit на токен, а не на IP (несколько интеграций за одним NAT не должны делить лимит) и стандартные заголовкиRateLimit-*в ответе. - Placement как паттерн для маркетплейса (фаза 5). Именованные слоты UI + iframe- изоляция стороннего интерфейса с автопередачей контекста — способ пустить чужой UI в админку, не давая ему прав Filament-плагина. Согласуется с capability-слоем.
cms:hooksуже лучше, чем у Bitrix: их методeventsотдаёт список событий по scope — наш реестр точек расширения задуман шире (события + фильтры + payload). Довести до конца, включая machine-readable вывод (--json) для SDK и Claude Code.
Что сознательно не берём
- RPC-имена методов (
crm.deal.list) — остаёмся на REST-ресурсах; их v3 сам дрейфует к нашей модели. - Токен в query-параметре — только заголовок Authorization (их
authв URL оседает в логах и рефererrах). - Секрет вебхука в URL — у нас HMAC-подпись тела + timestamp.
- Двойное URL-кодирование batch — признано ошибкой в их же v3.
- Фиксированную страницу в 50 записей без выбора размера.
- Online-события без ретраев — наш webhooks-out с backoff и dead-letter сильнее.
- EAV — уже отвергнут (content-model).
Уроки для нашего docs-site
Репозиторий b24-rest-docs — эталон docs-as-code для API (Diplodoc/YFM, 2657 md):
- Жёсткий шаблон страницы метода: scope и «кто может выполнять» → параметры → примеры в табах (cURL / JS SDK / PHP SDK) → ответ → таблица ошибок → «продолжить изучение». Повторяющиеся куски — include-фрагменты. Взять как шаблон для страниц нашего
/api/v1(связка с dx-claude-code). - AI-friendly выдача: кнопка «скопировать страницу как Markdown», MCP-сервер к документации, фронтматтер под LLM. Для нас:
llms.txt+ Markdown-выдача VitePress + наш MCP-модуль (specs/mcp) поверх той же документации. toc.yamlна раздел (у них 437 штук) вместо одного гигантского — у нас аналог уже есть (sidebar по разделам в config.mjs).