Для AI-агентов: markdown этой страницы — /docs-content/changelog.md индекс документации — /llms.txt

Журнал изменений API Вайбкод

История изменений API Вайбкод: новые возможности, исправления и изменения с потерей обратной совместимости. Записи расположены от новых к старым.

Префиксы записей

  • NEW — новая возможность: новый эндпоинт, новое необязательное поле или параметр, новый код ошибки в новом сценарии. Прежние запросы клиентов продолжают работать.
  • FIX — исправление поведения. Ответ меняется на корректный, действий со стороны клиента не требуется.
  • BC — изменение с потерей обратной совместимости. Требует действий со стороны клиента. Старый формат поддерживается указанный срок, затем прекращается.

Формат кода записи: {ТИП}-{ММДД}-{N}, где ММДД — дата публикации, N — номер записи внутри этой даты. Код остаётся постоянным идентификатором записи, а номера внутри даты могут идти с пропусками.

Часть записей NEW помечена в процессе раскатки: метод вышел в конкретном обновлении Битрикс24 и доступен не на всех порталах. Пока обновление не приехало на портал, вызов возвращает 422 METHOD_NOT_YET_AVAILABLE с целевой версией — это признак раскатки, а не ошибка интеграции.

2026-09-28

NEW-0928-1: результаты задач через старый REST

На платформе Вайбкод появились четыре маршрута для результатов задач: создание из старого комментария, список, первый и последний результат. Они вызывают методы tasks.Task.Result.* и возвращают результат Bitrix24 без изменения. Пустой список остаётся [], а отсутствие первого или последнего результата — { "result": 0 }. Создание из комментария относится только к старым комментариям форума; новая карточка задачи не обещана.

NEW-0928-2: результаты задач через REST 3.0

API Вайбкод добавляет четыре маршрута для создания результата задачи, создания из сообщения чата, обновления и удаления. Они вызывают методы REST 3.0 Битрикс24 и сохраняют их ответ внутри data. Для всех маршрутов нужен скоуп task или tasks и право на запись.

Затронутые эндпоинты: POST /v1/task-results, POST /v1/task-results/from-chat-message, PATCH /v1/task-results/:id, DELETE /v1/task-results/:id. Справка по результатам задач.

NEW-0928-3: Четыре метода шаблонов задач

Через API Вайбкод доступны чтение, список, добавление и изменение шаблонов задач. POST /v1/task-templates вызывает tasks.Template.add и возвращает ID. Список сохраняет форму task_templates и сведения о следующей странице.

NEW-0928-4: 12 операций Scrum доступны через именованные маршруты

Добавлены маршруты /v1/scrum/backlogs, удаление эпика и этапа, создание, изменение, удаление, запуск и завершение спринта, а также добавление и удаление задачи на доске. Каждый маршрут вызывает соответствующий метод Bitrix24 и возвращает его результат в data. Завершение активного спринта принимает ID группы. Прежние успешные ответы остаются успешными без изменения.

FIX-0928-5: создание пользовательского поля проверяет то значение, что уходит в Битрикс24

Было

POST /v1/userfields/users и POST /v1/items/{entityTypeId}/userfields (вместе с POST /v1/userfields/invoices) проверяли обязательные поля по первому написанию ключа в теле. Если в теле были оба написания — userTypeId и USER_TYPE_ID (для полей сотрудников также fieldName и FIELD_NAME), — проверку проходило одно значение, а в Битрикс24 уходило другое. Пустое значение доезжало до портала, и ответ приходил как 422 BITRIX_ERROR с b24Code: "0", по которому не понять, что не так с запросом.

Стало

Проверяется значение, которое реально уходит в Битрикс24: при двух написаниях одного ключа действует последнее в теле, как и в POST /v1/userfields/{entity}. Пустое или отсутствующее значение отклоняется до обращения к порталу ответом 400 MISSING_FIELD. Для смарт-процессов и счетов в Битрикс24 уходит один ключ userTypeId, даже если в теле передан USER_TYPE_ID. Запросы с одним написанием ключа работают как раньше.

FIX-0928-6: повторный `POST /v1/bots` для существующего бота больше не ломает ему токен

Было

Повторный POST /v1/bots с code уже зарегистрированного бота отвечал 201, но после этого бот переставал работать: вызовы /v1/bots/{botId}/* отбивались ошибкой авторизации, а входящие вебхуки бота не доходили. Портал оставлял боту прежние учётные данные, а у себя мы сохраняли новые, которые портал не принимал.

Стало

Если портал вернул тот же botId, сохраняются прежние учётные данные бота, и он продолжает работать. Ответ 201 и его форма не изменились.

BC-0928-7: отказ `agent_turn_loop_detected` для зациклившегося хода агента включается

Поддержка старого формата до: не предусмотрена

Было

Код отказа agent_turn_loop_detected (409) у POST /v1/chat/completions был объявлен, но не действовал: запрос, в котором после последнего сообщения с ролью user накопилось много ответов модели без tool_calls, обслуживался как обычно.

Стало

Отказ действует. Если в messages после последнего сообщения с ролью user накопилось 20 и более ответов модели с ролью assistant без tool_calls, запрос получает 409 с кодом agent_turn_loop_detected, обращение к модели не выполняется и лимит не расходуется. Порог поднят с объявленных ранее 15 до 20. Ответы с tool_calls в счёт по-прежнему не входят, поэтому ход, в котором модель на каждом шаге вызывает инструменты, условие не задевает. Окно поддержки прежнего поведения не предусмотрено: отказ защищает от холостой петли, которая без него расходует лимит до исчерпания.

Что делать интеграторам

Обычной интеграции ничего менять не нужно. Если клиент сам продолжает ход без нового сообщения пользователя, получив agent_turn_loop_detected, остановите ход и дождитесь сообщения пользователя: новое сообщение с ролью user начинает счёт заново.

NEW-0928-8: Доступны настройки Definition of Done и данные метрик спринта

API Вайбкод добавил четыре маршрута для чтения и сохранения настроек и списка Definition of Done, а также два маршрута для данных спринта: /v1/scrum/groups/:groupId/burn-down и /v1/scrum/groups/:groupId/team-speed. Они возвращают результат Битрикс24 в data без вычисления метрик на платформе. Маршрут получения списка DoD пока не опубликован: форма его успешного REST-ответа требует проверки на доступной группе с настроенным DoD.

NEW-0928-9: Потоки и задачи по статусам доступны через API

Появились девять маршрутов /v1/tasks/flows для создания, чтения, изменения и удаления потока, переключения его активности и получения отдельных списков выполненных, ожидающих и выполняемых задач. Маршруты вызывают соответствующие методы Bitrix24 и возвращают их результат в data. Для чтения подходит READONLY-ключ; для изменений нужен ключ с правом записи. Справка по потокам.

FIX-0928-10: ссылка на распределение мест в списке сотрудников Коворка ведёт в раздел «Сотрудники»

Было

Поле links.assign в ответе GET /v1/platform/cowork/members вело на /admin/cowork. Эта страница перенаправляет в обзор консоли компании, и администратор портала попадал не туда, где выдают места.

Стало

links.assign ведёт на /admin/company?ctab=people — раздел «Сотрудники» консоли компании, где администратор распределяет купленные места. Остальные ссылки блока links не изменились, ответ по-прежнему HTTP 200.

Влияние на интеграторов

Менять ничего не нужно: ссылку по-прежнему берите из ответа, а не собирайте сами.

FIX-0928-11: Публичное приложение сообщает о недоступности при запрете пробуждения

Было

Публичное приложение без туннеля при запрете пробуждения продолжало показывать ожидание запуска со статусом 503, хотя поднять его было нельзя.

Стало

Только для режима PUBLIC подтверждённый запрет пробуждения при отсутствии туннеля даёт 409: браузеру — «Приложение временно недоступно» с кнопкой «Обновить», машинному клиенту — BH_APP_UNAVAILABLE в JSON. Ответ не раскрывает причину, баланс или бренд подписки и не содержит Retry-After. Автоматическое ожидание прекращается, пробуждение не запускается. В приватном режиме для опознанного пользователя сохраняется 402 BH_WAKE_BLOCKED.

Влияние на интеграторов

Успешные запросы работают как прежде. Не повторяйте запрос с BH_APP_UNAVAILABLE автоматически. После снятия запрета приложение можно открыть вручную по прежнему адресу, POST автоматически не повторяется.

BC-0928-12: доступ к исходникам сервера привязан к ключу владельца

Поддержка старого формата до: не предусмотрена

Было

Второй личный ключ того же пользователя получал 404 на GET /v1/infra/servers/:id, но мог читать и менять исходники через серверные операции с исходниками. POST /v1/infra/servers/:id/sources/cleanup отвечал 200.

Стало

Второй личный ключ без привязки приложения к серверу получает 404 и на запросы к исходникам, в том числе если его владелец — администратор. Указатели в GET /v1/me/sources и GET /v1/apps/:id/sources больше не обещают ему доступ. Текущий ключ сервера и личный ключ его привязанного приложения сохраняют доступ; администратор по-прежнему может обращаться к серверам других пользователей.

Что делать интеграторам

Для запросов к исходникам своего сервера используйте его текущий управляющий ключ либо личный ключ приложения, привязанного к этому серверу.

FIX-0928-13: шаблоны, активити и роботы бизнес-процессов на портале без модуля отвечают 409 BIZPROC_MODULE_NOT_ENABLED

Было

На портале, где бизнес-процессы не входят в тариф или выключены в настройках, операции шаблонов бизнес-процессов, активити и роботов отвечали 404 ENTITY_NOT_FOUND, а на англоязычном портале — 422 BITRIX_ERROR. Такой ответ выглядел как неверный идентификатор, хотя дело было в состоянии портала. В пакетных вызовах POST /v1/batch и POST /v1/{entity}/batch тот же отказ приходил элементом с внутренним кодом Битрикс24 ERROR_METHOD_NOT_FOUND либо с общими кодами CALL_FAILED и AUTO_PAGINATION_FAILED. Разделы запущенных процессов на том же портале уже отвечали 409 BIZPROC_MODULE_NOT_ENABLED.

Стало

Все операции этих трёх сущностей, которые обращаются к Битрикс24, на портале без бизнес-процессов отвечают 409 BIZPROC_MODULE_NOT_ENABLED — так же, как раздел запущенных процессов. В пакетных вызовах ответ пакета остаётся HTTP 200, а отказавший элемент несёт код BIZPROC_MODULE_NOT_ENABLED с тем же сообщением. Сообщение называет вызванный метод Битрикс24, обе возможные причины и действие: попросить администратора портала включить бизнес-процессы. Остальные ошибки этих операций не изменились.

Влияние на интеграторов

Действий не требуется: отказ как был 4xx, так и остался, а прежние 404 ENTITY_NOT_FOUND и 422 BITRIX_ERROR для этого состояния документацией не обещались. Интеграция, которая в элементе пакетного вызова ловила внутренний код ERROR_METHOD_NOT_FOUND или общие коды CALL_FAILED и AUTO_PAGINATION_FAILED, увидит для этого случая собственный код BIZPROC_MODULE_NOT_ENABLED; интеграция, которая считает незнакомый код сбоем вызова, продолжит работать. Повторять запрос бесполезно, пока бизнес-процессы на портале недоступны.

NEW-0928-14: планы Коворка отдают блок promo — акцию с лимитом мест и цену остальных мест заказа

GET /v1/platform/cowork/plans отдаёт акцию с лимитом мест на заказ в форме, которую читает чекаут: блок promo в корне ответа и цены под срок в price.terms[].

Блок promo равен null, если у портала акции с лимитом мест нет. Иначе в нём:

  • code — какая акция действует: FIRST_PURCHASE, TEAM_VOLUME или PROMO
  • discountPercent — процент акции, только для подписи «−N %»
  • plans — планы, у сроков которых есть цена по акции
  • seatsLimit — сколько мест заказа идут по цене акции, то есть сколько мест портал ещё может по ней взять
  • rest — второй уровень: цена остальных мест заказа, не вошедших в seatsLimit, или null. В нём discountPercent — процент для подписи и plans — планы с ценой остальных мест

В сроках price.terms[] появились поля:

  • promo — vibesMonth и vibesTotal места по цене акции на этот срок
  • promoRest — vibesMonth и vibesTotal остального места заказа на этот срок

Цены посчитаны и округлены платформой, vibesTotal равен vibesMonth × months. Если у срока такой цены нет, поля в нём нет. Акции без лимита мест в блок не попадают: их цена уже в vibesMonth и vibesTotal.

Как считать заказ: места в пределах seatsLimit — по promo, начиная с места с наибольшей экономией за весь срок (vibesTotal − promo.vibesTotal), при равенстве — старший план, затем более длинный срок. Остальные места заказа — по promoRest, только если хотя бы одно место пошло по promo, иначе по vibesMonth и vibesTotal. Заказ без котировки зачисляется по тем же правилам на момент оплаты: место получает цену promoRest, только если место по цене promo в этом же заказе создано.

FIX-0928-15: пустые POST-запросы получают предусмотренный ответ валидации

Было

POST-запросы без тела и без Content-Type к перечисленным ниже эндпоинтам отвечали HTTP 500 вместо ответа о некорректном запросе.

Стало

Такие запросы получают уже предусмотренный HTTP 400. Для POST /v1/calls/register ответ на успешный запрос остаётся HTTP 201, а для POST /v1/connect/device/authorize ответ на успешный запрос остаётся HTTP 200.

Влияние на интеграторов

Изменения не требуются. Клиенты могут обрабатывать отсутствие обязательных параметров как ошибку запроса, а не как внутреннюю ошибку сервера.

Затронутые эндпоинты: POST /v1/calls/register, POST /v1/calls/:callId/show, POST /v1/calls/:callId/hide, POST /v1/calls/:callId/finish, POST /v1/calls/:callId/transcription, POST /v1/calls/auto-call, POST /v1/calls/auto-call-audio, POST /v1/calls/callback, POST /v1/connect/device/authorize.

FIX-0928-16: предупреждение о доступе приложения к порталу после деплоя

Было

Успешный ответ POST /v1/infra/servers/:id/deploy с OAuth-app-ключом не напоминал, что ключ запроса не переносится автоматически в окружение приложения, а доступ приложения к порталу не проверен.

Стало

Успешный ответ добавляет это пояснение в warnings: в JSON отдельной виртуальной машины и Galaxy-приложения, а в SSE — в событии done. Успех деплоя и прежние предупреждения сохраняются. Для личного ключа и при ошибке деплоя новое предупреждение не добавляется. Переменные окружения и ключи не меняются; предупреждение не утверждает, что у приложения отсутствуют учётные данные. Контракт приложения описывает настройку авторизации от имени пользователя и headless-сервиса.

Влияние на интеграторов

Обязательных изменений в клиентах API нет. Если приложение должно обращаться к Битрикс24, его автору нужно явно настроить runtime-авторизацию; успешный deploy её не подтверждает. Обрабатывайте новый элемент warnings как рекомендацию, а не ошибку публикации.

FIX-0928-17: ключ десктопа Коворка снова читает исходники сервера своего приложения

Было

GET /v1/infra/servers/{id}/sources и GET /v1/infra/servers/{id}/sources/{versionId}/download отвечали ключу десктопа Коворка 404 SERVER_NOT_FOUND, хотя GET /v1/applications отдавал этому же ключу карточку приложения с этим сервером. GET /v1/me/sources для такой строки возвращал reachableViaApi: false и пустые указатели.

Стало

Ключ десктопа Коворка читает список версий и получает ссылку на скачивание, если на сервере есть живая карточка приложения того же пользователя на том же портале. GET /v1/me/sources отдаёт для такой строки reachableViaApi: true и рабочие указатели. Запись, удаление и очистка версий этому ключу по-прежнему закрыты; прочие вторые личные ключи владельца сервера доступа не получают.

BC-0928-18: скоуп vibe:onec требует доступа к 1С, а раздел 1С в самоописании — самого скоупа

Поддержка старого формата до: не предусмотрена

Было

POST /v1/keys, PATCH /v1/keys/:id, POST /v1/apps и PATCH /v1/apps/:id выдавали скоуп vibe:onec без проверки того, есть ли у владельца ключа доступ к 1С. Ключ или приложение создавались без отказа, а вызовы раздела 1С отвечали 403 ONEC_USER_NOT_MAPPED. Каталог GET /v1/onec/tools при этом требует только скоуп — имена опубликованных методов и колонок базы 1С были доступны и без доступа к 1С. Раздел интеграции 1С в GET /v1/guide (блок onecApi) и в GET /v1/me (строка api._rules) отдавался любому ключу российского сегмента, включая ключи без vibe:onec, которым сам раздел недоступен.

Стало

Запрос скоупа vibe:onec без доступа к 1С отвечает 403 ONEC_ACCESS_REQUIRED — на всех четырёх адресах одинаково. Доступ выдаёт администратор портала: кабинет → Интеграция 1С → сопоставления и назначения. Проверяется доступ того, кому право принадлежит — владельца ключа или приложения, — а не того, кто послал запрос: администратор, правящий чужой ключ, получает ответ по доступу владельца, а не по своему. Отказ не гарантирован: часть запросов может пройти, поэтому полагаться на него как на признак отсутствия доступа нельзя. Правка (PATCH), которая оставляет уже выданный vibe:onec на месте и не добавляет новых прав, не отбивается. Раздел интеграции 1С в GET /v1/guide и GET /v1/me теперь требует сам скоуп vibe:onec — ключ без него раздел больше не видит.

Что делать интеграторам

Скрипт, который выпускает или правит ключи и приложения со скоупом vibe:onec, обрабатывает 403 ONEC_ACCESS_REQUIRED как отказ выписки: не повторяет запрос, а просит администратора портала выдать сотруднику доступ к 1С (кабинет → Интеграция 1С → сопоставления и назначения) либо выпускает ключ без vibe:onec. Агент, который читал раздел интеграции 1С в GET /v1/guide или GET /v1/me ключом без vibe:onec, получает этот раздел только ключом со скоупом. Ключам и приложениям, которые уже держат vibe:onec, менять ничего не нужно: правка и перевыпуск без добавления права не отбиваются.

NEW-0928-19: сброс лимитов Cowork/Code по праву акции

Новый метод POST /v1/cowork/limits/reset тратит право сбросить расход всех трёх окон квоты — пятичасового, недельного и месячного. Право выдаётся по акциям платформы и действует до окончания акции. Тратить его может только ключ десктопа Cowork/Code; rightId служит ключом идемпотентности, и повтор с тем же значением возвращает alreadyUsed: true.

В GET /v1/cowork/me и GET /v1/cowork/state появляется блок limitReset: права пользователя, момент последнего сброса и признак замороженного счёта. Блок приходит, когда возможность включена для аккаунта, и до первой акции список прав в нём пуст. Проверяйте наличие ключа: без него возможности нет.

FIX-0928-20: чтение сайтов на аккаунте без модуля «Сайты» отвечает отдельным кодом

Было

Чтение сайтов на аккаунте Битрикс24, где модуль «Сайты» недоступен, отвечало 422 BITRIX_ERROR с текстом Битрикс24 «Method not found!» или 404 ENTITY_NOT_FOUND при русском «Метод не найден». Эти коды не объясняли, что модуль выключен, а повтор запроса возвращал тот же отказ сколько угодно раз.

Стало

Те же запросы отвечают 409 LANDING_MODULE_NOT_ENABLED, и сообщение называет причину прямо: на аккаунте нужно включить раздел «Сайты», после чего методы landing.* станут доступны. Прочие ошибки Битрикс24 на тех же адресах свой прежний код сохраняют.

Затронутые эндпоинты: GET /v1/sites, GET /v1/sites/{id}, POST /v1/sites/search, POST /v1/sites/aggregate и прежний GET /v1/sites/aggregate. Создание, изменение и удаление сайта идут другими методами Битрикс24 и отвечают как прежде.

Влияние на интеграторов

Менять вызовы не нужно. Обработчик, который разбирал текст ответа, чтобы отличить выключенный модуль от сбоя, теперь может ветвиться по коду LANDING_MODULE_NOT_ENABLED, а повтор такого запроса стоит откладывать до включения раздела — до этого ответ не изменится.

NEW-0928-21: спека называет отказ раскатки у методов Follow-up звонков

Методы Follow-up звонков выходят в обновлении Битрикс24 call 26.600.0 и приехали пока не на все порталы. На портале без обновления вызов отвечает 422 METHOD_NOT_YET_AVAILABLE, а в теле ошибки приходит поле error.release с номером ожидаемого обновления — это признак раскатки, а не ошибка интеграции. Раньше про это знали только страницы документации, а спека openapi.json обещала на 422 единственный код BITRIX_ERROR, поэтому клиент, собранный по спеке, отличить раскатку от отказа Битрикс24 не мог. Теперь оба кода названы в спеке, а описание операций несёт номер релиза. Спека портал не опрашивает, поэтому заметка общая и состоянием вашего портала не является: вызовите метод, 200 означает, что он там уже живой. Затронуты POST /v1/calls/followups/list и GET /v1/calls/followups/:callId. Менять в интеграции ничего не нужно.

NEW-0928-22: Облачные задания Коворка

Для клиентского приложения Коворк добавлены API создания однократных и повторяющихся заданий после подтверждения пользователя, чтения расписания, состояния и результата каждого запуска, изменения текста и расписания, приостановки, возобновления, остановки будущих запусков, ручного запуска через POST /v1/cowork/cloud-tasks/:id/runs, отзыва доступа и получения событий через replay/SSE. Расписание принимает часовой пояс пользователя и поддерживает календарное повторение и интервал; ручной запуск сдвигает следующий интервальный запуск, но не календарный. Уже начатый запуск при остановке задания продолжается. Запросы требуют действующий ключ Коворка и ограничены его пользователем и порталом. Создание, подтверждение, загрузка выбранных входных файлов, изменение и ручной запуск требуют ключ в режиме записи; ключ «только чтение» может просматривать задания и результаты. После подтверждения задания Коворк может передать неизменяемый снимок выбранных файлов через POST /v1/cowork/cloud-task-inputs; ответная ссылка input_ref используется только для того же задания. Облачное исполнение поддерживает ограниченные операции чтения Битрикс24 и генерацию изображений с проверкой действующего доступа и обычными пользовательскими лимитами.

FIX-0928-23: Чтение исходников приложений из Коворка

Ключ десктопного Коворка снова может прочитать версии исходников приложения того же пользователя и получить ссылку на скачивание, если приложение привязано к действующему серверу. Доступ остаётся только на чтение; другие личные ключи без привязки его не получают.

BC-0928-24: Последовательный опрос событий бота использует сохранённую позицию

Поддержка старого формата до: не предусмотрена

Было

GET /v1/bots/:botId/events без offset мог повторно выдать событие. Ответ сообщал persisted: true до завершения сохранения.

Стало

Успешный ответ с persisted: true подтверждает сохранённую позицию для следующего опроса. Пересекающийся опрос получает 409 BOT_EVENTS_BUSY; невозможность подтвердить результат — 503 BOT_EVENTS_UNAVAILABLE без событий. Оба ответа содержат Retry-After: 1. Сбой подсистемы опроса может затронуть несколько ботов одновременно. Документация limit уточнена: действующий диапазон 1-200, значения вне него ограничиваются границами без новой ошибки. Явный offset ниже сохранённого по-прежнему не продвигает позицию.

Что делать интеграторам

Дождитесь завершения одного опроса перед следующим. На 409 BOT_EVENTS_BUSY и 503 BOT_EVENTS_UNAVAILABLE повторите запрос через секунду согласно Retry-After. Для продолжения используйте запрос без offset либо nextOffset предыдущего ответа. Сохраняйте защиту от повторной обработки: при обрыве соединения после сохранения позиции однократная обработка приложением не гарантируется.

NEW-0928-25: демо-статус места в ответах подписки Коворк/Код

Ответы GET /v1/cowork/me и GET /v1/cowork/state содержат поле demo. Пока у места идёт демо-доступ, это объект с моментом окончания until и ссылкой на правила использования rulesUrl, иначе null. Тариф места при этом остаётся FREE, а доли квоты уже считаются от лимитов демо — показывайте тариф как «Demo» с датой окончания и ссылкой на правила.

FIX-0928-26: ответ чата без вызовов инструментов больше не помечен как вызов инструментов

Было

POST /v1/chat/completions мог вернуть ответ с finish_reason: "tool_calls", в котором не было ни одного вызова инструмента: модель попыталась вызвать инструмент, но вызов остался обычным текстом. Так было и в потоке, и в ответе без потока. Агентный клиент по такой причине завершения ждал результатов инструментов, не завершал ход и повторял запрос с тем же контекстом, пока не упирался в лимит шагов.

Стало

Если в ответе нет ни одного вызова инструмента, finish_reason: "tool_calls" приходит как finish_reason: "stop": в потоке — в завершающем событии, без потока — в choices[].finish_reason. Ответы с настоящими вызовами инструментов не меняются. Статус ответа по-прежнему 200.

Влияние на интеграторов

Менять ничего не нужно. Клиент завершает ход по finish_reason: "stop" и показывает полученный текст вместо повторного запроса.

FIX-0928-27: сбой связи с порталом в операциях бизнес-процессов больше не выдаётся за выключенный модуль

Было

Операции раздела «Бизнес-процессы» — GET /v1/workflows, запуск, остановка, запись в журнал и отправка события — отвечали 409 BIZPROC_MODULE_NOT_ENABLED, если в тексте ошибки встречались слова «Method not found», независимо от кода ответа. Под это попадали временные сбои: портал вернул HTML-страницу вместо JSON и в её тексте были эти слова (502), либо лимит запросов ответил 429 с такими же словами в тексте. Клиент получал постоянный отказ «попросите администратора включить модуль» и переставал повторять запрос, хотя повтор помог бы.

Стало

409 BIZPROC_MODULE_NOT_ENABLED выдаётся только на клиентский отказ портала класса 4xx (кроме 429) — так же, как в сущностях /v1/bizproc-* и в batch. Временный сбой отвечает своим кодом: 502 BITRIX_UNAVAILABLE или 429 RATE_LIMITED, и такой запрос можно повторить. Портал без модуля бизнес-процессов по-прежнему получает 409 BIZPROC_MODULE_NOT_ENABLED с тем же сообщением.

Влияние на интеграторов

Действий не требуется. Интеграция, которая повторяет запросы на 502 и 429, теперь повторит и эти случаи, а не остановится на 409.

2026-09-27

BC-0927-1: планы Коворка отдают цену нового места со скидками портала

Поддержка старого формата до: не предусмотрена

Было

GET /v1/platform/cowork/plans отдавал в каждом сроке price.terms[] цену по прайсу: vibesMonth и vibesTotal — цена месяца и срока со скидкой за срок, discountPercent — процент скидки за срок из настроек.

Стало

vibesMonth и vibesTotal — лучшая цена нового места для этого портала: скидка за срок или акция без лимита мест. discountPercent — эффективный процент выигравшей скидки от месячной цены без скидок. Скидки не складываются: месту достаётся одна, самая большая.

В каждом сроке появились поля:

  • listVibesMonth — месячная цена без скидок, для зачёркнутой цены
  • renewalVibesMonth — цена месяца за этот срок без акции. Столько спишет продление, и столько стоит именная строка заказа без котировки
  • discount — выигравшая скидка или null: source (TERM — скидка за срок, PROMO — акция), percent, labelKey и label — подпись акции на языке платформы. У скидки за срок labelKey и label равны null
  • offers — акции с лимитом мест, которые портал ещё может получить: percent, labelKey, label, vibesMonth, vibesTotal, renewalVibesMonth и seatsLimit — сколько мест по этой цене осталось. Только для показа

Незнакомый портал получает акции, только когда они включены для всех порталов. Пока акции для портала не включены, vibesMonth и vibesTotal совпадают с прежними, discountPercent — процент скидки за срок, посчитанный от цены месяца (из-за округления он может на единицу отличаться от процента в настройках), а offers пуст.

Что делать интеграторам

Неименные места без котировки считать по vibesMonth и vibesTotal, как раньше. Именные строки (seats[]) без котировки считать по renewalVibesMonth: цена именной строки зависит от того, оплачено ли место сотрудника, а GET /v1/platform/cowork/plans этого не знает. Переплата безопасна — остаток остаётся вайбами на балансе. Цену всего состава со скидками и котировку отдаёт POST /v1/platform/cowork/quote, котировка кладётся в заказ.

NEW-0927-2: котировка цены мест Коворка для чекаута

Новый метод POST /v1/platform/cowork/quote считает цену состава заказа мест Коворка со скидками портала и выдаёт котировку — токен, действительный 14 дней. Нужен ключ интеграции со скоупом cowork:quote.

Тело запроса:

  • portalDomain — обязателен: тот же домен, что уйдёт в metadata.portal_domain оплаты, до 253 символов
  • portalNetworkId — необязателен, до 200 символов
  • application — тот же состав, что уйдёт в payment.paid, включая parts, в формате metadata.application: seats[], unassigned[] и parts — сколько чеков продажи несут состав. До 500 мест
  • context — необязателен: buyer (client или partner) и anonymous. По умолчанию anonymous равен true, и именные строки считаются по renewalVibesMonth без сведений о сотрудниках. false передаётся, только когда покупатель вошёл пользователем этого же портала. При buyer = partner сервер всегда считает anonymous равным true
  • contactEmails — до трёх адресов плательщика, они сохраняются вместе с котировкой. Неподходящие адреса отбрасываются

Незнакомые поля тела и context запросу не мешают — сервер их пропускает.

Ответ:

  • version и capturedAt — версия формы ответа и момент расчёта
  • portal — known (портал найден), portalNetworkId и portalDomain
  • lines[] — строки состава в порядке запроса: kind (seat — именная строка, unassigned — места без сотрудника), index — позиция в seats[] или unassigned[], plan, months и count. Именная строка эхом возвращает userId и b24UserId. В каждой строке groups[] — подряд идущие места с одинаковой ценой: count, listVibesMonth, vibesMonth, vibesTotal, renewalVibesMonth и discount — выигравшая скидка или null в той же форме, что у GET /v1/platform/cowork/plans: source, percent, labelKey и label
  • totals — итог состава: listVibes (без скидок), vibes и savedVibes
  • quote — token и expiresAt, или null, если в составе нет акционных цен и после отбора не осталось ни одного годного адреса

Котировка кладётся в заказ: quote.token — в application.quoteToken. Адресов нет ни в ответе, ни в котировке.

Ошибки: 400 INVALID_FILTER (в том числе без portalDomain, с доменом длиннее 253 символов или с недопустимыми символами в portalDomain и portalNetworkId), 400 INVALID_APPLICATION, 401 INVALID_KEY, 403 INSUFFICIENT_SCOPE, 413 (тело больше 256 КиБ) и 429.

FIX-0927-3: операции с бизнес-процессами на портале без модуля отвечают 409 вместо 404

Было

На портале, где бизнес-процессы не входят в тариф или выключены в настройках, код 409 BIZPROC_MODULE_NOT_ENABLED отдавал только список запущенных процессов — GET /v1/workflows. Четыре остальные операции раздела на том же состоянии портала отвечали 404 ENTITY_NOT_FOUND: POST /v1/workflows/start, DELETE /v1/workflows/:id, POST /v1/workflows/activity-log и POST /v1/workflows/event. Такой ответ выглядел как неверный идентификатор шаблона, документа или процесса, хотя идентификаторы были в порядке.

Стало

Все пять операций раздела на этом состоянии портала отвечают одинаково: 409 BIZPROC_MODULE_NOT_ENABLED. Сообщение называет вызванный метод Битрикс24, обе возможные причины и действие: попросить администратора портала включить бизнес-процессы. Остальные ошибки этих операций не изменились.

Влияние на интеграторов

Действий не требуется: отказ как был 4xx, так и остался, а прежний 404 ENTITY_NOT_FOUND для этого состояния документацией не обещался. Повторять запрос с другими идентификаторами бесполезно, пока бизнес-процессы на портале недоступны. У остановки процесса 404 ENTITY_NOT_FOUND по-прежнему означает, что экземпляр процесса не найден, а 409 BIZPROC_MODULE_NOT_ENABLED — что модуль недоступен и процесс не остановлен. Интеграция, которая считала 404 признаком уже завершённого процесса, на таком портале теперь увидит 409, и процесс при этом не остановлен.

NEW-0927-4: заказ Коворка с котировкой зачисляется по её ценам

Заказ мест Коворка, в котором application.quoteToken несёт котировку из POST /v1/platform/cowork/quote, теперь зачисляется по ценам котировки: акционная цена, показанная покупателю, закреплена на 14 дней с момента выдачи котировки. Сумма заказа — totals.vibes из ответа котировки.

  • Места по акциям с лимитом мест (offers в GET /v1/platform/cowork/plans) выдаются только по котировке в заказе. Чтобы закрепить акционную цену и получить место по такой акции, вызовите quote на шаге оплаты и положите quote.token в application.quoteToken.
  • Котировка чтится, если на момент оплаты она не истекла и выдана для того же портала (portalDomain совпадает с metadata.portal_domain оплаты) и того же состава заказа. Иначе заказ не отклоняется: места зачисляются по действующим ценам, как без котировки, а на что не хватило оплаченной суммы, остаётся вайбами на балансе.
  • Если чек продажи отозван, котировка не чтится.
  • Заказ без quoteToken зачисляется, как раньше.

NEW-0927-5: Действия над чек-листом и прикрепление файла к задаче

Добавлены десять именованных маршрутов /v1/tasks/:taskId/checklist/... и /v1/tasks/:taskId/files/attach: сохранение дерева, участники, перемещение, массовое завершение и вложения чек-листа, а также прикрепление файла Диска к задаче. Ответ каждого маршрута содержит результат метода Bitrix24 в data.

2026-09-26

BC-0926-1: ключ подписки Коворка у владельца без учётной записи Битрикс24 не ротируется, не включается заново и не продлевается

Поддержка старого формата до: не предусмотрена

Было

Владелец ключа подписки Коворка, вошедший из портала без учётной записи Битрикс24, мог ротировать такой ключ (POST /v1/keys/:id/rotate), а также вернуть отозванный ключ в статус ACTIVE и удлинить или снять его expiresAt (PATCH /v1/keys/:id).

Стало

Ротация отвечает 403 COWORK_BOX_SERVICE_ACCOUNT_DENIED. PATCH /v1/keys/:id отвечает тем же кодом только тогда, когда тело переводит ключ в ACTIVE из другого статуса либо удлиняет или снимает expiresAt. Остальные правки идут как раньше: переименование, отзыв ключа и сокращение срока по-прежнему успешны. Прежний ключ продолжает работать. Для владельцев с учётной записью Битрикс24 ничего не изменилось. Таблица кодов — Управление ключами.

Что делать интеграторам

Если вы ротируете ключ подписки Коворка или возвращаете его в работу — войти в кабинет учётной записью Битрикс24 и выпустить ключ от неё. Если вы ключи только переименовываете, отзываете или сокращаете им срок — менять ничего не нужно.

NEW-0926-2: Прямые действия над задачей через API

Для 14 действий над задачей появились именованные маршруты POST /v1/tasks/:taskId/{add-accomplices,add-auditors,approve,bind-crm-item,complete,defer,delegate,disapprove,pause,pause-timer,renew,start,start-timer,take}. Каждый маршрут вызывает одноимённый метод Битрикс24 — tasks.task.addAccomplices, tasks.task.addAuditors, tasks.task.approve, tasks.task.bindCrmItem, tasks.task.complete, tasks.task.defer, tasks.task.delegate, tasks.task.disapprove, tasks.task.pause, tasks.task.pauseTimer, tasks.task.renew, tasks.task.start, tasks.task.startTimer, tasks.task.take — с теми же параметрами. taskId передаётся в пути, остальные именованные аргументы метода — в JSON-теле. Результат Битрикс24 возвращается в data. Ключ должен разрешать запись и иметь скоуп task.

Маршруты и документация

FIX-0926-3: зацикленное рассуждение модели обрывается кодом reasoning_repetition

Было

Если модель с рассуждением начинала повторять в reasoning_content одни и те же фразы, поток POST /v1/chat/completions держался до срока обслуживания или до лимита max_tokens, а ответа так и не было. На запросе с response_format отказ приходил как 422 structured_output_truncated с советом поднять max_tokens и полем suggestedMaxTokens — на зацикленной модели больший бюджет только удлинял повтор, и попытка оплачивалась ещё раз.

Стало

Повтор одних и тех же фраз в рассуждении до начала ответа останавливается, не дожидаясь срока обслуживания и лимита max_tokens, — и на основном потоке, и на потоке резервной модели при исчерпанной квоте. В потоке перед data: [DONE] приходит событие {"error":{"code":"reasoning_repetition","type":"invalid_request_error"}}, статус потока остаётся 200 по протоколу; синхронный запрос с response_format получает 422 с кодом reasoning_repetition, полями finishReason и hint и без param и suggestedMaxTokens. Выход — повторить запрос с reasoning_effort: "none".

Обрывается только повтор одних и тех же фраз, а не перебор разных формулировок, и только в том, что приходит клиенту как reasoning_content: повторяющийся ответ в content, например массив одинаковых объектов, доходит целиком. Если модель присылает один и тот же текст сразу в оба канала, решает то, как он приходит клиенту: у модели с рассуждением этот текст приходит в content и доходит как ответ; если он приходит только как reasoning_content, его повтор обрывается так же, как рассуждение.

При response_format: {"type": "json_object"}, когда модель присылает сам ответ в reasoning_content как JSON-документ (начинается с { или [), готовый документ приходит в content ответом со статусом 200; если документ так и не сложился, приходит reasoning_repetition, а если он слишком длинный, чтобы его восстановить, повтор обрывается сразу. Без response_format ответ, пришедший только в reasoning_content, при зацикливании обрывается reasoning_repetition. Обычный обрыв по max_tokens без повтора по-прежнему отвечает structured_output_truncated.

FIX-0926-4: потоковый ответ чата всегда заканчивается причиной завершения

Было

Потоковый ответ POST /v1/chat/completions мог закончиться data: [DONE] без единого события с finish_reason. Так выглядели два разных случая: модель закончила ответ, но не прислала завершающего события, и соединение с моделью оборвалось посреди ответа. Клиент не мог отличить полный ответ от обрезанного, а часть клиентов не завершала ход без finish_reason.

Стало

Если модель закончила ответ без завершающего события, платформа сама добавляет перед data: [DONE] событие с пустым delta и finish_reason: "tool_calls", когда в ответе были вызовы инструментов, или finish_reason: "stop" в остальных случаях. Если соединение оборвалось раньше, чем пришла причина завершения, перед data: [DONE] приходит событие ошибки с кодом stream_incomplete, type: "server_error", retryable: true и retryAfter. Статус ответа по-прежнему 200, как у остальных ошибок внутри потока.

Влияние на интеграторов

Менять ничего не нужно. Клиент, который читает поток до конца и проверяет поле error, получит stream_incomplete тем же путём, что и stream_idle_timeout, и может повторить запрос с учётом retryAfter.

FIX-0926-5: крупная замена связей пользователей с 1С больше не отвечает 503

Было

Замена полного набора связей PUT /v1/cowork/onec/access/legacy/mappings, которая затрагивала несколько тысяч связей, могла быть отклонена ответом HTTP 503 с кодом DB_TRANSIENT и заголовком Retry-After. Запрос был корректным, но изменения не сохранялись.

Стало

Замена набора из нескольких тысяч связей доходит до конца за один запрос. Корректная замена в пределах лимитов фиксируется, ответ сохраняет HTTP 200.

Влияние на интеграторов

Менять ничего не нужно. Повтор после 503 по-прежнему безопасен: замена задаёт полный набор связей.

2026-09-25

BC-0925-1: нечитаемый номер поля отбивается до изменения и удаления поля смарт-процесса

Поддержка старого формата до: не предусмотрена

Было

PATCH /v1/items/{entityTypeId}/userfields/{id} и DELETE /v1/items/{entityTypeId}/userfields/{id} принимали в :id любую строку. Значение с цифрами в начале молча обрезалось по первому постороннему символу: по :id со значением 7abc изменялось или безвозвратно удалялось поле с номером 7 — другое, чем запрошено, — и ответ был успешным ({"updated": true} и 204). Значение без цифр уходило на портал пустым, и запрос заканчивался ошибкой Битрикс24 о непереданном обязательном параметре.

Стало

:id обязан быть целым положительным числом в обычной записи: только цифры, без ведущего нуля, знака, дробной части, экспоненты и пробелов, и не больше 9007199254740991. Иначе платформа Вайбкод отвечает 400 INVALID_PARAMS и называет параметр в тексте сообщения. Обращения к порталу при таком отказе не происходит, поле не изменяется и не удаляется.

Что делать интеграторам

Если номер поля берётся из списка полей смарт-процесса и подставляется как есть, менять нечего — такие вызовы работают как раньше.

Отказ теперь получают три класса значений, и различаются они тем, что происходило раньше:

  • раньше менялось или удалялось ЧУЖОЕ поле под кодом успеха — строка с цифрами в начале и посторонним хвостом (7abc читалось как 7), экспонента (7e2 читалось как 7, а не 700) и номер больше 9007199254740991 (при пересчёте съезжал на соседний). Эти вызовы меняли не то, что вы просили, и молчали об этом;
  • раньше операция шла над ПРАВИЛЬНЫМ полем под кодом успеха — номер, записанный не в обычной форме: ведущий ноль (007), знак (+7), дробь (7.0), пробелы по краям. Такие вызовы работали верно и теперь получают 400. Это единственный класс, который правка ломает у исправно работавшей интеграции, — уберите лишние символы, оставив только цифры;
  • раньше приходила ошибка портала — значение без цифр, в том числе системное имя поля вида UF_CRM_…. Теперь платформа Вайбкод отвечает 400 сама, до обращения к порталу.

Во всех трёх случаях подставьте числовой номер поля из списка полей ровно в том виде, в каком он там приходит.

Затронутые эндпоинты: PATCH /v1/items/{entityTypeId}/userfields/{id}, DELETE /v1/items/{entityTypeId}/userfields/{id} и их короткие адреса для смарт-счетов /v1/userfields/invoices/{id}.

FIX-0925-2: статус 1С в API Коворка показывает готовое подключение как READY

Было

GET /v1/cowork/onec/status и GET /v1/cowork/onec/admin возвращали работающему подключению 1С setup.state: "CONFIGURING" с проблемой INSTANCE_NOT_BOUND, хотя модуль был подключён и настроен. Из-за этого /status не предлагал администратору REFRESH_USERS, REFRESH_TOOLS и MANAGE_ACCESS, а permissions.canUseDataPlane оставался false. REFRESH_USERS и REFRESH_TOOLS в /status дополнительно требовали полной готовности, хотя POST /v1/cowork/onec/refresh/users и /refresh/tools принимали запрос, если модуль объявил нужную возможность.

Стало

Подключение, у которого модуль выполнил привязку и сохранил настройки с возможностями listUsers, listTools и getData, получает setup.state: "READY". Уже подключённые модули переходят в READY без переподключения и перевыпуска ключа. availability.generation.instanceFingerprint теперь заполнен у привязанного подключения. REFRESH_USERS и REFRESH_TOOLS появляются в availableActions ровно тогда, когда соответствующий метод примет запрос, и совпадают с canRefreshUsers и canRefreshTools в /admin. contractRevision и формы ответов не изменились.

FIX-0925-3: вход из Коворка открывает и приложения Galaxy

Было

POST /v1/cowork/app-login выдавал вход только в приложение на отдельном сервере. На адрес приложения Galaxy он отвечал 404 APP_NOT_AVAILABLE, даже когда политика доступа пускала владельца ключа.

Стало

Приложение Galaxy проходит ту же проверку доступа, что и приложение на отдельном сервере, и при допуске получает ответ HTTP 200 с адресом входа. Машина-хост Galaxy по-прежнему отвечает 404 APP_NOT_AVAILABLE: это не приложение.

FIX-0925-4: сервер, чьё создание сорвалось на стороне облака, больше не висит в `PROVISIONING`

Было

Когда облако отказывало уже после начала создания, а запрос шёл с заголовком Idempotency-Key, сервер оставался в статусе PROVISIONING без идентификатора облачной машины. Повтор с тем же ключом возвращал ту же запись, и она навсегда выглядела как «ещё создаётся»: GET /v1/infra/servers/:id отдавал status: "PROVISIONING", хотя создание уже провалилось.

Стало

Такой сервер сразу получает status: "ERROR". Ответ на само создание не изменился — это по-прежнему 502 PROVIDER_ERROR с прежним текстом, и повтор с тем же Idempotency-Key по-прежнему возвращает ту же запись, а не создаёт вторую машину. Изменился только статус в записи: он честно называет исход, и такой сервер виден среди неисправных, а не среди создающихся.

FIX-0925-5: чтение логов спящего приложения не называет адрес пробуждения, который запрещён на хосте

Было

GET /v1/infra/servers/{id}/logs у спящего Galaxy-приложения выдавал recovery.recoveryAction с адресом пробуждения всякий раз, когда вызывающему не отказывала ни одна дверь по его ключу. Запрет пробуждения на самом хосте галактики (подписка или тариф, блокировка хоста) в этот признак не входил вовсе, поэтому полноправный владелец получал машинный адрес, звал его и получал 402 либо 403 SERVER_WAKE_BLOCKED. Тем же признаком выдавалось поле recovery.deliveryWakesHost — платный второй путь подъёма, который на заблокированном хосте тоже не срабатывает.

Стало

При запрете пробуждения на хосте ответ не называет адрес пробуждения ни одним полем — ни recovery.recoveryAction, ни текстом hint, — и не называет recovery.deliveryWakesHost. Вместо адреса hint называет условие: запрет стоит на общем хосте, снимает его владелец аккаунта, и смена ключа не поможет. Сам ответ чтения логов остаётся HTTP 200 с пустым списком строк, как и прежде. Поле recovery.wakeSchedule в этом состоянии не меняется: создание окна по расписанию остаётся доступным, и проза отдельно предупреждает, что хост окно не поднимет.

Влияние на интеграторов

Менять ничего не нужно, если вы уже ветвитесь по НАЛИЧИЮ поля recovery.recoveryAction, как того требует документация. Клиент, подставлявший адрес пробуждения безусловно, перестанет получать гарантированный отказ по нему.

FIX-0925-6: курсор с повреждённой датой в выгрузках выручки отклоняется кодом INVALID_CURSOR, а не ошибкой 500

Было

Курсор с повреждённой датой — например, собранный или отредактированный вручную — ронял запрос: вместо отказа в курсоре выгрузка отвечала HTTP 500. Так вели себя GET /v1/platform/revenue/topups, GET /v1/platform/revenue/charges, GET /v1/platform/revenue/expirations, GET /v1/platform/revenue/refunds, GET /v1/platform/revenue/consumption/by-service, GET /v1/platform/revenue/consumption/reconstructed и GET /v1/platform/revenue/money-in/payments.

Стало

Такой курсор отклоняется ответом 400 INVALID_CURSOR, как и курсор, выданный для другого окна. Курсор из поля nextCursor предыдущей страницы работает как раньше, порядок обхода и состав страниц не изменились.

Влияние на интеграторов

Менять ничего не нужно. Клиент, который повторял запрос после ответа HTTP 500, теперь сразу получает 400 INVALID_CURSOR: повтор с тем же курсором не поможет, обход начинается заново без cursor.

FIX-0925-7: выгрузка списаний учитывает перерасход ИИ сверх квоты

Было

Списания вайбов за перерасход ИИ сверх квоты портала не попадали в GET /v1/platform/revenue/charges и GET /v1/platform/revenue/consumption/by-service: строка такого списания не несла разбивки по траншам, а обе выгрузки строятся по ней. Потреблённые так купленные вайбы были видны только косвенно — по уменьшению remaining в GET /v1/platform/revenue/topups.

Стало

Списание за перерасход ИИ сверх квоты записывает разбивку по траншам, как остальные списания, и входит в обе выгрузки: в /charges — движением по траншу, в /consumption/by-service — с типом услуги AI_TOKENS. Такое списание — одна строка на портал за московские сутки: она относится к UTC-суткам своего первого перерасхода и дорастает до конца московских суток, поэтому последние закрытые UTC-сутки стоит перезапрашивать после 21:00 UTC следующих суток. Так же теперь пишется и внешний расход ИИ сверх квоты — у него своя строка на каждое списание. Списания, записанные до выпуска исправления, разбивки не несут и в выгрузки не попадают. Формат ответа прежний, ответ остаётся HTTP 200.

FIX-0925-8: загрузка файла ботом проверяет обязательные поля до вызова Битрикс24

Было

POST /v1/bots/:botId/files передавал тело в Битрикс24 как есть. Если dialogId отсутствовал или файл был прислан под нераспознанными именами полей (например, fileName и fileContent), запрос уходил в Битрикс24 без обязательных параметров, и оттуда возвращался код 100 — «не передан обязательный параметр», без указания, какой именно.

Стало

Обязательные поля проверяются до обращения к Битрикс24. Без dialogId, имени или содержимого файла ответ — 400 MISSING_PARAMS. В тексте ошибки перечислены нераспознанные поля тела и показана правильная форма запроса.

Влияние на интеграторов

Корректный запрос в любом из трёх поддерживаемых форматов тела работает как прежде. Клиент, который получал 100, теперь видит адресный отказ платформы Вайбкод и знает, какое поле пропущено.

FIX-0925-9: Правка прав ключа, чей вебхук остался на прежнем адресе портала, отвечает PORTAL_ADDRESS_CHANGED

Было

PATCH /v1/keys/:id с новым списком scopes для ключа, чей вебхук после смены адреса портала остался выписан на прежний адрес, отвечал одним из отказов синхронизации прав — 410 STALE_DEVELOPER_KEY, 502 RECOVERY_FAILED или 502 DEVKEY_SCOPE_SYNC_FAILED. Ни один из них не называл настоящую причину: повтор не помогал, а совет переподключить аккаунт этот случай не лечил. Любой вызов такого ключа к порталу при этом уже отвечал 409 PORTAL_ADDRESS_CHANGED.

Стало

Правка прав такого ключа отвечает тем же 409 PORTAL_ADDRESS_CHANGED и к порталу не обращается. Права ключа не меняются ни на платформе Вайбкод, ни в Битрикс24. Остальные ответы правки прав прежние.

Влияние на интеграторов

Обработайте 409 PORTAL_ADDRESS_CHANGED так же, как на остальных вызовах ключа: переподключите ключ — вебхук перевыпустится на текущий адрес портала, — затем повторите правку прав. Описание кода — Авторизация, ключи и права.

FIX-0925-10: список бизнес-процессов на портале без модуля отвечает понятным кодом

Было

Если модуль бизнес-процессов не входит в тариф портала или выключен в его настройках, Битрикс24 отвечает на метод bizproc.workflow.instances сообщением «метод не найден». Запрос GET /v1/workflows отдавал это как 404 ENTITY_NOT_FOUND — сообщение о ненайденной сущности там, где в порядке и сущность, и сам запрос. Кода ENTITY_NOT_FOUND не было в таблице ошибок страницы, а причина отказа по ответу не читалась.

Стало

То же состояние портала отвечает 409 BIZPROC_MODULE_NOT_ENABLED. Сообщение называет обе возможные причины и действие: попросить администратора портала включить бизнес-процессы. Ответ остаётся клиентским отказом класса 4xx, а защита от циклов ошибок такой отказ не считает — он остаётся понятным при любом числе повторов, а не подменяется на 429 ERROR_LOOP_DETECTED.

Влияние на интеграторов

Действий не требуется: как был отказ 4xx, так и остался. Интеграция, которая отличала выключённый модуль по тексту сообщения Битрикс24, может перейти на код 409 BIZPROC_MODULE_NOT_ENABLED. Прежний 404 ENTITY_NOT_FOUND для этого состояния документацией не обещался. Остальные операции раздела «Бизнес-процессы» отвечают как прежде — изменение касается только списка запущенных процессов.

FIX-0925-11: BitrixGPT 5.5 принимает json_schema, если модель поддерживает structured outputs

Было

POST /v1/chat/completions с response_format.type = json_schema и моделью bitrix/bitrixgpt-5.5 отвечал 400 model_does_not_support_structured_outputs, а GET /v1/me не показывал доступные structured outputs.

Стало

Модели BitrixGPT 5.5 и Thinking, поддерживающие structured outputs, появляются в GET /v1/me, а запросы с json_schema проходят проверку возможностей. Клиентам не требуется менять формат запроса.

Влияние на интеграторов

Изменений в интеграционном коде не требуется: используйте прежний response_format.type = json_schema.

FIX-0925-12: хранилище: чтение и удаление по ключу берут неудалённую копию файла

Было

Если под одним ключом лежало несколько копий файла и самая ранняя из них была удалена, GET /v1/storage/objects/{key}, HEAD /v1/storage/objects/{key} и DELETE /v1/storage/objects/{key} отвечали 410 STORAGE_OBJECT_DELETED, хотя файл оставался в списке объектов. После первого удаления по такому ключу каждый следующий DELETE отвечал так же, а оставшаяся копия не удалялась.

Стало

Запрос по ключу берёт самую раннюю неудалённую копию этого файла. Пока такая копия есть, файл читается и удаляется по ключу, и каждый DELETE удаляет одну копию. Когда неудалённых копий не осталось, ответ прежний: 410 STORAGE_OBJECT_DELETED.

Влияние на интеграторов

Менять ничего не нужно. Для файла с несколькими копиями повторный DELETE по ключу удаляет следующую копию и отвечает 410 STORAGE_OBJECT_DELETED, только когда копий не осталось.

BC-0925-13: список почтовых ящиков без модуля «Почта» отвечает понятным 409

Поддержка старого формата до: не предусмотрена

Было

Если на портале нет модуля «Почта», метод не входит в тариф либо портал ещё не получил обновление с mail.mailbox.list, Битрикс24 отвечал «метод не найден», и на GET /v1/mail/mailboxes это приезжало как 404 ENTITY_NOT_FOUND. Состояние портала было неотличимо от отсутствующей записи.

Стало

Только GET /v1/mail/mailboxes отвечает 409 MAIL_MODULE_NOT_ENABLED. В тексте названы возможные причины — модуль не установлен, метод не входит в тариф либо на портал ещё не приехало обновление с mail.mailbox.list, — потому что снаружи их не различить. Прочие отказы Битрикс24 на этом маршруте не изменились: отказ по правам остаётся 403, лимит запросов — 429 со своим Retry-After.

Влияние на интеграторов

Успешные ответы не изменились. Если код ветвился на 404 для списка ящиков, добавьте ветку 409 MAIL_MODULE_NOT_ENABLED. Не повторяйте неизменный 409 вслепую; повторите запрос после подтверждённого включения модуля, изменения тарифа или обновления портала. Соседние маршруты почтовых ящиков изменение не затрагивает.

BC-0925-14: лента чата задачи для ключа только для чтения — страница не больше 50 сообщений

Поддержка старого формата до: не предусмотрена

Было

GET /v1/tasks/:taskId/chat/messages мог отдать любому ключу, в том числе ключу только для чтения, страницу до 200 сообщений — столько, сколько передано в limit.

Стало

Для ключа только для чтения страница ленты чата задачи — не больше 50 сообщений при любом limit: чтение страницей до 200 сообщений может сделать пользователя участником чата задачи, то есть изменяет данные портала, а ключ только для чтения данные портала не меняет. hasNextPage для такого ключа вычисляется по заполненности страницы: полная страница означает, что история может продолжаться. Ответ остаётся HTTP 200 той же формы. Ключ с правом записи читает ленту как прежде, до 200 сообщений на страницу.

Что делать интеграторам

Если ключ только для чтения запрашивает limit больше 50, листайте историю курсором lastId до hasNextPage: false и не рассчитывайте, что страница вместит весь limit. Чтобы получать до 200 сообщений на страницу, переключите ключ в режим записи, как описано на странице прав доступа.

NEW-0925-15: Чаты: режим format=v2, загрузка чата, счётчики и сообщения вокруг сообщения

Раздел чатов получил режим format=v2 у двух эндпоинтов и три новых эндпоинта. GET /v1/chats/recent с параметром format=v2 отдаёт список последних диалогов страницами по курсору lastMessageDate и признаку hasNextPage, а GET /v1/chats/:dialogId/messages с тем же параметром читает ленту по курсору lastId в обе стороны через order. Без format=v2 оба эндпоинта отвечают как раньше. Новые эндпоинты GET /v1/chats/:dialogId/load открывают чат одним запросом — карточка, первая страница сообщений и закреплённые, GET /v1/chats/counters отдаёт счётчики непрочитанного по чатам, а GET /v1/chats/messages/:messageId/context — сообщение вместе с соседними. Лимиты вне диапазона обрезаются с эхом в meta, неизвестный или повторный параметр, кроме format, отклоняется с 400 INVALID_PARAMS; при повторе format берётся последнее значение. Форма format[]=v2 тоже принимается, если все элементы равны v2. Код отказа портала приходит в error.b24Code у ответов 422 и 404. Дата, которой нет в календаре (2026-02-30, 24:00), в курсоре lastMessageDate и в updatedAfter отклоняется с 400 INVALID_PARAMS, а не сдвигается на соседний день. Открытие чата, сообщения вокруг сообщения и лента в режиме v2 могут сделать пользователя участником чата, если чат разрешает автовступление, поэтому они считаются изменяющими: ключ только для чтения получает на них 403 WRITE_BLOCKED_READONLY_KEY, как описано на странице прав доступа.

FIX-0925-16: обрыв связи с порталом отвечает 502 BITRIX_UNAVAILABLE

Было

Если соединение с порталом Битрикс24 обрывалось до ответа (сброс или отказ соединения, сбой TLS, обрыв посреди ответа), запрос к данным портала получал 500 INTERNAL_ERROR, как будто сломалась сама платформа. Так же отвечал и POST /v1/batch.

Стало

Такой обрыв отвечает 502 BITRIX_UNAVAILABLE, как и описано в справочнике ошибок. В error.message указан машинный код причины, если он известен, например ECONNRESET, в error.hint — правило повтора: чтение можно повторить с паузой, а перед повтором записи перечитайте запись. Сам запрос платформа не повторяет.

Влияние на интеграторов

Действий не требуется. Если вы повторяете запрос с паузой на BITRIX_UNAVAILABLE, короткие сетевые сбои теперь попадают в эту ветку, а не в INTERNAL_ERROR.

NEW-0925-17: расход вайбов по порталам: метод GET /v1/platform/revenue/spend

Revenue Export API показывал потребление только купленных вайбов, только за закрытые UTC-сутки и с разрезом не глубже типа услуги: GET /v1/platform/revenue/consumption/by-service не отдавал ни подаренные вайбы, ни текущие сутки, ни тариф сервера, тариф Коворка или модель ИИ.

Новый метод GET /v1/platform/revenue/spend (право ключа revenue:spend) отдаёт фактический расход вайбов: сутки × портал × сервис × позиция прайса × источник вайбов — за окно до 92 суток с 30.06.2026 по текущие сутки включительно, в JSON или CSV. Источник (funding) различает купленные вайбы, стартовый грант, бонусы Битрикс24, ручные начисления, компенсации, вернувшиеся возвратом вайбы, списания в долг и списания, у которых источник не записан. Страница — одни UTC-сутки, обход по nextCursor. Сутки начиная с finalBefore помечены provisional и ещё могут измениться. Остальные методы не изменились.

FIX-0925-18: будильник на сервере с режимом работы по расписанию заводится и правится через API

Было

Запись FIX-0916-10 обещала, что сервер в режиме работы SCHEDULE не считается круглосуточным и дополнительный будильник на нём заводится обычным порядком. Через API обещание не выполнялось: POST /v1/infra/servers/{id}/wake-schedules и PATCH /v1/infra/servers/{id}/wake-schedules/{scheduleId} отвечали 400 ALWAYS_ON_CONFLICT такому серверу на невытесняемом тарифе без порога засыпания — и ключу владельца, и ключу администратора команды сервера.

Стало

Сервер в режиме работы SCHEDULE получает будильник через API обычным порядком: создание отвечает 201, правка — 200, как и у любого сервера, которому окно разрешено. Серверу в режиме работы ALWAYS на невытесняемом тарифе отказ 400 ALWAYS_ON_CONFLICT приходит по-прежнему, кроме приложения в галактике: тариф оно наследует от галактики и круглосуточным не считается.

Влияние на интеграторов

Менять ничего не нужно.

FIX-0925-19: справочник сотрудников использует доступные ключи

GET /v1/infra/servers/:id/b24-users находит сотрудников, даже если у ключа сервера нет прав на справочник.

Было

Если у ключа сервера не было права user, Битрикс24 отказывал, и ответ приходил пустым: { "success": true, "data": [] } — без объяснения причины.

Стало

Если ключ сервера получил отказ по правам, Вайбкод пробует другие ключи того же портала: запасной ключ сервера, затем ключи действующих администраторов портала. Если права на справочник не нашлись ни у одного ключа, ответ остаётся HTTP 200 с пустым data и содержит hint с объяснением, какие права выдать. При сетевой ошибке ответ прежний: HTTP 200 и { "success": true, "data": [] }.

BC-0925-20: события портала и колбэки роботов приходят без токенов Битрикс24 при ключе «Только чтение»

Поддержка старого формата до: не предусмотрена

Было

Сервер на платформе Вайбкод получал события портала и колбэки роботов бизнес-процессов с auth[access_token] и auth[refresh_token] при любом режиме ключей.

Стало

Если ключ сервера или ключ авторизации подписанного приложения в режиме «Только чтение», событие и колбэк приходят без auth[access_token] и auth[refresh_token], и шлюз не добавляет к ним заголовки пользователя события. Так же приходят события серверу, чей ключ удалён или отозван, и приложению без активного ключа авторизации. Событие на адрес приложения вне платформы — не субдомен Black Hole и не собственный домен сервера — приходит без этих токенов при любом режиме ключей. auth[application_token], auth[user_id] и данные события остаются. Обмен токена события на сессию через POST /v1/oauth/placement-session такому обработчику недоступен, а робот, ждущий ответа, не завершится: ответ роботу — запись в Битрикс24. Если адрес приложения указывает на сервер платформы, событие пользователя, которому политика доступа этого сервера закрыта, не доставляется; событие на внешний адрес политикой доступа не фильтруется.

Что делать: для чтения вызывайте API Вайбкод своим ключом — вызовы пойдут от владельца ключа. Чтобы вернуть токены и ответы роботам, переведите в «Чтение и запись» оба ключа — ключ сервера и ключ приложения. Если адрес приложения внешний, перенесите обработчик на сервер платформы: туда токены приходят, когда оба ключа в «Чтение и запись».

BC-0925-21: выкладка ключом «Только чтение» не публикует анонс новой версии

Поддержка старого формата до: не предусмотрена

Было

POST /v1/infra/servers/:id/deploy с полем changelog публиковал текст новой версии подписчикам в ленту канала приложения в мессенджере Битрикс24 при любом режиме ключа.

Стало

Выкладка ключом в режиме «Только чтение» анонс не публикует: публикация в ленту — запись в Битрикс24. Заметка версии сохраняется в хранилище исходников, карточка каталога и иконка публикуются, в warnings ответа приходит предупреждение. Ответ HTTP 200 не меняется.

Что делать: чтобы анонс публиковался, выкладывайте ключом в режиме «Чтение и запись».

NEW-0925-22: поле `note` в блоке `eventDelivery` ответа `GET /v1/me`

В блоке eventDelivery ответа GET /v1/me появилось поле note. Оно объясняет, при каком режиме ключей события и колбэки приходят без auth[access_token] и auth[refresh_token], что из этого следует и как вернуть токены. Поле приходит всегда, когда в ответе есть блок eventDelivery, независимо от режима ключа, которым вызван GET /v1/me.

NEW-0925-23: начисления вайбов по порталам: метод GET /v1/platform/revenue/credits

Revenue Export API отдавал только купленные транши (GET /v1/platform/revenue/topups) и их сгорания (/expirations): подаренные вайбы — стартовый грант, бонусы Битрикс24, ручные начисления, компенсации, — а также вайбы, вернувшиеся возвратом, в том числе купленные, в выгрузках не встречались, и путь вайбов портала от начисления до нулевого баланса не сходился.

Новый метод GET /v1/platform/revenue/credits (право ключа revenue:spend) отдаёт все транши вайбов по порталам — купленные и подаренные — строкой на транш: вид начисления (kind — те же значения, что funding в /spend), источник транша, для возврата — позиция возвращённой подписки, суммы «начислено / потрачено / осталось / сгорело / отозвано» на момент запроса и даты зачисления, сгорания и отзыва. Окно from/to необязательно и фильтрует дату зачисления, текущие сутки разрешены; страница — до limit строк, обход по nextCursor; формат JSON или CSV. Остальные методы не изменились.

2026-09-24

FIX-0924-1: записи доступа 1С через API Коворка принимаются при любом размере сохранённого графа

Было

PUT и DELETE /v1/cowork/onec/access/assignments/{onecUserId} и PUT /v1/cowork/onec/access/legacy/mappings отвечали 413 ONEC_ASSIGNMENT_GRAPH_TOO_LARGE, если сохранённый граф назначений превышал предел чтения, даже когда запись его уменьшала. Замена legacy-связей принимала не больше 10 000 пар.

Стало

Запись, которая не увеличивает итог выборов, принимается при любом размере сохранённого графа; 413 ONEC_ASSIGNMENT_GRAPH_TOO_LARGE у этих методов остаётся только для записи, результат которой превысит 10 000 выборов и окажется больше текущего. DELETE этот код больше не возвращает. Если после принятой записи сохранённый граф всё ещё выше предела чтения (больше 10 000 аккаунтов 1С, больше 20 000 личных или больше 20 000 подразделенческих выборов, больше 20 000 legacy-связей), ответ остаётся 200, но вместо графа содержит "accounts": null или "mappings": null и "graphReadable": false; у назначений в нём есть новая version. Когда граф читается, ответ прежний. Замена legacy-связей принимает до 20 000 пар.

NEW-0924-2: Признак множественности у полей портала в описании полей сущности

В ответе GET /v1/{entity}/fields (например, GET /v1/deals/fields) у поля, которое пришло с портала, появился ключ multiple: true, если Битрикс24 сообщает, что поле принимает несколько значений, — например, у множественного пользовательского поля ufCrm_*. Значение такого поля при создании и изменении записи передавайте массивом. Отсутствие ключа multiple не означает, что поле одиночное: признак не выводится у полей, которые платформа описывает сама, и у полей, для которых портал множественность не сообщил.

BC-0924-3: Сортировка документов CRM принимает только один уровень скобок

Поддержка старого формата до: не предусмотрена

Было

GET /v1/crm-documents принимал под именем поля сортировки второй уровень скобок. Написания order[updateTime][]=desc и order[updateTime][0]=desc отвечали HTTP 200 и сортировали выдачу так, будто передано order[updateTime]=desc — то есть за вызывающего досочинялась сортировка, которой он не писал. Написание order[updateTime][x]=desc отвечало HTTP 500.

Стало

Запись сортировки обязана нести направление, а не структуру. Второй уровень скобок под именем поля — order[updateTime][x]=desc, order[updateTime][]=desc, order[updateTime][0]=desc — возвращает HTTP 400 INVALID_ORDER до обращения к Битрикс24. Одноуровневое написание order[updateTime]=desc и регистр направления работают как прежде. Неизвестное направление по-прежнему отбрасывается без отказа, а написание, которое разбору не поддаётся вовсе — order=desc, order[]=desc, третий уровень скобок, — по-прежнему оставляет выдачу без сортировки.

Что делать интеграторам

Передавайте сортировку одним уровнем скобок: order[updateTime]=desc. Если строка запроса собирается из массива или индексированного списка, приведите её к этому виду: прежние написания order[updateTime][]=desc и order[updateTime][0]=desc теперь отказываются, а не сортируют.

FIX-0924-4: статус управления 1С в Коворке по умолчанию не запрашивает подтверждение кодом

Было

При включённой двухфакторной защите платформы GET /v1/cowork/onec/status возвращал администратору портала admin.stepUp.required=true и непустой список channels.

Стало

По умолчанию GET /v1/cowork/onec/status возвращает admin.stepUp={required:false,state:"NOT_REQUIRED",expiresAt:null,freshUntil:null,channels:[]} и не предлагает действие REQUEST_STEP_UP. Ответ по-прежнему 200. Ревизия контракта onec-cowork-management/v1-proposed-2026-09-21-r3 и форма ответа не изменились.

FIX-0924-5: суточная норма вызовов считается по закрытым часам

Было

Заголовки X-RateLimit-Used и X-RateLimit-Remaining были описаны как учитывающие и вызовы текущего часа — с точностью до минуты.

Стало

Оба заголовка считают израсходованное за сегодня по закрытым часам: вызовы текущего часа попадают в счёт после его окончания, поэтому заголовки отстают от факта до 70 минут и в начале часа могут не показывать уже сделанные вызовы. Размер суточной нормы, заголовок X-RateLimit-Quota и код отказа QUOTA_EXCEEDED не менялись, вызов в пределах нормы по-прежнему отвечает HTTP 200. Клиенту менять ничего не нужно; если остаток нужен точно, учитывайте собственные вызовы текущего часа на своей стороне.

FIX-0924-6: справочник агрегации в GET /v1/guide описывает POST-агрегацию и поля числовых функций по типу

Было

Блок operations.aggregate в GET /v1/guide описывал устаревший GET …/aggregate с параметрами op и field и называл aggregatableFields источником поля для sum/avg/min/max. Для элементов смарт-процессов (/v1/items/:entityTypeId) он указывал путь GET /v1/items/:entityTypeId/aggregate, которого нет. Сообщение 400 INVALID_PARAMS о неизвестном поле в POST …/aggregate перечисляло тот же список aggregatableFields вместо числовых полей.

Стало

operations.aggregate описывает POST …/aggregate с массивом aggregate[]. Поле для sum/avg/min/max — любое поле с типом number из …/fields или числовое пользовательское поле там, где сущность их поддерживает (есть ключ ufSupport). aggregatableFields — поля для groupBy. Сообщение 400 INVALID_PARAMS перечисляет числовые поля сущности, а у сущности без числовых полей говорит This entity declares no number-typed field.. У сущностей, где пользовательских полей нет, ключа ufSupport больше нет, и справочник с openapi.json их не обещает. Ответ GET /v1/guide по-прежнему HTTP 200, код и статус ошибки агрегации прежние.

Влияние на интеграторов

Если клиент читал из справочника params.op и params.field, переходите на params.aggregate и вызывайте POST …/aggregate. Вызовы устаревшего GET …/aggregate продолжают работать как раньше.

NEW-0924-7: расписание, на котором рождаются новые машины

Администратор аккаунта Битрикс24 может отметить расписание работы, на котором рождаются новые машины: PUT /v1/work-schedules/:id/default с телом {"audience": "machines", "enabled": true}. Отметок две: machines — для серверов и приложений в галактике, agents — для агентов и ботов. Вторая отдельная, потому что агент на расписании вне окна спит и не отвечает на сообщения до начала следующего окна. У каждой стороны отмечено не больше одного расписания: новая отметка переезжает с прежнего, "enabled": false её снимает.

Сервер, созданный через POST /v1/infra/servers без runMode, рождается на отмеченном расписании. Явный runMode, включая IDLE, по-прежнему сильнее отметки, а уже созданные машины свой режим не меняют.

В каждой строке библиотеки GET /v1/work-schedules появились поля defaultForNewMachines и defaultForNewAgents. Отметку меняет только администратор, остальные получают 403 ADMIN_ONLY.

FIX-0924-8: режим работы, названный при создании, доезжает до приложения в галактике

Было

POST /v1/infra/servers принимал блок runMode, но когда машина размещалась в галактике, блок молча выбрасывался: приложение рождалось в режиме по умолчанию. Расписание, которого в аккаунте нет, в этом случае тоже не отвергалось.

Стало

Блок runMode применяется и к приложению в галактике. Ненайденное или пустое расписание отвергается до создания, как и у отдельного сервера: 400 WORK_SCHEDULE_NOT_FOUND или 400 WORK_SCHEDULE_EMPTY, приложение при этом не создаётся.

Влияние на интеграторов

Интеграция, которая передаёт runMode, получает названный режим без отдельного вызова PATCH /v1/infra/servers/:id/run-mode. Расписание, которого нет, теперь отвергается и при размещении в галактике, как документация и обещала.

FIX-0924-9: агрегация отвечает об ошибке портала так же, как список

Было

POST /v1/{entity}/aggregate и устаревший GET /v1/{entity}/aggregate отдавали отказ Битрикс24 без пояснений, привязанных к вызванному методу: тело ошибки приходило без поля hint с известным ограничением метода и без поля warning о том, что та же ошибка уже повторялась. Список и поиск по той же сущности на тот же отказ отвечали с этими полями. Из-за этого один и тот же отказ портала выглядел у клиента по-разному в зависимости от того, какой адрес его вызвал.

Стало

Обе двери агрегации отвечают об одной причине тем же кодом и с теми же пояснениями, что GET /v1/{entity} и POST /v1/{entity}/search. В теле ошибки появляются hint с известным ограничением вызванного метода Битрикс24 и warning, когда отказ повторяется. Успешные ответы агрегации не изменились, коды успешных ответов прежние.

NEW-0924-10: новый код отказа `agent_turn_loop_detected` для зациклившегося хода агента

POST /v1/chat/completions получил код отказа agent_turn_loop_detected (409) для зациклившегося хода агента. Сейчас отказ не действует: такие запросы обслуживаются как прежде, код ничего не меняет в работе существующих интеграций. О включении отказа будет объявлено отдельной записью типа BC с датой.

Условие отказа после включения: в messages после последнего сообщения с ролью user накопилось 15 и более ответов модели с ролью assistant без tool_calls. Считаются все такие ответы после последнего сообщения user, а не только идущие подряд: вызовы инструментов между ними счёт не сбрасывают. Сами ответы с tool_calls в счёт не входят, поэтому ход, в котором модель на каждом шаге вызывает инструменты, условие не задевает. Обращение к модели по такому запросу не выполняется, и петля не расходует лимит. Чтобы продолжить, отправьте новое сообщение пользователя — счёт начинается заново.

FIX-0924-11: пустой `fields` в теле бот-запроса больше не съедает плоские поля

Было

Тело {"fields": [], "command": "help", "title": "Help"} — так сериализуется пустой словарь, например в PHP через json_encode([]) — принималось за формат Битрикс24 и уходило в портал как fields: []. Присланные command и title терялись молча, без предупреждения в ответе. То же на PATCH /v1/bots/{botId} и PATCH /v1/bots/{botId}/chats/{dialogId}, причём там пустую обёртку изображали ещё и null, false, 0 и пустая строка: запрос уезжал в портал как есть, портал отвечал 200, и ничего не менялось.

Стало

Пустая обёртка fields обёрткой не считается — тело читается как плоское и доезжает до портала целиком. Затронуты POST /v1/bots/{botId}/commands, PATCH /v1/bots/{botId}/commands/{commandId}, PATCH /v1/bots/{botId} и PATCH /v1/bots/{botId}/chats/{dialogId}. Непустая обёртка по-прежнему передаётся без изменений. Два следствия: ключ fields больше не попадает в warning.droppedFields, а неизвестный ключ верхнего уровня при разворачивании отбрасывается — до портала он всё равно не доезжал.

NEW-0924-12: отказ в создании приложения из Коворка говорит, откроет ли дверь пробный период Маркета

Отказ POST /v1/cowork/applications «подписки нет» у аккаунта подписочной модели — 402 MARKETPLACE_REQUIRED — теперь несёт необязательное поле error.details.activation. Это блок той же формы, что activation в GET /v1/cowork/state, но посчитанный для этой двери: marketTrial.available: true значит, что одноразовый пробный период Маркета откроет создание приложений. Включается он прежней ручкой POST /v1/cowork/activate-market-trial с явным подтверждением человека.

Значения not_required_for_data здесь не бывает: для чтения данных подписка не нужна, а для создания приложений — нужна. Аккаунту на бесплатном тарифе Битрикс24 пробный период не предлагается: REST там недоступен, и период сгорел бы впустую. У аккаунта тарифной модели доступа поля нет: пробный период Маркета ему доступа не откроет, а путь покупки тарифа в теле отказа уже есть. Другие отказы этой двери поля не несут: пробный период снимает только отсутствие подписки, а подписку, которую не удалось прочитать, лечит переподключение приложения под главным администратором. Поля нет и тогда, когда строку аккаунта прочитать не удалось; остальное тело отказа не меняется.

FIX-0924-13: управление 1С из Коворка больше не требует подтверждения кодом по умолчанию

Было

Методы управления 1С из Коворка (/v1/cowork/onec/admin, /v1/cowork/onec/connection/…, /v1/cowork/onec/refresh/…, /v1/cowork/onec/access/…) при включённой двухфакторной защите платформы отвечали 403 ONEC_STEP_UP_REQUIRED, пока администратор не подтвердит вход кодом на почту или в Битрикс24. POST /v1/cowork/onec/step-up/request с каналом EMAIL отвечал 202 и отправлял код, даже когда подтверждение не требовалось.

Стало

По умолчанию подтверждение не требуется: методы управления принимают запрос администратора портала по desktop-ключу Коворка, его разрешениям и свежей проверке роли. Успешные ответы методов управления не изменились. Проверки ключа, роли, согласия, read-only ключа и подтверждения последствий у перевыпуска и отключения тоже не изменились. Если подтверждение включено для портала, POST /v1/cowork/onec/step-up/request работает как раньше: ответ по-прежнему 202. Если не требуется, этот метод для любого канала отвечает 400 STEP_UP_CHANNEL_UNAVAILABLE и код не отправляет. Клиенту, который решает о подтверждении по полю admin.stepUp.required, менять ничего не нужно. Ревизия контракта onec-cowork-management/v1-proposed-2026-09-21-r3 и формы ответов не изменились.

FIX-0924-14: Запрос событий бота с явным offset ниже сохранённого больше не сдвигает курсор

Было

GET /v1/bots/{botId}/events с явным offset ниже сохранённой позиции (например, offset=0 для отладки) сдвигал сохранённый курсор, если в ответе были события. Следующий обычный запрос без offset уходил с новой позицией, Битрикс24 удалял отданные отладочному запросу события как подтверждённые, и основной цикл бота их не получал.

Стало

Явный offset ниже сохранённой позиции возвращает события, но курсор не сдвигает, и в ответе приходит persisted: false. Явный offset, равный сохранённой позиции или больше неё, сдвигает курсор как раньше. Подтверждённые события Битрикс24 удаляет, поэтому offset=0 показывает только события, ещё не подтверждённые в очереди, а не всю историю.

Влияние на интеграторов

Менять ничего не нужно. Отладочный запрос с offset=0 больше не отнимает события у основного цикла, листание по nextOffset работает как раньше.

NEW-0924-15: resubscribe сообщает режим событий на стороне Битрикс24

Бот в режиме fetch получает события, только пока такой же режим стоит у него на стороне Битрикс24. Это значение авторитетное, и оно может разойтись с тем, что хранит платформа Вайбкод, — тогда опрос GET /v1/bots/{botId}/events успешен, ошибок нет, а очередь остаётся пустой сколько угодно долго.

Ответ POST /v1/bots/{botId}/resubscribe теперь несёт три новых поля. b24EventMode — режим, который Битрикс24 держал ДО вызова (null, если портал его не сообщил). diverged — расходился ли он с полем eventMode, которое хранит платформа. hint — что вызов сделал на самом деле и что проверять дальше.

Различать это важно: Битрикс24 перепривязывает события только при РЕАЛЬНОЙ смене режима. Если режимы уже совпадали, вызов ничего не перепривязал, и прежний ответ { resubscribed: true } читался как выполненное восстановление. Теперь такой случай назван прямо, а причину пустой очереди подсказка предлагает искать выше очереди — в составе участников чата и в том, упоминают ли бота, если он отвечает только на упоминания.

Подсказка в ответе GET /v1/bots/{botId}/events после серии пустых опросов переписана по тому же разбору: она помечает eventMode как значение платформы и отправляет за авторитетным в resubscribe.

NEW-0924-16: вход в задеплоенное приложение по ключу десктопа Коворка

Новый эндпоинт POST /v1/cowork/app-login принимает адрес задеплоенного приложения и возвращает тот же адрес с добавленным параметром __gw_token и сроком его жизни expiresIn. Встроенный браузер Cowork/Code, открыв такой адрес, входит в приложение от имени владельца ключа, с его номером пользователя в Битрикс24, без формы авторизации. Токен открывает только это приложение. Эндпоинт принимает только ключ десктопа Cowork/Code со скоупом vibe:cowork. Неточный адрес приложения отклоняется кодом 400 INVALID_APP_URL, любой исход проверки доступа — единым кодом 404 APP_NOT_AVAILABLE, неизвестный номер пользователя в Битрикс24 — кодом 409 B24_USER_UNKNOWN.

BC-0924-17: методы списков проверяют ключ до чтения тела и без ключа отвечают 401

Поддержка старого формата до: не предусмотрена

Было

На всех методах /v1/lists/* ключ проверялся после того, как платформа прочитала и разобрала тело запроса. Поэтому запрос без ключа или с неверным ключом получал ответ о теле, а не о ключе: 400 INVALID_JSON_BODY на неразобранном JSON и 413 PAYLOAD_TOO_LARGE на теле больше 1 МБ, например на POST /v1/lists/:iblockId/elements. Сервер при этом читал тело целиком для любого отправителя.

Стало

Ключ проверяется первым, до чтения тела. Запрос без ключа или с неверным ключом на любом методе /v1/lists/* получает 401 независимо от размера и содержимого тела, а само тело не читается. Это закрывает возможность заставить сервер читать крупное тело без ключа — условие, при котором запись элемента смогла принять тело до 40 МиБ. Ответы на запросы с корректным ключом не изменились.

Что делать интеграторам

Ничего, если ключ передаётся. Если код различает ответы на запрос без ключа или с неверным ключом по кодам 400 или 413, заменить эту проверку на 401.

FIX-0924-18: файл в свойстве элемента списка больше не упирается в 1 МБ

Было

Значение свойства типа «Файл» у элемента списка едет в теле запроса как base64, а тело записи элемента было ограничено 1 МБ. Практический потолок одного файла — около 750 КБ: POST /v1/lists/:iblockId/elements и PATCH /v1/lists/:iblockId/elements/:elementId с файлом крупнее отвечали 413 PAYLOAD_TOO_LARGE, и запись не выполнялась. Тот же файл в поле сделки или контакта проходил.

Стало

Тело создания и обновления элемента списка ограничено 40 МиБ, как у записи сущностей CRM, — это чуть меньше 30 МиБ исходного файла после base64. Остальные методы списков и пакетные вызовы (POST /v1/batch) сохраняют прежний потолок 1 МБ, поэтому крупный файл отправляйте одиночным вызовом.

Запись элемента с телом крупнее 1 МБ делит общий лимит одновременно обрабатываемых крупных тел с записью сущностей CRM: когда он занят, приходит 429 LARGE_BODY_BACKEND_BUSY с заголовком Retry-After: 5, запрос не выполнялся, и его нужно повторить.

Влияние на интеграторов

Действий не требуется: запрос, который раньше получал 413, теперь выполняется. Вызов в Битрикс24 ограничен 15 секундами и не повторяется, поэтому файл у самой границы на медленном аккаунте может получить BITRIX_TIMEOUT — оставляйте запас.

2026-09-23

NEW-0923-1: управление подключением 1С через API Коворка

На российской площадке API Вайбкод добавляет 20 методов /v1/cowork/onec/* для статуса подключения, проверки прав администратора, выпуска и отзыва ключа, обновления каталогов и управления доступом. На международной площадке доступен только безопасный статус, который сообщает, что управление 1С недоступно в этом сегменте. Старый ключ Коворка может запросить нужные разрешения через /v1/connect/device/authorize и /v1/connect/token.

Результат listTools дополнен форматом "2" с полной нормализованной схемой входных данных инструмента. Формат "1" сохранён без изменений для ранее записанных операций и существующих клиентов.

На российской площадке поверхность исходно выключена и включается отдельно для портала после проверки готовности серверов. Безопасный статус международной площадки остаётся в состоянии UNAVAILABLE. Изменение не добавляет пользовательский интерфейс и не означает, что методы уже включены для порталов.

NEW-0923-2: выключение и включение уведомлений чата

Новый эндпоинт POST /v1/chats/:chatId/mute выключает ("mute": true) или включает обратно ("mute": false) уведомления из чата. Настройка личная: она меняется у того пользователя, от имени которого идёт вызов. Чтобы поменять её сотруднику, открывшему ваше приложение, вызывайте эндпоинт с ключом приложения и сессией этого сотрудника. Поле mute обязательно, запрос без него отклоняется кодом 400 MISSING_PARAMS до обращения в Битрикс24.

BC-0923-3: создание сделки или лида со стадией не из справочника больше не отвечает успехом

Поддержка старого формата до: не предусмотрена

Было

POST /v1/deals и POST /v1/leads со стадией, которой нет в справочнике аккаунта, отвечали 201: Битрикс24 принимал запись и молча клал её на стадию по умолчанию. Так вели себя опечатка в stageId, другой регистр букв, лишние пробелы, стадия другой воронки без её categoryId и стадия сделки в statusId лида. Клиент видел успех, а запись лежала не там, куда просили. Остальные двери — PATCH, /move, пакетные записи и импорт — такую стадию уже отклоняли.

Стало

Перед записью стадия сверяется со справочником аккаунта буквально, как её сверяет сам Битрикс24: у сделки — со стадиями воронки из categoryId (без него — основной, DEAL_STAGE, для другой воронки — DEAL_STAGE_{categoryId} со стадиями вида C{categoryId}:NEW), у лида — со статусами STATUS. Стадии в справочнике нет — ответ 400 UNKNOWN_STAGE, запись не создаётся. message называет ключ и справочник и подсказывает ближайшее точное написание либо categoryId, который нужно передать вместе со стадией другой воронки; details.knownStages перечисляет стадии справочника (до пятидесяти), details.entityId — какой справочник смотреть через GET /v1/statuses?filter[entityId]=…. Любое написание ключа, которое Битрикс24 читает как STAGE_ID или CATEGORY_ID (сырое STAGE_ID, stage_id, StageId), проверяется так же; при нескольких написаниях в теле действует последнее, и ошибка называет его в details.field. Список или объект вместо названия стадии под сырым написанием ключа — тоже 400 UNKNOWN_STAGE, потому что Битрикс24 кладёт такую запись на стадию по умолчанию (под stageId / statusId его раньше отклоняет проверка формы — 400 INVALID_PARAMS); отказ выносится без чтения справочника: details.knownStages пуст, details.stageId называет форму значения. Пустая стадия ("", null) по-прежнему не проверяется — запись ложится на стадию по умолчанию. Справочник читается один раз на аккаунт и воронку и хранится пять минут, поэтому обычное создание не стало дороже; если справочник прочитать не удалось или он пришёл неполным, запись выполняется как прежде. Стадия из справочника отвечает как раньше.

Что делать интеграторам

Проверьте, что стадии в ваших запросах совпадают со справочником буква в букву: GET /v1/statuses?filter[entityId]=DEAL_STAGE для основной воронки сделок, DEAL_STAGE_{categoryId} для остальных, STATUS для лидов. Стадию другой воронки передавайте вместе с её categoryId. Обрабатывайте 400 UNKNOWN_STAGE — повторять запрос с той же стадией бессмысленно, выберите одну из details.knownStages.

BC-0923-4: CRM_CREATE в конфигурациях открытых линий теперь принимает только поддерживаемые значения

Поддержка старого формата до: не предусмотрена

Было

POST /v1/openline-configs и PATCH /v1/openline-configs/:id возвращали 200 для значений contact, company и других неподдерживаемых значений crmCreate, после чего Битрикс24 сохранял режим как lead.

Стало

Для crmCreate и его вариантов написания принимаются только точные строки none, lead, deal. Любое другое значение возвращает HTTP 400 с кодом VALIDATION_ERROR и сообщением с полем и полным списком допустимых значений. Запрос отклоняется до изменения конфигурации.

Что делать интеграторам

Передавайте только none, lead или deal. Клиентам, которые отправляли contact или company, нужно выбрать поддерживаемый режим и обрабатывать 400 VALIDATION_ERROR.

FIX-0923-5: Проверка имён полей при чтении реквизитов

Было

Некоторые несуществующие имена полей, включая constructor, __proto__ и toString, ошибочно проходили проверку фильтрации и сортировки реквизитов.

Стало

Проверка полей теперь отклоняет унаследованные имена до обращения к Битрикс24: с 400 UNKNOWN_FILTER_FIELD для фильтра и 400 UNKNOWN_SORT_FIELD для сортировки. Порядок обработки запроса не меняется: разбор тела может отказать раньше проверки полей, а объект order по-прежнему имеет приоритет над sort. Допустимые поля и их поддерживаемые алиасы работают как прежде.

Затронутый эндпоинт: GET /v1/requisite-presets/:presetId/fields.

Влияние на интеграторов

Запросы с допустимыми именами полей менять не требуется.

FIX-0923-6: Повторное использование сервера после перепривязки ключа

Было

Для приложений с ранее утраченной привязкой ключа повторный POST /v1/infra/servers мог пытаться создать ещё один сервер. При конфликте перепривязки сервер и карточка могли остаться связаны с разными ключами.

Стало

Конфликт перепривязки сохраняет прежнюю связь сервера и карточки целиком. Создание распознаёт единственный подходящий сервер приложения, уже управляемый текущим ключом, и восстанавливает пустую привязку. Ответ при повторном использовании остаётся HTTP 201 с reused: true. При конкурентном изменении привязки возвращается HTTP 409 APPLICATION_REUSE_CONFLICT с retryable: true; запрос можно повторить. GET /v1/me показывает необязательный deployment.standalone.reuseTarget и checklist для существующего сервера, не меняя привязки. TRIAL_PORTAL_LIMIT содержит retryable: false и подсказку без повторного создания.

FIX-0923-7: Скобочная форма `order` и `scope` больше не даёт внутреннюю ошибку

Было

Запрос с параметром в скобочной форме, например ?order[x]=1 к GET /v1/tasks/:taskId/history или ?scope[x]=1 к POST /v1/pages/:id/publication, отвечал 500 вместо штатного ответа.

Стало

GET /v1/tasks/:taskId/history считает такой order неизвестным значением и сортирует по возрастанию, как без параметра; ответ остаётся HTTP 200. POST /v1/pages/:id/publication отклоняет такой scope с 400 INVALID_SCOPE и показывает полученное значение в тексте ошибки, как для любого другого недопустимого scope.

Влияние на интеграторов

Запросы со строковыми order и scope менять не требуется.

FIX-0923-8: диагностика galaxy-приложения проверяет публичный адрес

Было

Блок reachability в ответе GET /v1/infra/servers/:id собирал вердикт effectiveStatus только из состояния контейнера и маршрутизации на самой галактике. Оба факта измеряются на хосте, а последний участок пути до https://app-XXXX.vibecode.bitrix24.tech — это туннель приложения на шлюзе, и на хосте его не видно: запущенный юнит маршрутизации означает, что его запустили, а не что он дозвонился до шлюза. Приложение, чей публичный адрес снаружи не отвечал, поэтому могло приходить как "effectiveStatus": "running".

Стало

Вердикт учитывает публичный вход, и рядом приходит новое поле reachability.publicEntry — live, если у шлюза есть живой туннель на поддомен приложения, no-tunnel, если туннеля нет, и unknown, если состояние шлюза узнать не удалось. Приложение с живым контейнером и активной маршрутизацией, но без туннеля, отвечает "effectiveStatus": "unreachable". Значение unknown вердикт не ухудшает: «не смогли посмотреть» — это не «ничего не работает». Остальные значения effectiveStatus и все прежние поля блока не изменились, запросы править не нужно. Описание значений — Сон и пробуждение Galaxy-приложения.

FIX-0923-9: снятие доступа к серверу прекращает уже открытые сессии

Было

Пользователь, открывший приложение обычным путём, получал сессию браузера, и дальше она жила сама по себе: снятие его записи из списка доступа (DELETE /v1/infra/servers/{id}/access/{accessId}) на неё не влияло. Та же вкладка продолжала открывать приложение, а при активности пользователя срок сессии продлевался — то есть доступ сохранялся неограниченно долго. Отказ приходил только новым входом.

Стало

Право на вход перепроверяется и у уже открытой сессии. После снятия доступа — записью пользователя, отделом или сменой политики — приложение перестаёт открываться в течение минуты, и сессия больше не продлевает себя.

Режим «только владелец» — исключение: часть уже открытых сессий доживает до конца своего срока, до 40 минут, и продлить себя они больше не могут. Рассчитывайте на этот срок, если переводите сервер в этот режим ради немедленного закрытия доступа.

Влияние на интеграторов

Действий не требуется. Доступ по ссылке-приглашению работает как прежде: он не подчиняется списку доступа сервера и отзывается отдельно, отзывом самой ссылки.

FIX-0923-10: Устаревшие GET-агрегаты сообщают о неполных данных

Было

GET /v1/{entity}/aggregate мог вернуть HTTP 200 с числами по части записей без признака неполноты.

Стало

Ответ остаётся HTTP 200. Прежние числовые поля и группировка сохраняются. В data.meta всегда присутствуют recordsProcessed и truncated. При известном общем числе записей добавляется totalRecords, при положительном недоборе — recordsShortfall, при ошибке страницы — безопасный образец pageErrorSample с code и message.

Частичный результат содержит truncated: true в data, каждом элементе data.groups и data.total, если есть группировка. Пометка группы означает неполноту общей выборки, а не доказанную потерю строк именно в этой группе. Добавляется data.meta.warnings с кодом AGGREGATE_TRUNCATED. В полном ответе условные пометки и предупреждение отсутствуют, а data.meta.truncated равно false.

Если общее число неизвестно, totalRecords и recordsShortfall отсутствуют. Явный признак неполноты сохраняется даже без количественной оценки потери. Операция count на этом GET-пути также считает прочитанные строки и может быть частичной. Прежний отказ HTTP 422 при превышении лимита в 10 000 записей и заголовки устаревания сохраняются.

Влияние на интеграторов

Проверяйте data.meta.truncated, прежде чем считать числа окончательными. При частичном результате сузьте фильтр. Контракт POST-агрегации не изменился.

BC-0923-11: fieldName при создании пользовательского поля проверяется до обращения к Битрикс24

Поддержка старого формата до: не предусмотрена

Было

POST /v1/userfields/:entity отправлял fieldName в Битрикс24 каким получил, не проверяя его. Пустая строка, null, число, массив или объект доезжали до портала, и что с ними будет, решала уже та сторона: пустое имя возвращалось ошибкой без кода — наружу 422 BITRIX_ERROR с b24Code: "0", по которому нельзя понять, что именно в запросе не принято, — а не-строковое значение могло быть приведено к строке и создать поле с мусорным именем под успешным ответом.

Стало

Значение проверяется до обращения к порталу: оно обязано быть строкой, в которой есть хотя бы один непробельный символ, иначе отказ 400 INVALID_FIELD_NAME. Проверяется именно то значение, которое уедет в Битрикс24, — если тело несёт оба написания, fieldName и прямой аналог FIELD_NAME, в портал уходит последнее из них, его и судит проверка. То же правило распространилось на обязательный userTypeId, и для тел с ОДНИМ написанием не изменилось ничего: пропущенный или пустой тип, как и прежде, отбивается 400 MISSING_FIELD. Разница только там, где тело несёт оба написания сразу — прежде судилось первое непустое, теперь то, которое уезжает в Битрикс24. Тело {"userTypeId": "string", "USER_TYPE_ID": ""} раньше доезжало до портала и возвращало его непрозрачный ответ, а теперь получает 400 MISSING_FIELD; обратное тело {"userTypeId": "", "USER_TYPE_ID": "string"} раньше отбивалось 400 MISSING_FIELD, а теперь создаёт поле с типом string. Формат имени по-прежнему за Битрикс24: префикс UF_CRM_, длину и набор символов проверяет он.

Что делать интеграторам

Вызовы с корректным fieldName не затронуты, как и вызовы вообще без этого ключа — там имя поля генерирует Битрикс24. Менять код нужно тем, кто подставлял в fieldName не строку, пустую строку или строку из одних пробелов: такой вызов теперь отвечает 400 INVALID_FIELD_NAME вместо прежнего ответа. Под это попадает и имя из необрезанного пользовательского ввода или из ячейки выгрузки — обрезайте пробелы на своей стороне. Чтобы имя сгенерировала платформа, ключ не передают вовсе: пустая строка и null для этого не годятся.

NEW-0923-12: карточка сервера отдаёт команду запуска, установки и порт последнего деплоя

Ответ GET /v1/infra/servers/:id дополнен тремя полями: startCommand, installCommand, deployPort — снимок start/install/port из тела последнего УСПЕШНОГО POST /v1/infra/servers/:id/deploy. Для серверов без единого успешного деплоя все три поля возвращают null. Участник команды разработки (access.via: "collaborator") получает эти поля так же, как владелец.

NEW-0923-13: в отказе по активным серверам приложения появился владелец сервера

В ответе 409 APP_HAS_ACTIVE_SERVERS на DELETE /v1/apps/:id каждый элемент details.servers получил необязательное поле ownerName — имя владельца сервера либо null, если владельца у сервера нет. Раньше по такому ответу нельзя было понять, чей именно сервер мешает удалить приложение, и владельца искали вручную. Остальные поля элемента и код ошибки прежние, запросы старых клиентов работают как работали.

BC-0923-14: повтор создания приложения Коворка после сетевого сбоя возвращает исход первой попытки — если он ещё свежий

Поддержка старого формата до: не предусмотрена

Было

POST /v1/cowork/applications после отказа при выписке ключа (например, недоступность Битрикс24 Нетворк на стадии обращения к порталу) навсегда занимал ключ идемпотентности: повтор с тем же Idempotency-Key отвечал общим 409 IDEMPOTENCY_KEY_ALREADY_USED независимо от причины первого отказа, и восстановить заготовку было нельзя — но повтор тем же ключом гарантированно никогда не создавал дубликат: он либо реплеился в 409, либо не проходил вовсе.

Стало

Такой повтор в течение 20 минут после первого отказа получает ТОТ ЖЕ ответ, что и первая попытка — тот же HTTP-статус, тот же error.code, то же тело — с добавленным заголовком Idempotent-Replayed: true. Спустя 20 минут ключ идемпотентности забывается, и повтор с ним выполняется как совершенно новая попытка со всеми проверками заново. Если первый отказ произошёл ПОСЛЕ обращения к порталу (например, 502 BITRIX_UNAVAILABLE), портал мог уже получить вебхук от первой попытки; повтор тем же ключом спустя 20 минут этого не проверяет и может выписать ВТОРОЙ ключ и создать вторую заявку поверх первой. 409 IDEMPOTENCY_KEY_ALREADY_USED теперь означает только «ключ принадлежит приложению, которое с тех пор удалили» — постоянный отказ, как и раньше.

Что делать интеграторам

Код IDEMPOTENCY_KEY_ALREADY_USED для сценария «первая попытка дошла до Битрикс24 и там не удалась» клиенты больше не увидят: вместо него в окне 20 минут приходит исходный отказ (например, 502 BITRIX_UNAVAILABLE) с заголовком Idempotent-Replayed: true. Повторяйте тем же Idempotency-Key только ВНУТРИ этого окна — там реплей детерминирован и безопасен. Спустя 20 минут после первого отказа смена значения Idempotency-Key от дубля не защищает: что старым, что новым ключом повтор выполняется как совершенно новая попытка, и если первый отказ случился после обращения к порталу, она может выписать второй ключ поверх первого. На платформе при этом, как правило, нечего проверить — заявка на бронь удаляется вместе с исходом отказа, а ключ на большинстве стадий отказа не чеканится вовсе; возможный дубль в этом случае ищите в самом портале Битрикс24, если это возможно, и считайте риск частью цены повтора спустя окно. Исключение — 500 APPLICATION_CREATE_FAILED: если ключ уже выписан, а карточку записать не удалось, лишний ключ можно найти и отозвать в разделе ключей кабинета платформы; пустой список значит, что чистить нечего. Ветвление по error.code менять не нужно; полагаться на постоянство IDEMPOTENCY_KEY_ALREADY_USED для отказов ПОСЛЕ обращения к порталу больше нельзя.

2026-09-22

BC-0922-1: пакет одной сущности листает списки: offset в подвызове наконец читается

Поддержка старого формата до: не предусмотрена

Было

Списочный подвызов POST /v1/{entity}/batch игнорировал offset: параметр не извлекался из params, на сторону Битрикс24 не уезжал и клиентским окном не применялся. Два подвызова, отличающиеся только смещением, возвращали HTTP 200 и одни и те же записи — листать пакетом одной сущности было невозможно, тогда как общий POST /v1/batch и одиночные GET /v1/{entity} и POST /v1/{entity}/search смещение читали. У сущностей, чей метод Битрикс24 навигацию не поддерживает, тот же подвызов тоже отвечал HTTP 200 — первой порцией записей при любом смещении, то есть молча не тем окном, которое просил клиент.

Стало

Подвызов читает offset и отдаёт честное окно [offset, offset + limit) — тем же разбором, что общая дверь: смещение выравнивается под страницу Битрикс24, остаток снимается с головы на нашей стороне, а у сущностей, чей метод навигацию не применяет (statuses, deal-categories, bizproc-activities, bizproc-robots) и у тех, что отдают весь набор одним ответом, окно режется по полной коллекции. Ответ подвызова теперь несёт hasMore рядом с total, а вырожденное окно приезжает предупреждением OFFSET_BEYOND_FETCHED_PAGE в meta этого же подвызова.

Ломающая часть одна: сущность, чей метод Битрикс24 смещение принять не может (сегодня это telephony-lines), на подвызов с offset больше нуля отвечает ошибкой UNSUPPORTED_OFFSET вместо прежних HTTP 200 и первой порции записей. Отказ пер-вызовный — соседние подвызовы пакета выполняются, и сам пакет по-прежнему отвечает HTTP 200. Вместе со смещением подвызов начал соблюдать и limit: страница, которую Битрикс24 вернул длиннее запрошенной, режется по limit (по умолчанию 50), как на остальных дверях. Раньше такой подвызов отдавал всё, что прислал портал, — с окнами это означало бы, что страницы разных смещений перекрываются и клиент читает одни записи дважды.

Что делать интеграторам

  1. Уберите offset из подвызовов к telephony-lines: смещение этой сущности недоступно, и ответ UNSUPPORTED_OFFSET называет это прямо. Нужна выборка — сужайте её фильтром.
  2. Если код рассчитывал на страницу длиннее запрошенной — передавайте нужное число в limit явно, до 5000 записей.
  3. Листайте по hasMore из ответа подвызова, а не по арифметике от total.

NEW-0922-2: два новых кода отказа для ключей рантайма агента

Ключ рантайма агента теперь получает два кода отказа, которых раньше не было. Пока ход агента ждёт подтверждения пользователя, POST /v1/chat/completions отвечает 409 с кодом agent_turn_awaiting_confirmation: обращения к модели по такому ходу не выполняются, поэтому ожидание ответа человека больше не расходует лимит. Код снимается, как только пользователь подтвердил или отменил операцию, а также по истечении срока ожидания.

Вызов DELETE к V1 от того же ключа, совпавший с предыдущим по методу и адресу в пределах окна в 30 секунд, отвечает 409 с кодом AGENT_WRITE_ALREADY_EXECUTED. Такой вызов на портал не уходит, а в поле error.firstStatus возвращается код ответа первого удаления и в error.firstExecutedAt — его время. Неуспешное первое удаление повтор не запирает: ретрай проходит обычным порядком. Создание и обновление дедупликации не подлежат.

Ключей, не принадлежащих рантайму агента, ни один из двух кодов не касается, и прежние успешные ответы не изменились.

NEW-0922-3: приём результата долгого метода решения

Появился публичный маршрут POST /v1/solution-calls/:callId/result, куда решение присылает результат асинхронного вызова метода по контракту (solution.invoke с mode: async). Адрес, одноразовый reply-токен и срок приходят решению вместе с самим вызовом в заголовках X-Vibe-Reply-Url, X-Vibe-Reply-Token и X-Vibe-Reply-Deadline; платформа шлёт вызов с Prefer: respond-async — решение отвечает 202 и присылает результат сюда либо отвечает 200 с тем же телом сразу. Аутентификация — Authorization: Bearer vcr_…, ключ платформы на этом маршруте не принимается. Тело — {"outcome":"completed","result":{…}} либо {"outcome":"failed","error":{"code","message","problem"}}; ответ 200 с data.state. Ошибки: 401 REPLY_TOKEN_INVALID (токен не подошёл ни к одному открытому вызову; существование callId не раскрывается), 409 SOLUTION_CALL_CLOSED (вызов уже закрыт, error.state несёт его состояние), 422 RESULT_INVALID (тело больше 512 КиБ, не JSON нужной формы или result не по схеме того снимка контракта, по которому вызов принят). Ответ 422 закрывает вызов как failed — повторять с тем же телом бессмысленно. Принятый исход платформа доставляет на портал Битрикс24 сама. Прежние вызовы и ответы не меняются: до этого релиза mode: async отвечал FEATURE_DISABLED, и на порталах без включённой возможности ответ остаётся прежним.

FIX-0922-4: отказ Битрикс24 «не передан параметр» отвечает 400, а не 422

Было

Когда Битрикс24 отклонял вызов до запуска метода — потому что вызов не принёс параметр, который метод объявляет обязательным, — портал отвечал Could not find value for parameter {…}, а платформа Вайбкод передавала это наружу как 422 BITRIX_ERROR, то есть как бизнес-ошибку портала. Имя недостающего параметра лежало только в тексте сообщения, подсказки не было, и повтор того же запроса возвращал тот же отказ — на списке реквизитов (GET /v1/requisites) так повторялись 92 вызова на пяти порталах за три недели.

Стало

Тот же отказ приходит как 400 INVALID_PARAMS — код той же группы, что и остальные отклонения параметров со стороны Битрикс24, — а error.hint называет параметр, значения которого не хватило, и говорит, что повтор без изменения вызова не поможет. Ответ на любой запрос, который Битрикс24 принимает, не изменился.

Влияние на интеграторов

Клиент, который различал этот случай по 422, увидит 400 INVALID_PARAMS. Ответ по-прежнему несёт текст Битрикс24 в error.message, а имя параметра теперь ещё и в error.hint. Отказы, отклонённые проверками самой платформы до обращения к порталу, как и раньше отвечают 400 MISSING_REQUIRED_PARAMS.

FIX-0922-5: смена адреса портала: вызов отвечает 409 `PORTAL_ADDRESS_CHANGED` вместо 500

Было

После смены адреса портала в Битрикс24 вебхук выданного ключа оставался выписан на прежний адрес. Платформа отбивала такой вызов до отправки — секрет не уходит туда, где портала больше нет, — но отказ был безымянным: любой проксируемый запрос отвечал 500 INTERNAL_ERROR без кода и без подсказки, а в батч-конверте подвызов уезжал в общий бакет CALL_FAILED. Причину и путь восстановления клиент из ответа узнать не мог.

Стало

Отказ называет и причину, и действие: 409 с кодом PORTAL_ADDRESS_CHANGED, подсказка ведёт на переподключение ключа — в разделе «Ключи» кнопка «Переподключить»; строка ключа и привязанные к нему боты при этом сохраняются. Тот же код с той же подсказкой едет в подошибке батч-конверта. Срока ожидания отказ не несёт намеренно: повтор не поможет, пока вебхук ключа не перевыпущен.

FIX-0922-6: удаление приложения освобождает ключ авторизации в карточке

Было

DELETE /v1/apps/{id} снимал приложение и отзывал парные ключи, но карточка приложения продолжала считать ключ авторизации выданным. Выдать новый ключ было нельзя — дверь выдачи отвечала 409 APPLICATION_HAS_AUTH_KEY, а перевыпуск — 409 APPLICATION_NO_AUTH_KEY. Тем же следствием POST /v1/cowork/applications/{id}/key возвращал warningCodes со значением APPLICATION_HAS_AUTH_KEY для приложения, у которого ключа авторизации уже не было.

Стало

Удаление снимает и привязку: после него приложение считается без ключа авторизации, ложный APPLICATION_HAS_AUTH_KEY в warningCodes по такому приложению больше не приходит, а выдача нового ключа проходит штатно. Успешный ответ самого удаления не изменился — это по-прежнему HTTP 204.

FIX-0922-7: схема агрегации в OpenAPI описывает поля так же, как их принимает API

Правка касается ОПИСАНИЯ в GET /v1/openapi.json; поведение POST /v1/{entity}/aggregate не меняется, ответы прежние.

Было

Поле aggregate[].field публиковалось закрытым списком из "*" и полей группировки сущности. Список группировки и поля, по которым считаются sum/avg/min/max, — разные множества, поэтому клиент, проверяющий тело запроса по спеке, не отправлял вызовы, которые API выполняет: агрегацию по числовому полю сущности вне списка группировки (у сделок это probability, taxValue, receivedAmount и другие) и по пользовательскому полю портала типа integer, double или money. Одновременно спека разрешала пары, на которые API всегда отвечает 400 INVALID_PARAMS: count по именованному полю и числовую функцию по "*" или пустому имени. Поле groupBy публиковалось таким же закрытым списком и не допускало пользовательских полей, которые API принимает.

Стало

aggregate[].field описан в паре с function: count считается по "*", а sum/avg/min/max принимают имя числового поля — как объявленного в сущности, так и пользовательского поля портала. Числовые поля сущности перечислены в examples как подсказка, а не как ограничение: набор пользовательских полей зависит от портала и в общей спеке не публикуется, актуальный список отдаёт GET /v1/{entity}/fields. Пары, на которые API всегда отвечал отказом, схема теперь тоже отвергает — включая агрегацию по полю, которое сущность объявляет нечисловым: такое имя API отклоняет независимо от того, что настроено на портале. groupBy допускает пользовательские поля и отвергает имена, совпадающие с ключами ответа (count, aggregates, meta, groups), — их API не принимал никогда. Текстовое описание операции больше не выдаёт список группировки за список полей агрегации: теперь оно называет обе роли раздельно.

FIX-0922-8: подняты дефолтные лимиты на пользователя — ключи, серверы, боты

Было

На портале, где эти лимиты ни разу не настраивали вручную, по умолчанию действовали ограничения на пользователя: 10 API-ключей, 5 серверов, 5 ботов.

Стало

Дефолты подняты до 100 ключей, 50 серверов и 50 ботов на пользователя. Изменение затрагивает только порталы, которые не задавали свои значения; портал с собственной настройкой сохраняет её без изменений. Существующие запросы работают по-прежнему — доступного объёма стало больше.

BC-0922-9: Повтор незавершённой ротации ключа требует восстановления

Поддержка старого формата до: не предусмотрена

Было

После неполного отката замены ключа запрос с новым Idempotency-Key мог ответить 201 для осиротевшего кандидата, хотя отзыв токенов исходного ключа не был подтверждён.

Стало

Повторная ротация исходного или нового ключа отвечает 409 KEY_ROTATION_RECOVERY_REQUIRED. Замена ключа приложения и назначение такого ключа серверу отвечают 409 APPLICATION_KEY_RECOVERY_REQUIRED. Новый Idempotency-Key не обходит отказ. Выдача токена доступа при незавершённой ротации также отвечает 409 KEY_ROTATION_RECOVERY_REQUIRED, а если сервер уже сменил ключ — 403 SERVER_KEY_MISMATCH, без нового токена. Передача бота с исходным или целевым ключом в состоянии незавершённой ротации отвечает 400 TARGET_KEY_INVALID с error.reason: recovery_required, без изменения владельца. Параллельная передача того же портала отвечает существующим 409 BOT_TRANSFER_CONFLICT вместо непрозрачного 500; привязка не меняется.

Затронутые эндпоинты: POST /v1/keys/{id}/rotate, DELETE /v1/keys/{id}, POST /v1/cowork/applications/{id}/key, POST /v1/infra/servers, POST /v1/infra/servers/{id}/access-tokens, POST /v1/bots/{botId}/transfer.

Неподтверждённый отзыв отвечает 500 KEY_ROTATE_REVOKE_FAILED без сырого секрета. Кандидат указан в error.details.orphanKeyId, остаток отката — в error.details.strandedSlots. Непустой остаток требует поддержки. Отсутствующий или пустой остаток сам по себе не разрешает повтор. Ничем не используемый кандидат удаляется только штатным DELETE-эндпоинтом после подтверждения, что на него больше не ссылаются другие ресурсы.

Влияние на интеграторов

Не повторяйте запрос с новым ключом идемпотентности. Завершите восстановление через поддержку либо подтвердите штатное удаление пустого кандидата, затем перечитайте актуальное состояние ключа и сервера перед новым запросом. Поддержка прежнего поведения не предусмотрена.

FIX-0922-10: служебные имена JavaScript больше не подменяют значение параметра

Было

Имена, которые есть у любого объекта JavaScript — constructor, toString, valueOf, hasOwnProperty, — при сверке со списком допустимых значений считались найденными в этом списке. Значением оказывалась служебная функция, и дальше поведение расходилось по ручкам:

  • GET /v1/userfields/constructor — отказ 400 UNKNOWN_ENTITY не приходил, вместо него возвращалась ошибка Битрикс24;
  • GET /v1/requisite-links?filter[constructor]=… и ?sort=constructor — вместо 400 UNKNOWN_FILTER_FIELD / 400 UNKNOWN_SORT_FIELD в Битрикс24 уходило мусорное имя поля;
  • GET /v1/openline-configs?filter[constructor]=… и ?sort=constructor — мусорное имя поля вместо ожидаемого CONSTRUCTOR; отказа по неизвестному полю у этой ручки нет и не было;
  • POST/PATCH /v1/userfields/:entity — поле с таким именем уезжало в Битрикс24 под подменённым именем вместо передачи как есть; то же — у внутренних ключей элементов списка (list);
  • POST /v1/users/invite — то же подменённое имя вместо передачи как есть;
  • model: "constructor" в запросах к AI подменял имя модели служебным значением.

Стало

Список допустимых значений проверяется по собственным ключам, поэтому служебные имена в нём не находятся. Там, где ручка отвечает отказом на неизвестное значение, этот отказ приходит до обращения к Битрикс24; там, где отказа нет, имя поля передаётся как есть.

NEW-0922-11: билет десктопа Коворк/Код для телефонного доступа

Новый метод POST /v1/cowork/relay-ticket выдаёт десктопному приложению Коворк/Код короткоживущий подписанный билет, по которому relay телефонного доступа пускает десктоп. В теле передаётся pubkey — публичный ключ Ed25519 десктопа (32 байта, base64url без паддинга). Ответ 200 содержит ticket (JWT с алгоритмом EdDSA, живёт 5 минут) и expiresAt. Метод доступен только ключу десктопного приложения со скоупом vibe:cowork и только при активном месте Коворк/Код.

Коды отказов: 403 INSUFFICIENT_SCOPE (нет скоупа vibe:cowork), 403 COWORK_DESKTOP_KEY_REQUIRED (ключ не десктопного приложения), 404 COWORK_NOT_ACTIVATED (нет места), 403 COWORK_SEAT_INACTIVE (место не активно), 400 INVALID_PUBKEY (неверный ключ), 429 (не больше 30 билетов в час на человека), 503 COWORK_FEATURE_DISABLED и 503 COWORK_RELAY_NOT_CONFIGURED (выдача билетов временно недоступна).

FIX-0922-12: политика «только владелец» работает одинаково на всех входах в приложение

Было

При политике OWNER_ONLY пользователь с записью в списке доступа открывал приложение через плейсмент внутри Битрикс24, но получал отказ по прямой ссылке. Документация при этом обещает, что при OWNER_ONLY записи списка доступа не применяются: сценарий «вернуть приватность» — перевести сервер обратно в OWNER_ONLY и оставить записи в базе — на деле закрывал доступ не для всех.

Стало

Вход один и тот же на всех путях: при OWNER_ONLY приложение открывает только владелец, а записи списка доступа не применяются, как и написано в документации. Чтобы снова открыть доступ конкретным людям, переключите политику на NAMED_USERS — записи уже в базе и начнут действовать сразу.

Влияние на интеграторов

Если ваш сценарий опирался на то, что человек из списка доступа входит через плейсмент при OWNER_ONLY, переключите сервер на NAMED_USERS: набор людей останется прежним.

FIX-0922-13: приложению в галактике снова можно задать расписание пробуждения

Было

Приложению внутри галактики — в том числе AI-агенту — нельзя было задать окно расписания: POST /v1/infra/servers/{id}/wake-schedules отвечал 400 ALWAYS_ON_CONFLICT, как серверу, который владелец оплатил на круглосуточную работу. На самом деле такой выбор никто не делал: тариф приложения наследуется от галактики, а галактике доступны только тарифы без прерываний, поэтому признак «работает круглосуточно» выполнялся сам собой. Вместе с тем, что агенту недоступен сон по бездействию, у владельца не оставалось способа остановить работу на ночь — при том, что оплата шла круглосуточно.

Стало

Тариф, унаследованный от галактики, больше не считается выбором круглосуточного режима: окна расписания приложению в галактике задаются обычным порядком. У отдельного сервера, которому владелец сам выбрал тариф без прерываний и отключил сон, поведение прежнее — 400 ALWAYS_ON_CONFLICT.

Влияние на интеграторов

Действий не требуется. Запрос, который раньше отклонялся для приложения в галактике, теперь создаёт окно; ответ GET /v1/infra/servers/{id}/logs у спящего приложения больше не сообщает об отказе окна.

NEW-0922-14: в библиотеке расписаний работы появились три новые готовые заготовки

GET /v1/work-schedules отдаёт три дополнительных готовых расписания — weekdays-9-19, weekdays-9-20 и weekdays-8-19 (будни, понедельник–пятница). Раньше готовых было три, и, если рабочая неделя аккаунта в них не попадала, оставалось заводить своё расписание через POST /v1/work-schedules, даже когда отличие было в одном часе.

Новые заготовки заводятся в аккаунте при первом чтении библиотеки, как и прежние, и так же не редактируются: у заготовки canEdit: false, а попытка правки отвечает WORK_SCHEDULE_PRESET_READONLY. Назначать их машинам платформа сама не станет — они лишь появляются в списке.

Прежние ключи и их окна не менялись, уже назначенные машины остались на своих расписаниях.

Набор заготовок может пополняться и дальше, поэтому незнакомый presetKey стоит считать заготовкой без локальной подписи, а не ошибкой. Различать заготовки надёжнее по presetKey, а не по name: имя зависит от языка ключа.

2026-09-21

FIX-0921-1: пример пакетного чтения почтовых ящиков показывает подвызов

Было

Карточка справочника для POST /v1/mail/mailboxes/batch печатала тело {"action":"list"} — без массива calls. Скопированный как есть, такой вызов не выполнял ни одного подвызова и возвращал пустой результат, который читался как «у портала нет ящиков», хотя ящики были. Параметры подвызова эта дверь принимает только внутри calls[].params, и передать их было некуда. В машинной спецификации подвызов был описан как произвольный объект, а признак того, поднимает ли дверь обязательные параметры подвызова, не публиковался вовсе.

Стало

Пример показывает рабочую форму: {"action":"list","calls":[{"params":{"limit":5}}]}. Спецификация описывает элемент calls полем params и публикует расширение x-batch-list-lifts-params, как у соседних пакетных дверей. Ответы операции и её поведение не изменились: адрес, тело запроса, коды ответов и требуемое право прежние. В спецификации операция переехала из группы mail в группу mail-mailboxes — к ней же относятся остальные операции почтовых ящиков. Если вы генерируете клиента по GET /v1/openapi.json, при следующей генерации изменится имя класса, в который попадает этот метод; сам вызов менять не нужно.

FIX-0921-2: генерация изображений учитывает output_format и negative_prompt

Было

POST /v1/images/generations принимал output_format и negative_prompt, но сервис генерации их не получал. Запрос с output_format: "jpeg" возвращал картинку в PNG, и поле output_format в ответе тоже приходило равным png. Запрос с negative_prompt давал ту же картинку, что и без него.

Стало

Оба поля доходят до сервиса генерации. output_format: "jpeg" возвращает JPEG, "webp" — WebP, и поле output_format в ответе называет фактический формат. Без этого поля формат по-прежнему PNG. negative_prompt влияет на результат.

Влияние на интеграторов

Менять ничего не нужно. Если вы присылали output_format: "jpeg" и рассчитывали на PNG в ответе, читайте формат из поля output_format ответа, а не считайте его постоянным.

FIX-0921-3: `/exec` больше не портит многобайтные символы в выводе

Было

В ответе POST /v1/infra/servers/:id/exec, а также в потоковом режиме ?stream=true, многобайтный символ UTF-8, попавший на границу внутреннего буфера чтения, приходил как �: например — мог прийти как —��, а следующий фрагмент — начаться с �. Однобайтный вывод это не затрагивало.

Стало

Вывод собирается по границам символов: неполный символ на стыке фрагментов удерживается и дописывается следующим фрагментом, поэтому stdout и stderr доходят без порчи и в потоковом, и в JSON-режиме. Ответ остаётся HTTP 200, exitCode не меняется.

FIX-0921-4: внешний API приложений переживает короткое переподключение туннеля

Было

Вызов ANY /v1/applications/:id/api/*, который попадал в короткое окно переподключения туннеля приложения, мог сразу получить 503 APP_API_UNAVAILABLE с Retry-After, даже если приложение становилось доступно через несколько секунд.

Стало

В таком окне платформа Вайбкод ненадолго удерживает вызов и, если туннель успевает вернуться в общий бюджет запроса, доставляет его в приложение. Если туннель не вернулся вовремя, ответ остаётся 503 APP_API_UNAVAILABLE с Retry-After.

FIX-0921-5: схема 403 на пишущих операциях допускает все опубликованные коды

Было

Пишущие операции V1, которые документируют отказ WRITE_BLOCKED_READONLY_KEY, тем же описанием называли соседние коды 403: SCOPE_DENIED, отказы общего гейта ключа и отказы Битрикс24. При этом схема тела закрывала error.code одним значением WRITE_BLOCKED_READONLY_KEY, поэтому SDK, собранный по OpenAPI, считал остальные коды невозможными.

Стало

Схема этого ответа допускает стандартный V1-конверт { success: false, error: { code, message } } рядом со строгой веткой WRITE_BLOCKED_READONLY_KEY. Типизированные details для READONLY-отказа сохранены, а остальные опубликованные коды 403 больше не запрещены схемой.

FIX-0921-6: polling бота с остановленной подпиской возвращает пустую очередь

Было

GET /v1/bots/{botId}/events для бота, остановленного из-за неактивной подписки Маркетплейса Битрикс24, возвращал 403 B24_MARKET_SUBSCRIPTION_REQUIRED. Клиенты, которые не учитывали этот отказ, могли продолжать частый polling.

Стало

Для такого бота GET /v1/bots/{botId}/events возвращает успешный ответ с пустым списком событий, hasMore:false, сохранённым offset и nextPollAfterMs:60000 — опрашивайте с этим темпом. На запрос с withUserEvents=true поле темпа не приходит, как и в остальных режимах. Остальные операции с остановленным ботом продолжают получать 403 B24_MARKET_SUBSCRIPTION_REQUIRED до восстановления подписки.

BC-0921-7: Поле, которого нет в описании сущности, проверяется на записи по типу из Битрикс24

Поддержка старого формата до: не предусмотрена

Было

Проверка формы значения на записи охватывала только поля, объявленные в описании сущности. Поле, которое сущность отдаёт при чтении, но не объявляет (utmSource и locationId у сделок, birthdate, honorific и categoryId у контактов, employees у компаний, statusDescription у лидов), и пользовательские поля UF_* она не видела: {"locationId": {"a": 1}} на POST /v1/deals отвечал 201, а в карточке сохранялась строка Array; {"categoryId": "abc"} у контакта сохранялся как 0, а {"categoryId": {"a": 1}} — как 1, и контакт переезжал в другую категорию; объект в birthdate сохранялся пустым. Так вели себя все семь дверей записи — одиночные POST и PATCH, пакетная запись по одной сущности и общая, импорт.

Стало

Такое поле проверяется по тому, что о нём сообщает сам Битрикс24 в своём списке полей: объект или массив в поле строкового, числового, логического или датного типа портала и нечисловая строка в числовом отклоняются 400 с кодом INVALID_PARAMS до записи в Битрикс24, а сообщение называет поле и тип, которым портал его объявил. Пустая строка в поле-ссылке (сотрудник, контакт, компания, лид, сделка) и в пользовательском числовом поле не отклоняется — так Битрикс24 снимает привязку и очищает значение; в категории она отклоняется, потому что переносит запись в категорию 0. Поля, которые портал объявляет многозначными или только для чтения, неизменяемые после создания поля на обновлении, поля типов вне перечисленных (файл, перечисление, деньги), имена, которых в списке портала нет, и сущности без признака многозначности в списке полей (задачи) не проверяются — как прежде. Список полей запрашивается только когда в теле есть такое имя, и держится в памяти пять минут; если Битрикс24 его не отдал, запись проходит как раньше, и ближайшие тридцать секунд список перед записью не запрашивается повторно. Границы — в разделе INVALID_PARAMS.

Что делать интеграторам

Проверьте места, где в поля из списка GET /v1/<entity>/fields, отсутствующие в описании сущности, значения собираются структурой или строкой из внешней системы: раньше такой запрос отвечал успехом, а данные терялись или искажались, теперь он честно вернёт ошибку с указанием поля. Значения верного типа проходят без изменений.

BC-0921-8: схемы контакта и сделки достроены: двенадцать полей ответа объявлены только для чтения, тринадцать служебных и пустых ключей убраны

Поддержка старого формата до: не предусмотрена

Было

GET /v1/contacts/{id} (а также список, поиск, include и перечитанная запись в ответах на создание и изменение) отдавал восемь ключей, которых не было ни в схеме, ни в справочнике, ни в машинном описании OpenAPI: phoneWork, phoneMobile, phoneMailing, imol, address, shortName, login и entityTypeId. GET /v1/deals/{id} — семнадцать: isWon, isLose, isWork, hasProducts, receivedAmount, lostAmount, begindateShort, closedateShort, dateCreateShort, dateModifyShort, eventDateShort, eventId, eventDate, eventDescription, orderStage, productId и entityTypeId. Явный select любого из них отвечал 400 UNKNOWN_SELECT_FIELD, сортировка у контактов — 400 UNKNOWN_SORT_FIELD, а у сделок ?sort= по любому из них уходил в Битрикс24 как есть (200, по entityTypeId — сырой 422 BITRIX_ERROR). Значение, присланное в любой из этих ключей при создании, изменении, импорте или в пакете, Битрикс24 не хранил ни на одной двери, но платформа отвечала успехом (201/200): создание и изменение — с предупреждением UNRECOGNIZED_WRITE_FIELD, импорт — молча. Незаполненное значение любого из шести полей контакта (телефоны, контакт открытой линии, адрес, краткое имя) приходило пустой строкой, а не null, как у описанных полей.

Стало

У контакта объявлены шесть полей, все только для чтения: phoneWork, phoneMobile, phoneMailing (телефоны из мультиполя phone по типам), imol (контакт открытой линии), address (старая колонка адреса — заполняет только legacy crm.contact.add, адрес правится в реквизитах) и shortName (фамилия с инициалами, вычисляет Битрикс24). У сделки — шесть полей, которые Битрикс24 вычисляет, все только для чтения: isWon, isLose, isWork (флаги по смысловой категории стадии), hasProducts (есть ли товарные позиции), receivedAmount и lostAmount (сумма сделки в учётной валюте портала — пересчитанная по курсу, если валюта сделки другая, — пока она выиграна или проиграна, иначе 0). Все двенадцать описаны на странице полей контакта и странице полей сделки, в GET /v1/contacts/fields и GET /v1/deals/fields, в справочнике и в машинном описании; типы по живому замеру. Явный select этих полей работает; фильтр и сортировка по phoneWork, phoneMobile, phoneMailing, address, shortName и по receivedAmount/lostAmount работают (по imol — принимаются), receivedAmount/lostAmount принимаются и в sum/avg/min/max POST /v1/deals/aggregate; четыре флага сделки в filter не принимаются (400 UNKNOWN_FILTER_FIELD) — булев фильтр уходит в Битрикс24 как Y/N, а портал сравнивает эти флаги только с булевым значением: по трём флагам стадии строка Y даёт обратный набор (фильтруйте по stageSemanticId), по hasProducts не совпадает ни с одной записью (замены в фильтре нет); сортировка по флагам работает. Шесть вычисляемых полей сделки и shortName контакта Битрикс24 считает только для списка или поиска с явным select без *; одиночный GET, список без select и действие list пакета сущности отдают по ним null. Значение, присланное в любое из двенадцати полей, — которое Битрикс24 и раньше не сохранял — теперь отклоняется: создание и изменение — 400 READONLY_FIELD, импорт — 400 IMPORT_ITEM_VALIDATION, пакетный запрос сущности — 400 BATCH_ITEM_VALIDATION, общий пакетный запрос — READONLY_FIELD под вызовом в data.errors. Незаполненные значения шести полей контакта приходят как null, как остальные пустые строки записи. Тринадцать ключей из ответов убраны: у контакта entityTypeId (константа типа, 3) и login (пустой на любом портале, ни одна дверь его не пишет); у сделки entityTypeId (константа 2), пять коротких дат begindateShort, closedateShort, dateCreateShort, dateModifyShort, eventDateShort (те же даты второй раз) и пять пустых старых граф eventId, eventDate, eventDescription, orderStage, productId (пусты на любом портале, не заполняются ни через API, ни через legacy-методы). Убранные ключи не принимаются в select, filter и сортировке — ?sort=, ?order[], order тела поиска (400 UNKNOWN_SELECT_FIELD / 400 UNKNOWN_FILTER_FIELD / 400 UNKNOWN_SORT_FIELD) — у сделок раньше ?sort= по ним уходил в Битрикс24 насквозь. Исключение прежнее: params.order подвызова POST /v1/batch у контактов и сделок по-прежнему уходит на портал как есть.

Что делать интеграторам

Если вы отправляли любое из двенадцати полей при создании, изменении, импорте или в пакете — уберите его из тела: Битрикс24 не хранил его и раньше, телефоны записываются через phone, адрес — в реквизитах, флаги и суммы считает портал. Если вы читали entityTypeId, login, короткие даты или eventId/eventDate/eventDescription/orderStage/productId — эти ключи больше не приходят: тип сущности задан самим эндпоинтом, даты берите из begindate/closedAt/createdAt/updatedAt, остальные всегда были пустыми. Если вы проверяли phoneWork, phoneMobile, phoneMailing, imol, address или shortName на пустую строку — проверяйте на null. Если вам нужны isWon/isLose/isWork/hasProducts/receivedAmount/lostAmount или shortName — запрашивайте их явным select (без *) на списке или поиске.

Затронутые эндпоинты: GET /v1/contacts/{id}, GET /v1/contacts, POST /v1/contacts/search, GET /v1/contacts/fields, POST /v1/contacts, PATCH /v1/contacts/{id}, GET /v1/deals/{id}, GET /v1/deals, POST /v1/deals/search, GET /v1/deals/fields, POST /v1/deals, PATCH /v1/deals/{id}, POST /v1/deals/{id}/move, POST /v1/{entity}/import, POST /v1/{entity}/batch, POST /v1/batch.

FIX-0921-9: планы Коворка отдают сроки и незнакомому порталу, когда сроки открыты всем

Было

GET /v1/platform/cowork/plans отдавал сроки 3, 6 и 12 месяцев только тому порталу, который платформа знает. Порталу с portal.known: false в price.terms[] приходил один месяц со скидкой 0 — независимо от того, открыты сроки всем или нет.

Стало

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

Знакомый портал отвечает как прежде — по своей раскатке.

Что делать интеграторам

Ничего. Форма ответа прежняя, селектор срока по-прежнему строится из price.terms[], и один срок в массиве остаётся штатным состоянием. Изменилось одно: первый покупатель — портал, которого платформа ещё не знает, — теперь видит тот же набор сроков, что и остальные.

NEW-0921-10: в списке сотрудников Коворка появилась разбивка паркинга по плану и сроку

Что нового

В блоке seats ответа GET /v1/platform/cowork/members появилось поле parkingBreakdown — тот же паркинг, разложенный построчно:

JSON
"seats": {
  "parking": 3,
  "parkingBreakdown": [
    { "plan": "PRO", "count": 2, "validUntil": "2026-11-30T21:00:00.000Z" },
    { "plan": "ULTRA", "count": 1, "validUntil": "2026-10-15T21:00:00.000Z" }
  ]
}

Строки сгруппированы по паре «план + момент окончания срока» и упорядочены: план по лестнице тарифов, затем срок по возрастанию. Сумма count по всем строкам всегда равна счётчику parking — обе величины считает один проход.

validUntil того же вида, что у строки сотрудника: точный момент в ISO, а не календарная дата. День считает тот, кто знает часовой пояс покупателя, — место, истекающее 30 ноября в 23:30 UTC, в Москве истекает 1 декабря.

Места, купленные одной покупкой, делят момент и складываются в одну строку; купленные порознь идут отдельными строками.

Зачем

Из одного числа нельзя собрать строку «2 места Pro на паркинге, сгорят 30 ноября» — не хватает ни плана, ни даты.

Что делать интеграторам

Ничего, поле добавлено. Счётчик parking остаётся на месте и прежнего смысла.

NEW-0921-11: удаление сервера сообщает, чем кончился запрос на удаление машины в облаке

В ответе DELETE /v1/infra/servers/{id} появилось поле cloudDelete: confirmed — облако приняло запрос на удаление машины, already-gone — машины уже не было, failed — облако запрос не приняло, not-applicable — у сервера нет машины в облаке, например у galaxy-приложения. success по-прежнему true во всех четырёх случаях: запись сервера удалена, начисления остановлены. Раньше ответ этого не показывал: при failed запись сервера всё равно удалялась, а машина могла продолжать работать — по ответу это было незаметно. При failed не считайте ресурс уничтоженным.

NEW-0921-12: код DISK_FULL в provisionErrorCode сервера после неудачной починки

Если починка сервера (POST /v1/infra/servers/{id}/repair) не удалась из-за нехватки места на диске, сервер в GET /v1/infra/servers и GET /v1/infra/servers/{id} получает новое значение provisionErrorCode: "DISK_FULL" и понятный текст в provisionError: повтор не поможет, сначала нужно освободить место. Успешная починка снимает DISK_FULL. Уже стоящие вердикты AGENT_NEVER_CONNECTED и GUEST_NOT_BOOTING значение DISK_FULL не заменяет. Статус сервера не меняется, прежние запросы работают как раньше.

NEW-0921-13: у дел появился include: responsible, author, editor

Дело теперь объявляет три связи на сотрудников, и их можно затребовать параметром include у GET /v1/activities, GET /v1/activities/{id} и POST /v1/activities/search: responsible — ответственный (по полю responsibleId), author — автор (по authorId), editor — последний редактор (по editorId). Раньше связей у сущности не было объявлено ни одной, поэтому любое имя отвергалось с 400 INVALID_INCLUDE, а список допустимых имён в тексте отказа был пустым.

Запрос вида GET /v1/activities/{id}?include=responsible,author отвечает 200 и кладёт карточки сотрудников в _included. Запрос без include работает как прежде. Связь читает карточку сотрудника, поэтому ключ обязан нести право user — иначе запрос отвечает 403 SCOPE_DENIED. Доступные имена публикует GET /v1/activities/fields и спецификация OpenAPI.

Имя вне этого списка отвергается по-прежнему, но отказ теперь называет доступные связи: Unknown include 'owner'. Available: responsible, author, editor. Родитель дела (ownerTypeId + ownerId) и communications связями не объявлены намеренно: целевая сущность там меняется от записи к записи, и по одному числовому идентификатору невозможно выбрать, чью карточку читать; обе величины и так приезжают полями самой записи.

FIX-0921-14: структурированный ответ проверяется на разбор, а не только на обрыв

Было

При response_format типа json_object или json_schema платформа проверяла только то, что модель не оборвала генерацию и что ответ не пуст. Непустой ответ, который не разбирается как JSON-документ — текст вокруг объекта, ответ в markdown-ограждении кода, одиночное значение, — приходил как обычный ответ HTTP 200, и JSON.parse падал уже в интеграции.

Стало

Платформа разбирает ответ перед выдачей. Успешный ответ 200 не изменился: валидный JSON-объект или массив приходит как прежде, байт в байт. Если ответ непустой, но не разбирается, приходит отказ 422 с кодом structured_output_invalid_json, полем finishReason и подсказкой hint; полей param и suggestedMaxTokens в нём нет — больший бюджет токенов результата не меняет. В потоковом режиме отказ приходит событием {"error":{"code":"structured_output_invalid_json"}} перед data: [DONE], уже переданные фрагменты следует отбросить. Отказ structured_output_truncated остаётся за обрывом и пустым ответом. Вызов, дошедший до модели, тарифицируется так же, как и прежде.

2026-09-20

FIX-0920-1: спецификация OpenAPI и справочник API называют обязательные ключи фильтра и считают `count` по `"*"`

Было

Девять сущностей не выполняют чтение без обязательного ключа фильтра: календарь (type, ownerId), разделы календаря, файлы и папки Диска, комментарии таймлайна (entityType, entityId), товары и разделы каталога (iblockId), значения списочных свойств товаров (propertyId) и узлы оргструктуры (type). Без ключа операция отвечает 400 MISSING_REQUIRED_PARAMS или 400 MISSING_REQUIRED_FILTER. Машинная спецификация GET /v1/openapi.json об этом молчала: у списка GET ни одной из девяти сущностей, у поиска POST /search шести из них и у агрегации POST /v1/catalog-products/aggregate фильтр был описан как необязательный. Примеры справочника вели в тот же отказ: поиск печатал пустой filter, список — вызов без параметров. Пример агрегации у двадцати с лишним сущностей считал count по полю, хотя операция принимает count только по "*" и отвечает 400 INVALID_PARAMS, а перечень допустимых полей агрегации в спецификации "*" не содержал. Пакетный вызов сущности POST /v1/{entity}/batch в примере передавал limit рядом с params, где подвызов его не читает.

Стало

Спецификация объявляет обязательные ключи там, где операция их требует. У поиска и агрегации они указаны в описании filter, у поиска файлов и папок ключ можно передать и на верхнем уровне тела. Значение folderId / parentId описано как непустой скаляр. У списка каждый ключ описан отдельным параметром запроса, и его можно передать как ?type=… или как filter[type]=…, как и раньше. Машиночитаемый перечень ключей добавлен расширением x-required-filter-keys, а признак, поднимает ли пакетный вызов обязательные параметры подвызова, — расширением x-batch-list-lifts-params. Каждый ключ назван вместе с кодом отказа. Перечень полей агрегации содержит "*". Подвызов пакетного вызова описан полем params. Примеры справочника передают обязательные ключи, агрегация считает count по "*", пакетный пример кладёт параметры в params. Там, где пакетный list не может получить обязательные параметры, пример показывает get, а у разделов календаря — create. Ответы операций не изменились.

Влияние на интеграторов

Рабочие запросы не затронуты: операции отказывали без этих ключей и раньше. Клиент, сгенерированный по спецификации, после обновления получит обязательный filter у поиска и агрегации этих сущностей. Исключение — поиск файлов и папок: там обязателен ключ, в filter или на верхнем уровне тела.

BC-0920-2: правка и удаление несуществующего поля сотрудника отвечают 404

Поддержка старого формата до: не предусмотрена

Было

PATCH /v1/userfields/users/:id и DELETE /v1/userfields/users/:id на идентификатор, которого на портале нет, отвечали 422 BITRIX_ERROR с сообщением Access denied. — тем же ответом, что и настоящий отказ по правам. Повторное удаление уже удалённого поля отвечало так же, а GET /v1/userfields/users/:id на тот же идентификатор отвечал 404. Идентификатор с ведущими нулями GET не находил: 007 отвечал 404, хотя правка и удаление того же поля по 007 работали.

Стало

Перед правкой и удалением платформа проверяет, существует ли поле. Поля нет — ответ 404 ENTITY_NOT_FOUND, и запрос на изменение на портал не уходит. Тот же код приходит на повторное удаление и на идентификатор поля, которое не является полем сотрудника. Отказ по правам остаётся прежним: владельцу ключа, не являющемуся администратором портала, приходит 422 BITRIX_ERROR с сообщением Access denied.. Заодно GET /v1/userfields/users/:id теперь находит поле по идентификатору с ведущими нулями: 007 читается как 7, как это давно работает у правки и удаления.

Что делать интеграторам

Ветку «поля нет» переведите с 422 BITRIX_ERROR на 404 ENTITY_NOT_FOUND — так же, как она уже написана для полей CRM. Повторный DELETE, получивший 404, означает «поле уже удалено», а не ошибку. Ветку 422 BITRIX_ERROR с сообщением Access denied. сохраните: теперь она означает нехватку прав либо гонку, когда поле удалили между проверкой и записью. Правка и удаление стали на один запрос к порталу дольше.

FIX-0920-3: два одновременных создания сервера приложению дают одну машину

Было

Два запроса POST /v1/infra/servers, пришедшие почти одновременно ключом одного приложения, у которого сервера ещё нет, выполнялись оба: каждый видел приложение без сервера, и каждый создавал платную машину. К карточке приложения привязывалась одна из них, вторая оставалась работать и тарифицироваться, но не показывалась ни в карточке, ни в переиспользовании — найти её можно было только в списке GET /v1/infra/servers, а удалять приходилось руками.

Стало

Речь об обычном создании — запросе БЕЗ placement: "dedicated". Место приложения под сервер занимается на платформе Вайбкод неделимо, в тот же момент, когда запись сервера создаётся в базе, и до обращения в облако. Это касается и приложений, вызывающих API ключом авторизации: раньше их карточка получала сервер только при выкладке, а до неё каждое следующее создание заводило ещё одну машину — теперь сервер встаёт в карточку сразу, и повторный вызов возвращает его же. Поэтому вторая машина больше не рождается: запрос, опоздавший к свободному месту, останавливается раньше, чем что-либо оплачено, и в ответ получает 201 с сервером, который создал первый запрос, и reused: true, так что повторять запрос не нужно. Если такой опоздавший вызов нёс исходники, судьбу их называет сам ответ, и она зависит от того, есть ли что портить. Когда на возвращаемом сервере прямо сейчас идёт выкладка первого запроса — и всегда на обычном сервере, куда создание исходники не кладёт в принципе, — вторая выкладка поверх чужой не запускается: ответ несёт sourceIgnored: true, следите за состоянием сервера, а свои исходники выложите отдельным вызовом, если они отличались. Когда портить нечего (сервер приложения ещё ни разу не выложен), исходники опоздавшего вызова выкладываются обычным порядком и ответ несёт deploying: true. Смотрите на эти поля, а не предполагайте исход заранее. Если сойтись на этот сервер не удалось, ответ будет 409 с новым кодом APPLICATION_SERVER_SLOT_TAKEN, и машина по нему всё равно не создаётся. Такой ответ означает, что пригодного к выдаче сервера у приложения сейчас нет: его карточку успели удалить либо стоящая на ней машина отдана быть не может — она мертва или это общий хост. Повтор запроса в обоих случаях заведёт НОВЫЙ сервер, а не вернёт прежний, поэтому перед повтором стоит прочитать GET /v1/applications.

Защита работает и тогда, когда карточки приложения ещё нет: первый вызов заводит сервер, второй получает 409 с кодом APPLICATION_FIRST_SERVER_IN_PROGRESS, и повтор возвращает уже существующий сервер, как только карточка появилась. Коды отказа тут намеренно разные, потому что и делать по ним надо разное: после APPLICATION_FIRST_SERVER_IN_PROGRESS повтор вернёт сервер, а после APPLICATION_SERVER_SLOT_TAKEN — заведёт новый. Различайте их по коду: текст сообщения для этого не предназначен. Границ у защиты две, обе намеренные. Первая — запрос с placement: "dedicated": им дополнительную машину просят осознанно, и отказывать в ней нельзя. Вторая — аккаунт, где раздел «Приложения» выключен: карточек там не заводят вовсе, обе машины остаются видны в списке серверов, и ни одна не теряется. На этих двух путях собственную защиту от повторных вызовов снимать рано. Успешные ответы одиночных созданий не изменились.

BC-0920-4: из ответа списка сотрудников Коворка снято поле clientId

Поддержка старого формата до: не предусмотрена

Было

Блок portal в ответе GET /v1/platform/cowork/members нёс поле clientId. У облачных порталов оно было пустым всегда: колонка, из которой его брали, не заполнена ни у одного облачного портала.

Стало

Поля clientId в блоке portal нет. Портал по-прежнему опознаётся по portalNetworkId и portalDomain, остальные поля ответа прежние.

Что делать интеграторам

Ничего, если поле не читалось. Если клиент ждёт ключ clientId в блоке portal, уберите эту проверку: значение в нём всё равно было пустым. Адресуйтесь к порталу через portalNetworkId.

FIX-0920-5: статус сервера перестаёт показывать «подключён» при мёртвом туннеле

Было

Когда соединение сервера с платформой обрывалось незаметно, статус сервера мог навсегда остаться CONNECTED: карточка сервера показывала «Работает», хотя вызовы внешнего API приложения уже не доходили. Вызывающий получал 503 APP_API_UNAVAILABLE, но состояние само не исправлялось — сервер оставался в этом виде до случайного переподключения, и по статусу отличить «приложение сейчас молчит» от «соединения нет вовсе» было нельзя.

Стало

Получив отказ из-за оборванного соединения, платформа сверяется с фактическим списком живых соединений и, если соединения действительно нет, снимает неверный CONNECTED — статус сервера становится DISCONNECTED, и сервер подхватывает штатное восстановление. Сверка идёт только на этой ветке отказа и только при подтверждённом отсутствии соединения: пока список живых соединений недоступен или соединение в нём есть, статус не меняется. Ответ вызывающему при этом прежний — 503 APP_API_UNAVAILABLE, новых кодов ошибок нет.

FIX-0920-6: схема OpenAPI объявляет отказ по скоупу на читающих операциях сущностей

Было

Читающие операции сущностей — список, чтение по идентификатору, fields, search, aggregate, чтения связей и позиций товаров — не объявляли в схеме OpenAPI ответ 403, хотя API Вайбкод отвечает 403 с кодом SCOPE_DENIED, когда у ключа нет скоупа, который требует операция. Требование скоупа при этом публиковалось: расширение x-required-scope и фраза «Requires scope» в описании операции. Клиент, сгенерированный по схеме, не имел модели ошибки для самого обычного отказа и получал её как непредусмотренный ответ сервера.

Стало

Каждая читающая операция сущности объявляет 403 с кодом SCOPE_DENIED и перечисляет коды, которые приходят на том же статусе от общего гейта ключа и от самого Битрикс24. У fields кодов Битрикс24 в этом перечислении нет намеренно, и причин тут две. Там, где у сущности есть живой метод набора полей, запрос выполняется по мере возможности, а его отказ приходит успешным ответом с предупреждением fields_partial. Там, где такого метода нет, набор полей отдаётся по объявленному контракту и обращения в Битрикс24 не происходит вовсе — это сказано в описании самой операции.

Влияние на интеграторов

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

BC-0920-7: пакетные записи и импорт больше не отвечают успехом, когда Битрикс24 не применил стадию или ручную сумму

Поддержка старого формата до: не предусмотрена

Было

Одиночные PATCH /v1/deals/{id}, POST /v1/deals/{id}/move (и те же у лидов) уже отвечали 422 STAGE_NOT_APPLIED, а POST / PATCH /v1/items/{entityTypeId} — 422 AMOUNT_NOT_APPLIED, когда Битрикс24 принял запись, но не применил стадию, воронку или явно запрошенный ручной режим суммы. Те же записи через POST /v1/{entity}/batch, общий POST /v1/batch и POST /v1/{entity}/import отвечали success: true по каждому элементу: сделка оставалась на прежней стадии (при импорте — ложилась на стадию по умолчанию), сумма элемента смарт-процесса становилась нулём, а клиент видел успех.

Стало

В POST /v1/{entity}/batch элемент, чью стадию, воронку или ручную сумму Битрикс24 не применил, приходит с success: false, кодом STAGE_NOT_APPLIED либо AMOUNT_NOT_APPLIED в error, пояснением в message и details.unappliedFields / details.currentValues; такой элемент сохраняет id — запись уже создана или изменена, повторять её не нужно. В общем POST /v1/batch такой подвызов переходит из data.results в data.errors под своим id, с теми же code / message / details, а запись, которую Битрикс24 вернул в ответе на подвызов, приходит в data.errors.<id>.data. В POST /v1/{entity}/import строка результата получает те же поля и тоже сохраняет id. Для обеих пакетных дверей лишних обращений к Битрикс24 не появилось: сверяется запись, которую Битрикс24 и так возвращает в ответе на каждый подвызов. Импорт после всех пачек один раз перечитывает созданные записи страницами по пятьдесят, и только те, у которых была стадия, воронка или явный ручной режим суммы, — остальные импорты стоят ровно столько, сколько раньше; импорт не запускает правила автоматизации, поэтому перечитанная запись — итог самого импорта. Одиночное создание POST /v1/deals и POST /v1/leads стадию не сверяет и отвечает как прежде: там автоматизация портала работает, и робот «при создании — сменить стадию» выдавал бы ложный отказ. Лид, который портал в простом режиме CRM сам перевёл в «сконвертирован» при создании, за неприменённую стадию не считается; при изменении уже закрытого лида неприменённая стадия отказывается, как и в одиночном PATCH. Записи с валидными значениями отвечают как прежде; сумма без явного isManualOpportunity: true по-прежнему не проверяется.

FIX-0920-8: рассуждение прошлого хода снова доходит до модели

Было

В диалоге с инструментами рассуждение прошлого хода ассистента, возвращённое в messages[].reasoning_content, до модели не доходило: платформа передавала его в кластер под именем, которое шаблон модели не читает. Модель теряла нить между вызовами инструментов, а текст рассуждения не попадал во входные токены.

Стало

Платформа передаёт рассуждение под тем именем, которое шаблон модели читает, и в диалоге с инструментами модель снова видит свои прошлые рассуждения. Поле запроса не изменилось: возвращайте рассуждение в messages[].reasoning_content, как описано в вызове функций. Текст рассуждения теперь учитывается во входных токенах, поэтому usage.prompt_tokens у таких запросов выше.

Затронутые эндпоинты: POST /v1/chat/completions.

2026-09-19

NEW-0919-1: генерация изображений моделью BitrixGPT 5.6 Image

Появился POST /v1/images/generations — создание картинки по текстовому описанию моделью bitrix/bitrixgpt-5.6-image. Формат запроса и ответа совместим с OpenAI API: одна картинка за вызов приходит в поле data[0].b64_json в формате base64. Платформа изображения не хранит и ссылок на них не выдаёт.

Картинка учитывается в AI-квоте портала: в рамках квоты тарифа деньги с баланса не списываются, сверх квоты каждая картинка оплачивается с баланса портала по цене из поля pricing.perCall модели. Неудачный вызов не оплачивается.

Модель появляется в списке GET /v1/models у порталов, которым генерация открыта, — её отличает capabilities.image_generation. Остальным порталам ручка отвечает 404.

FIX-0919-2: потерянная связь с виртуальной машиной больше не выдаётся за её отсутствие и не советует создать вторую

Было

Любая операция над сервером, у которого в записи нет указателя на виртуальную машину, — пробуждение, запуск, остановка, перезагрузка, ресайз и автопробуждение внутри деплоя, выполнения команды, чтения логов и загрузки файлов — всегда отвечала 422 VM_MISSING с текстом «удалите этот сервер и создайте новый», а пробуждение добавляло подсказку «утечки не будет — освобождать нечего» и переводило запись в статус error. Пустой указатель, однако, означает только то, что указателя нет В ЗАПИСИ: если он не записался в момент создания сервера, машина существует, работает и тарифицируется. Совет удалить и пересоздать в этом случае приводил к тому, что клиент заводил вторую машину, а первая оставалась работать уже без всякой записи — то есть подсказка сама порождала утечку.

Стало

Перед вынесением вердикта платформа сверяется со своим разбором облака, и делает это ОДИНАКОВО на всех дверях. Если разбор видит машину под облачным именем этого сервера, ответ — 422 с НОВЫМ кодом VM_POINTER_LOST: связь с машиной потеряна, машина скорее всего работает и тарифицируется, удалять сервер и создавать новый НЕ нужно, случай виден платформе и решается через поддержку. Операция при этом всё равно не выполняется — без указателя она невозможна, — но запись в error НЕ переводится и остаётся в прежнем статусе. Когда разбор машины под этим именем не видит, ответ прежний — 422 VM_MISSING с прежним текстом и прежними побочными эффектами. Разбор облака обновляется раз в час, поэтому запись, которую он ещё не догнал, получает прежний ответ VM_MISSING.

Затронутые эндпоинты: POST /v1/infra/servers/{id}/wake, POST /v1/infra/servers/{id}/start, POST /v1/infra/servers/{id}/stop, POST /v1/infra/servers/{id}/reboot, а также автопробуждение внутри POST /v1/infra/servers/{id}/deploy, POST /v1/infra/servers/{id}/exec, GET /v1/infra/servers/{id}/logs и POST /v1/infra/servers/{id}/upload. У galaxy-приложения своей машины нет ПО ДИЗАЙНУ, поэтому его прежний ответ не меняется.

FIX-0919-3: признак `wasEverCommercial` в `GET /v1/me` — история оплат, а не доступ

Было

Описание поля tariff.wasEverCommercial в GET /v1/me и MCP-справка для агентов (промпт upgrade-from-trial, справочник vibe://tariff-gate-reference) подавали признак как доверие: будто портал, который когда-либо был на коммерческом тарифе, сохраняет доступ после понижения тарифа и остановить его может только баланс, а OPEN-режим он получает в обход проверки тарифа.

Стало

Описания приведены к поведению платформы, которое действует с 02.09.2026: признак лишь фиксирует, что портал когда-либо наблюдался на коммерческом тарифе. Доступа к инфраструктуре и к OPEN-режиму он не даёт и отказов не снимает — доступ определяется текущим тарифом, подпиской и демо. Ответы API не изменились.

Что делать интеграторам

Не ветвитесь по wasEverCommercial. Доступность операции читайте из capabilities того же ответа, причину отказа — из кода ошибки.

NEW-0919-4: режим работы сервера и библиотека расписаний теперь доступны по API

Режим машины, который ответы V1 уже показывают (runMode, workSchedule), теперь можно и задать ключом. PATCH /v1/infra/servers/{id}/run-mode принимает {"mode":"ALWAYS"}, {"mode":"IDLE","idleMinutes":30} или {"mode":"SCHEDULE","scheduleId":"..."} и отвечает тем же объектом режима, что и чтение.

Библиотека расписаний открыта целиком: GET/POST /v1/work-schedules и GET/PATCH/DELETE /v1/work-schedules/{id}, окна — [{"isoDay":1,"start":"09:00","end":"18:00"}] в часовом поясе самого расписания. Библиотека общая на портал, поэтому правка несёт version и отвечает 409 WORK_SCHEDULE_STALE, если расписание успели изменить. Готовое расписание не редактируется (403 WORK_SCHEDULE_PRESET_READONLY). Расписание с назначенными машинами удаляется только с ?reassign=true — приложения переходят в сон по простою, агенты и боты в круглосуточный режим, а без согласия ответ 409 WORK_SCHEDULE_IN_USE. У хоста галактики своего режима нет (400 GALAXY_NOT_SUPPORTED), а пока режимы на портал не раскатаны, запись отвечает 400 RUN_MODE_UNAVAILABLE. Прежние запросы, включая PATCH /v1/infra/servers/{id}/sleep, работают как раньше.

FIX-0919-5: спецификация OpenAPI и справочник API описывают историю задачи, стадии канбана и агрегацию справочников CRM

Было

Машинная спецификация GET /v1/openapi.json и справочник API не содержали работающих операций GET /v1/tasks/:taskId/history и GET /v1/tasks/stages/:entityId, а также POST /aggregate у сущностей без полей для группировки: POST /v1/currencies/aggregate, POST /v1/statuses/aggregate, POST /v1/deal-categories/aggregate, POST /v1/calendar-events/aggregate, POST /v1/telephony-lines/aggregate и POST /v1/humanresources/nodes/aggregate. Клиент, собранный по спецификации, и ИИ-агент их не находили, хотя первые пять операций описаны на страницах документации. Кроме того, у двухсот с лишним карточек справочника не был указан скоуп, хотя спецификация его объявляла, — среди них все операции разделов «Поиск дубликатов», «История стадий», «Раскладка карточки CRM», «Связи реквизитов» и «Склады». У POST /v1/products/aggregate спецификация обещала фильтр с операторами, тогда как операция принимает только точное совпадение, как и поиск товаров.

Стало

Все восемь операций описаны в спецификации и справочнике. Для сущностей без полей для группировки описание агрегации называет то, что операция принимает: count по "*" и числовые функции по числовым полям сущности, без groupBy. Где фильтр сущности ограничен — только точное совпадение или только отдельные ключи, — фильтр агрегации описан тем же перечнем допустимых ключей, что у поиска. Ключи фильтра, которые передаются параметрами вызова и без которых операция отвечает MISSING_REQUIRED_PARAMS, в спецификации отмечены как обязательные, а пример в карточке справочника их передаёт. Карточка справочника показывает скоуп, объявленный спецификацией. Поведение операций не изменилось.

Влияние на интеграторов

Никакого: запросы и ответы прежние. Клиент, сгенерированный по спецификации, после обновления получит методы для этих восьми операций.

BC-0919-6: проектный ключ деплоя выдаётся только допущенному порталу

Поддержка старого формата до: не предусмотрена

Было

POST /v1/cowork/deploy-key спрашивал скоуп ключа, платформенные выключатели и наличие места Cowork/Code — и на этом останавливался. Портал без действующей подписки BitrixGPT + Маркетплейс получал рабочий проектный ключ, хотя на выписке такого же ключа из кабинета платформа Вайбкод отвечала ему отказом. Сотрудник, которому портал запретил создавать серверы, ключ тоже получал и упирался в отказ уже на первом вызове POST /v1/infra/servers.

Стало

Перед выдачей маршрут спрашивает те же две двери, что кабинет и создание сервера: допуск портала к платформе и политику портала по серверам. Портал без подписки получает 402 с кодом MARKETPLACE_REQUIRED, а там, где доступ даёт тариф Битрикс24, — KZ_PAID_ONLY, UZ_PAID_ONLY, BY_PAID_ONLY или INT_TARIFF_REQUIRED. Если платформа не смогла прочитать подписку или тариф портала, ответ — 403 PORTAL_SUBSCRIPTION_UNREADABLE или 403 PORTAL_TARIFF_UNREADABLE, и покупка такой отказ не снимает. Портал с отключённой инфраструктурой получает 403 INFRA_NOT_PERMITTED, сотрудник вне политики — 403 SERVER_CREATION_DISABLED или 403 SERVER_CREATION_ADMINS_ONLY. При отказе ключ не выписывается и прежний проектный ключ остаётся действующим, поэтому запущенные деплои не прерываются.

2026-09-18

FIX-0918-1: фильтр и сортировка предложений по невозвращаемому полю contacts отвечают отказом платформы, а не ошибкой Битрикс24

Было

У предложений поле contacts помечено как не возвращаемое (item-API Битрикс24 никогда его не отдаёт, привязанные контакты — в contactIds), но ?filter[contacts]=…, ?sort=contacts и ?order[contacts]=… уходили в Битрикс24 и отвечали 422 BITRIX_ERROR с текстом Unknown field definition CONTACTS — словами Битрикс24, тогда как у компаний, лидов, сделок и счетов те же запросы отвечают 400 UNKNOWN_FILTER_FIELD / 400 UNKNOWN_SORT_FIELD.

Стало

У предложений filter, sort и order по contacts отклоняются до обращения к Битрикс24: 400 UNKNOWN_FILTER_FIELD и 400 UNKNOWN_SORT_FIELD, сообщение перечисляет допустимые имена — как у остальных сущностей CRM. Остальное поведение поля не изменилось: запись — 400 READONLY_FIELD, select — 400 SELECT_FIELD_NOT_RETURNED.

Влияние на интеграторов

Никакого: такие запросы не работали и раньше. Фильтруйте по contactId (основной контакт) или по contactIds.

Затронутые эндпоинты: GET /v1/quotes, POST /v1/quotes/search, POST /v1/quotes/aggregate, POST /v1/{entity}/batch, POST /v1/batch.

BC-0918-2: Участник команды разработки сервера получает собственный ключ, и у блока key появилось значение collaborator

Поддержка старого формата до: не предусмотрена

Было

Ключ приложения выдавал только владелец приложения, а slot в блоке key карточки принимал auth, api или null. Маршруты разработки сервера отвечали участнику команды отказом.

Стало

Участник команды разработки сервера (сотрудник портала с ролью ADMIN или DEVELOPER) получает собственный ключ через POST /v1/cowork/applications/:id/key и видит sources и activeOperation чужого приложения. В карточке такой ключ приходит со значением slot: "collaborator"; ключ владельца участнику не показывается. Ключ работает на тринадцати парах маршрутов разработки своего сервера, включая снятие замка выкладки и короткие api-bearer access-token с обязательным ttlSeconds не больше 600. Иконка сервера в перечень не входит — она принадлежит владельцу и администратору. Режим share-url закрыт с 403 PORTAL_COLLABORATOR_TOKEN_MODE_FORBIDDEN, неверный TTL — 400 INVALID_TTL, отозвать можно только свой токен (чужой отвечает 404). Новый ответ может содержать COLLABORATOR_KEY_REQUIRES_SERVER, COLLABORATOR_MEMBERSHIP_BIND_CONFLICT, PORTAL_COLLABORATOR_KEY_OUT_OF_SCOPE, PORTAL_COLLABORATOR_KEY_WRONG_SERVER, PORTAL_COLLABORATOR_NOT_A_MEMBER и предупреждение ENV_SYNC_NOT_APPLICABLE_FOR_COLLABORATOR.

Что делать интеграторам

Если клиент разбирает slot закрытым перечнем, добавьте в него collaborator либо сводите незнакомое значение к null. Обращение ключом участника к паре «метод + маршрут» вне перечня ниже отвечает 403 PORTAL_COLLABORATOR_KEY_OUT_OF_SCOPE.

Затронутые эндпоинты: POST /v1/cowork/applications/:id/key, GET /v1/applications, GET /v1/applications/:id и тринадцать пар, на которых работает ключ участника: GET /v1/infra/servers/:id, POST /v1/infra/servers/:id/deploy, POST /v1/infra/servers/:id/exec, POST /v1/infra/servers/:id/upload, GET /v1/infra/servers/:id/logs, POST /v1/infra/servers/:id/wake, GET /v1/infra/servers/:id/sources, POST /v1/infra/servers/:id/sources, GET /v1/infra/servers/:id/sources/:versionId, GET /v1/infra/servers/:id/sources/:versionId/download, DELETE /v1/infra/servers/:id/lock, POST /v1/infra/servers/:id/access-tokens, DELETE /v1/infra/servers/:id/access-tokens/:tokenId.

NEW-0918-3: Приложению из Коворка права можно не указывать

b24Scopes в POST /v1/cowork/applications стало необязательным. Поле пропущено — ключ получает все права Битрикс24, которые может выдать этот портал: каталог без прав, закрытых фича-флагом, плюс те из них, что порталу разрешены, минус права, которые Битрикс24 не хранит на входящем вебхуке. Явный список по-прежнему принимается и нужен, чтобы попросить МЕНЬШЕ прав; пустой массив отбивается как раньше — ключ без прав портала расширить потом нечем. Молчанием такая выдача не остаётся: в warningCodes ответа приходит B24_SCOPES_DEFAULTED_TO_ALL, а в журнале портала у записей о создании приложения и выдаче ключа появилось поле scopesSource — default или explicit. Пропуск поля и явный полный список считаются разными телами: отпечаток идемпотентности берёт тело как пришло, а не результат подстановки.

BC-0918-4: Поле envSync при замене ключа приложения-галактики возвращает реальный исход доставки

Поддержка старого формата до: 17.03.2027

Было — для приложения на платформе Вайбкод, чей сервер живёт контейнером в Галактике, замена ключа с syncServerEnv: true всегда отвечала { "status": "skipped", "reason": "galaxy" }: доставить ключ в контейнер платформа не умела, и поле сообщало только об этом.

Стало — платформа доставляет ключ в окружение контейнера, и поле возвращает исход этой доставки: { "status": "recreated", "variable": "..." } (ключ доставлен, контейнер пересоздан из того же образа; alsoBakedInImage: true добавляется, когда СТАРОЕ значение вдобавок зашито в образ и продолжит отвечать до пересборки из исходников, а bakedVariable называет эту переменную образа), { "status": "not_required" } (ключа платформы в окружении контейнера нет вовсе), { "status": "baked_in_image", "bakedVariable": "..." } (ключ пришёл из образа, а не задан при запуске: перекрыть переменную можно, окончательно лечит пересборка), { "status": "recreate_failed", "reason": "..." } (доставка не удалась, причина в reason), { "status": "unreachable" } (хост Галактики или приложение спит — ничего не тронуто), { "status": "not_found" } (в контейнере ключ неизвестного платформе поколения), { "status": "superseded" } (доставку опередила более новая замена ключа) или { "status": "failed" } (платформа не смогла определить, куда доставлять ключ; исход общий для обоих видов серверов, не только для галактических). Прежний { "status": "skipped", "reason": "galaxy" } остаётся достижимым — он приходит, когда доставка в контейнеры для аккаунта выключена.

Что делать интеграторам — разберите все перечисленные значения envSync.status, а не только skipped. Ветка, считающая { "status": "skipped", "reason": "galaxy" } единственным возможным ответом для приложения-галактики, теперь получает значения, которых не ожидает.

  1. Считайте ключ доставленным ТОЛЬКО на recreated. На recreate_failed, unreachable, baked_in_image, failed и skipped в контейнере остался прежний ключ; на not_found платформа не знает, какой ключ там стоит вообще — в окружении значение поколения, которого она не выписывала. В любом из этих случаев впишите ключ ЭТОЙ замены вручную, и успеть нужно до previousKey.graceUntil из того же ответа. Срок не всегда сутки и бывает пустым: null означает, что отсрочки нет вовсе (прежний ключ заблокирован) и доступ уже потерян.
  2. superseded — особый случай: ключ этой замены уже вытеснен более новой заменой, и вписывать его в контейнер НЕЛЬЗЯ — так приложение получит устаревший ключ. Разбирайтесь по исходу envSync ТОЙ, более новой замены и по её сырому ключу: секрет отдаётся один раз, в ответе на саму замену, и восстановить его потом нечем — карточка приложения отдаёт только метаданные. Того ответа под рукой нет — сделайте ещё одну замену с НОВЫМ Idempotency-Key и работайте по её ответу.
  3. На recreated с alsoBakedInImage: true и на baked_in_image дополнительно пересоберите приложение из исходников: старое значение зашито в образ (переменная названа в bakedVariable), и образ отдаст его снова на любом развёртывании без пересборки. Ручная запись переменной из пункта 1 действует — она перекрывает значение образа, — но перекрытие живёт лишь до следующего развёртывания: пересборка лечит окончательно, ручная запись только закрывает срок previousKey.graceUntil.
  4. Незнакомое значение status считайте недоставленным ключом, а не успехом: набор исходов будет пополняться.

Сама замена ключа по-прежнему завершается успешно независимо от исхода envSync, а пересоздание контейнера перезапускает приложение и рвёт его открытые соединения на время перезапуска. Прежний ответ { "status": "skipped", "reason": "galaxy" } остаётся достижимым и в течение окна поддержки: он приходит, когда доставка в контейнеры для аккаунта выключена.

Затронутые эндпоинты: POST /v1/cowork/applications/{id}/key

FIX-0918-5: загрузка иконки отличает отказ в правах от отсутствующего сервера

Было

POST /v1/infra/servers/:id/icon отвечал 404 SERVER_NOT_FOUND и тогда, когда сервер на портале есть, но привязан к другому API-ключу. Отказ в правах был неотличим от опечатки в id или удалённого сервера: ключ, которым проходят GET /v1/infra/servers/:id, выкладка и депонирование исходников, получал на иконке «сервера нет» — без указания, что именно не так и как это исправить.

Стало

Иконка отвечает тем же кодом, что и остальные операции контрол-плейна: сервер есть на вашем портале, но управляет им другой ключ — 403 WRONG_KEY, и в теле приходит error.hint с порядком восстановления (перепривязать сервер в кабинете, затем отправлять новый ключ в заголовке X-Api-Key). 404 SERVER_NOT_FOUND остаётся ровно на прежних основаниях: сервера с таким id нет, он удалён либо принадлежит другому порталу. Права загрузки не изменились — иконка по-прежнему требует управляющего ключа, потому что едет на карточку приложения в каталоге. Подробности — Иконка приложения и Восстановление доступа к серверу.

FIX-0918-6: внешний API приложения доходит до приложения, а не отвечает «недоступно»

Было

Канал ANY /v1/applications/{id}/api/* отвечал 503 с кодом APP_API_UNAVAILABLE и сообщением «Application is not reachable» на каждый вызов, прошедший проверки допуска, — даже когда приложение было запущено и открывалось из кабинета и по ссылке внешнего доступа. Отказ приходил мгновенно, без заголовка X-Vibe-Request-Id, и не зависел от размещения приложения. Кнопка «Проверить схему» в карточке приложения, которая ходит тем же каналом, сообщала «Сервер приложения сейчас недоступен».

Стало

Вызов доходит до приложения: ответ приложения отдаётся как есть — его собственный HTTP-статус, заголовки и тело сохраняются, успешный вызов остаётся успешным. Транзиентный 503 APP_API_UNAVAILABLE с Retry-After теперь означает ровно то, что заявлено в описании канала, — приложение действительно не отвечает, а не «канал не работает вообще».

FIX-0918-7: ход с вызовом функции больше не дублирует рассуждение в content

Было

Когда модель с рассуждением вызывала функцию, ответ POST /v1/chat/completions без потока приходил с finish_reason: "tool_calls", рассуждением в reasoning_content и той же цепочкой ещё и в content: клиент получал рассуждение модели как реплику ассистента. В потоке рассуждение в content не попадало.

Стало

У хода с вызовом функции content равен null, рассуждение приходит только в reasoning_content, как в примере вызова функций. Ответы без рассуждения и ответы с итоговым текстом не изменились.

Затронутые эндпоинты: POST /v1/chat/completions.

NEW-0918-8: места на паркинге компании в методе сотрудников Коворка

Ответ GET /v1/platform/cowork/members несёт в блоке seats новое поле parking — число платных мест на паркинге компании. Место попадает туда, когда сотрудник уходит из портала: оно остаётся за компанией, его срок идёт, и администратор может отдать его другому сотруднику.

В assigned и expiringWithin30Days такие места не входят. Место, чей срок уже кончился, не считается: отдать его другому сотруднику нельзя. У незнакомого портала поле приходит нулём.

FIX-0918-9: ушедшие сотрудники больше не приходят в списке сотрудников Коворка

Было

В data ответа GET /v1/platform/cowork/members приходили и люди, которые ушли из портала, но за которыми осталось платное место. Такой человек выглядел сотрудником без плана (plan.status: NONE), попадал в фильтр NO_PLAN, и его учитывали portal.usersTotal и portal.usersInVibecode.

Стало

Ушедшие в data не приходят и в usersTotal / usersInVibecode не учитываются. Их места не теряются: место на паркинге компании считается полем seats.parking.

FIX-0918-10: аккаунт, тариф которого не спрашивали, больше не получает отказ «нужен платный тариф»

Было

Отказ 403 PORTAL_TARIFF_UNREADABLE («тариф прочитать не удалось») выносился, только если последняя проба лицензии закончилась с записанным неудачным исходом. Аккаунт, у которого кода тарифа нет И исхода пробы нет вовсе — пробы не было либо она прошла до появления этой метки, — попадал в прежнюю ветку и получал 402 с советом подключить платный тариф Битрикс24. Совет опирался на пустой код тарифа, а пустой он и у аккаунта, чью лицензию платформа ни разу не спрашивала.

Стало

Оба состояния «спросить не удалось» разведены с «аккаунт на бесплатном тарифе» одинаково: кода тарифа нет И проба либо провалилась, либо не записана — ответ 403 с кодом PORTAL_TARIFF_UNREADABLE, details.requiredTariffs пуст, а details.upgradeUrl и alternatives[0].url ведут в поддержку. Аккаунт с УСПЕШНО прочитанной лицензией поведение не меняет: пустой код при успешной пробе по-прежнему означает бесплатный тариф и по-прежнему отвечает 402 с прежним текстом.

FIX-0918-11: отказ доступа отличает непрочитанную подписку от её отсутствия

Было

Аккаунт, состояние подписки которого платформа Вайбкод прочитать не смогла, получал на создании сервера и на выписке ключа 402 с кодом MARKETPLACE_REQUIRED и предложением оформить подписку. Совет мог быть неверным: состояние подписки приезжает только в конверте market пробы лицензии, а её отдаёт ключ разработчика участника аккаунта — ключу с узким набором прав Битрикс24 отвечает отказом, и тогда аккаунт с действующей подпиской выглядел для платформы так же, как аккаунт без неё.

Стало

Эти два состояния разведены. Если состояние подписки прочитать не удалось, ответ теперь 403 с кодом PORTAL_SUBSCRIPTION_UNREADABLE: поле details.requiredTariffs пустое — покупка этот отказ не снимает, — а details.upgradeUrl и alternatives[0].url ведут в поддержку. Поле userMessage называет исполнимое действие: переподключить приложение под главным администратором аккаунта, чтобы ключ разработчика получил права на чтение лицензии. Код MARKETPLACE_REQUIRED остаётся за своим состоянием — подписка прочитана, и её нет: там по-прежнему 402 и прежний текст. Доступ этой правкой никому не открывается: отказ остаётся отказом, меняются код, статус и предлагаемое действие.

NEW-0918-12: сервер можно создать сразу в нужном режиме работы

POST /v1/infra/servers принимает необязательный блок runMode той же формы, что и PATCH /v1/infra/servers/{id}/run-mode: {"mode": "ALWAYS"}, {"mode": "IDLE", "idleMinutes": 60} или {"mode": "SCHEDULE", "scheduleId": "..."}. Машина рождается уже в этом режиме — «создать, а потом переключить» стоило лишнего вызова, а машине — часов работы по умолчанию, за которые уже идёт счёт.

Отказ приходит до создания, поэтому неудачный запрос не оставляет машину: WORK_SCHEDULE_NOT_FOUND — расписания с таким идентификатором в аккаунте нет, WORK_SCHEDULE_EMPTY — у расписания нет ни одного окна, RUN_MODE_UNAVAILABLE — режимы работы для аккаунта ещё не включены. Тело разбирается по полю mode, поэтому лишнее поле рядом с режимом отвергается, а не выбрасывается молча. Запрос без блока runMode работает как прежде.

NEW-0918-13: потолок хранилища и числа ключей у аккаунта без коммерческого тарифа

У аккаунта Битрикс24 без коммерческого тарифа появились два необязательных потолка, включаемых платформой: на объём хранилища и на число живых ключей аккаунта. Аккаунт на коммерческом тарифе — и любой, который когда-либо был на нём, — потолков не получает.

Запись в хранилище, которая выводит аккаунт за потолок объёма, отвечает 507 с кодом STORAGE_QUOTA_EXCEEDED. Выписка ключа сверх потолка их числа отвечает 409 с уже существующим кодом KEY_LIMIT_REACHED; в объекте details такого ответа добавлено поле scope со значением portal — по нему потолок аккаунта отличается от лимита на одного сотрудника, у которого поля scope нет. Поле аддитивно: клиент, который его не читает, разницы не заметит. Запросы под потолком отвечают ровно как прежде.

NEW-0918-14: оплата с составом мест Коворка создаёт права на места

Состав, набранный покупателем в чекауте Битрикс24, теперь применяется: блок metadata.application события payment.paid превращается в оплаченные права на места, и rights[] в GET /v1/platform/cowork/members наполняется парами «план + срок». Место, адресованное конкретному сотруднику, резервируется за ним.

Применение идёт один раз на заказ — по sale_order_id, в момент, когда доехали все чеки покупки.

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

Что стоит заложить в клиент: после покупки Коворка баланс портала растёт не на всю сумму платежа. Оплаченные места отгораживаются от общего кошелька сразу, чтобы их нельзя было потратить на что-то другое. Если токенов на весь состав не хватило, создаётся столько прав, сколько покрыто деньгами, а остаток остаётся вайбами на балансе — порядок такой: сначала seats[] сверху вниз, затем unassigned[].

2026-09-17

NEW-0917-1: чтение планов Коворка: квоты, цены и сроки

Появился метод GET /v1/platform/cowork/plans — второй метод чтения для чекаута Битрикс24, рядом с уже работающим GET /v1/platform/cowork/members. Он отдаёт список планов Коворка с квотами, ценами и сроками продажи. Ключ тот же, скоуп тот же — cowork:read.

Квота и цена в ответе — разные числа, и путать их нельзя. quota говорит, сколько место может потратить: месячное окно плюс недельное и пятичасовое, о которые человек упирается в середине месяца. price говорит, сколько место стоит.

Цена срока приходит посчитанной. У каждого срока продажи есть vibesMonth — цена одного месяца под этот срок, уже со скидкой и уже округлённая, — и vibesTotal за весь срок. Считать итог самостоятельно не нужно и не следует: скидка применяется к цене одного месяца, а итог получается умножением уже уценённого месяца на число месяцев. Обратный порядок на некруглых процентах расходится на вайб, и списание идёт по первому. Поле discountPercent едет рядом, но только для подписи вида «−20 %».

Набор сроков в ответе — те, что продаются прямо сейчас: он меняется настройкой без выкатки, поэтому селектор срока следует строить из ответа, а не из собственного списка.

Непродаваемый план отдаётся с purchasable: false и price: null — у него нет цены, а не цена ноль.

Портал в запросе обязателен (portalNetworkId или portalDomain), потому что сетка не общая: часть порталов получает расширенные окна на бесплатном плане. Незнакомый портал ошибкой не считается — ответ придёт с portal.known: false и общей сеткой.

NEW-0917-2: у сервера появился режим работы: `runMode` и `workSchedule` в чтениях

GET /v1/infra/servers и GET /v1/infra/servers/{id} отдают два новых поля.

runMode — IDLE, SCHEDULE или ALWAYS. Он выводится из уже публичных полей: назначенного расписания, createdVia и sleepAfterMinutes. По одному sleepAfterMinutes эти состояния было не различить: у машины, которая просто не засыпает, и у агента, которому засыпать запрещено, там одинаково null. У агента и бота runMode всегда ALWAYS — число в sleepAfterMinutes на это не влияет.

workSchedule — null, пока сервер не работает по расписанию. Иначе объект:

JSON
{
  "id": "…",
  "name": "Смены склада",
  "presetKey": null,
  "timezone": "Europe/Moscow",
  "windows": [
    { "isoDay": 1, "start": "08:00", "end": "13:00" },
    { "isoDay": 1, "start": "14:00", "end": "20:00" }
  ]
}

isoDay — 1…7 от понедельника, start и end — местное время расписания в виде ЧЧ:ММ. Окна приходят списком, а не парой полей «начало/конец»: в одном дне их может быть два — например, смена с перерывом. У готового расписания платформы name пустое, а опознаётся оно по presetKey.

Поля добавочные: прежние запросы и разбор ответов продолжают работать без изменений.

NEW-0917-3: баланс портала в чтении сотрудников

В ответе GET /v1/platform/cowork/members появился блок balance — баланс вайбов портала, из которого оплачиваются места, и признак того, что списания по порталу заблокированы. Отдельный запрос за балансом при открытии страницы больше не нужен.

Блок приходит только ключу, которому выписан скоуп revenue:balances — тот же, что у выгрузки остатков. Сам метод по-прежнему открывается скоупом cowork:read, но он заведён под данные сотрудников, и деньги по нему не отдаются: ключ, которому денежный скоуп не выписывали, получает ответ вообще без этого поля.

Отсюда три состояния, и они значат разное. Поля нет — денежный скоуп ключу не выписан. Поле есть и равно null — счёта у портала нет вовсе, он ничего не покупал. Поле есть и заполнено — вот баланс. Сводить первые два к одному значению нельзя: «не положено» и «покупок не было» это разные ответы.

balance.vibes приходит строкой, как и usage.usedVibesMonth рядом: значение дробное, а число в JSON — это double, и на большом балансе он теряет копейки молча.

balance.blocked означает, что списания по порталу сейчас не идут. Признак считается так же, как его считает сам денежный гейт, а не по одному полю: у постоплаты блокировка вычисляется из баланса и разрешённого овердрафта, отдельной отметки о ней не существует. Поэтому blocked может быть истинным у портала, у которого никакой «даты заморозки» не проставлено.

Существующие поля не изменились, запросы, собранные до этой записи, продолжают работать.

FIX-0917-4: удалённое приложение показывает понятное состояние места показа

Было

Оставшееся в Битрикс24 место показа локально удалённого приложения открывало сырой ответ 401 APP_RESOLVE_FAILED.

Стало

POST /v1/bitrix-handler возвращает 410 Gone с локализованной страницей о недоступности приложения и ссылкой назад в Битрикс24. Ответ содержит Cache-Control: no-store, Pragma: no-cache и Referrer-Policy: no-referrer.

Влияние на интеграторов

Изменения интеграции не требуются. Пользователь видит конечное состояние вместо ошибки авторизации.

FIX-0917-5: внешнее API приложений возвращает 503 при перегрузке

Было

При высокой нагрузке новые запросы внешнего API приложений не получали специальный ответ о временной недоступности.

Стало

При временной перегрузке внешний API приложений отклоняет новый запрос с HTTP 503 APP_API_UNAVAILABLE и заголовком Retry-After. Форматы успешных ответов приложений не изменились.

Затронутые эндпоинты: GET, POST, PUT, PATCH, DELETE, HEAD /v1/applications/:id/api/*.

Влияние на интеграторов

Изменения не требуются. После HTTP 503 APP_API_UNAVAILABLE клиент может повторить запрос через время, указанное в Retry-After.

FIX-0917-6: предупреждения успешной установки рантайма теперь видны в ответе деплоя

Было

Шаг runtime возвращал status: "ok" и длительность, когда установка завершалась успешно, и всё, что установщик рантайма напечатал по ходу дела, до вызывающей стороны не доезжало — ни в обычном ответе, ни в потоке ?stream=true. Установщик при этом мог сообщить, что поставил рантайм не задуманным путём: например, один из источников пакетов он отложил в сторону, чтобы apt оставался читаемым, или взял рантайм из запасного источника. Деплой был зелёный, узнать об этом можно было только зайдя на сервер.

Стало

Если установка прошла успешно, но установщик сообщил о себе, шаг runtime возвращает status: "warning", текст сообщений лежит в stdout этого шага, а в warnings[] ответа появляется строка-указатель на него. Сам ответ остаётся успешным: success: true и HTTP 200 не меняются, поле duration шага на месте, форма ответа прежняя. Когда установщику сказать нечего, шаг по-прежнему возвращает status: "ok" без stdout.

NEW-0917-7: оплаченные права на место в чтении сотрудников

В ответе GET /v1/platform/cowork/members появился блок rights — места, за которые уже заплатили, но ещё никому не выдали. Каждая строка это plan, count и months: «три права Pro на двенадцать месяцев».

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

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

Блок приходит всегда, в том числе пустым массивом. Пустой массив значит «запаса нет» — не «не знаем»: строки читаются из своей таблицы и от доступности портала Битрикс24 не зависят, поэтому в ответе с degraded: true они такие же достоверные, как в обычном.

Соседнее поле seats.unassigned объявлено устаревшим. Оно остаётся пустым массивом и продолжает работать, но запас переехал в rights целиком: у права другая форма и другой смысл, и заполнить им старое поле значило бы молча поменять смысл опубликованного контракта. Снятие поля поедет отдельной записью.

Существующие поля не изменились, запросы, собранные до этой записи, продолжают работать.

FIX-0917-8: создание папки через глобальный POST /v1/batch больше не отклоняется Битрикс24

Было

Под-вызов { "entity": "folders", "action": "create" } в POST /v1/batch отправлял поля папки одним конвертом fields[...], тогда как метод Битрикс24 ждёт родительскую папку верхним параметром id, а остальные поля — под data[...]. Каждое такое создание возвращалось с ошибкой ERROR_ARGUMENT, хотя одиночный POST /v1/folders и пер-сущностный POST /v1/folders/batch с тем же телом работали.

Стало

Глобальный batch собирает создание папки в той же форме, что и одиночный роут, поэтому под-вызов действительно создаёт папку. Пропущенный parentId отклоняется кодом MISSING_PARENT_ID до обращения к Битрикс24 — тем же, что и на одиночном роуте, вместо сырой ошибки портала.

Влияние на интеграторов

Менять ничего не нужно. Если вы обходили дефект одиночными вызовами создания папки, теперь папки можно создавать пачкой в одном batch.

FIX-0917-9: список записей учёта времени отвечает 404 на отсутствующую задачу

Было

GET /v1/tasks/:taskId/time на задачу, которой нет или которая недоступна ключу, отвечал 422 BITRIX_ERROR, а в поле message приезжал внутренний текст исключения Битрикс24 вида TASKS_ERROR_EXCEPTION_#1; Task not found or not accessible; 1/TE/TASK_NOT_FOUND_OR_NOT_ACCESSIBLE. Соседние маршруты этого же раздела на такой же случай уже отвечали 404.

Стало

Тот же запрос отвечает 404 с кодом TASK_NOT_FOUND и коротким сообщением Task not found or not accessible.; внутренний текст исключения наружу больше не уходит. Успешный ответ не изменился: задача на месте — по-прежнему 200 с тем же телом. Прочие ошибки чтения, включая отбой по лимиту запросов, сохраняют прежние статусы.

Влияние на интеграторов

Менять код не требуется. Если обработчик различал «задачи нет» по статусу 422 или по тексту сообщения, переключите его на 404 и код TASK_NOT_FOUND.

FIX-0917-10: отказ «на портале нет учёта рабочего времени» получил собственный код

Было

Состояние портала, при котором учёт рабочего времени недоступен, приезжало в операции раздела «Рабочий день» двумя разными ответами, и ни один не называл причину. Если модуль «Рабочее время» не установлен или не входит в тариф, Битрикс24 отвечает «Метод не найден» на любой метод timeman.* — и клиент получал 404 ENTITY_NOT_FOUND, то есть сообщение о ненайденной сущности, хотя сама сущность была в порядке и недоступен был метод. Этого кода не было ни в одной таблице ошибок раздела. Если же модуль установлен, но учёт рабочего времени выключен в настройках портала, тот же по сути отказ приезжал общим 422 BITRIX_ERROR с текстом Битрикс24, то есть был неотличим от ошибки данных.

Стало

Оба состояния отвечают 409 TIMEMAN_MODULE_NOT_ENABLED — код, который раздел уже использовал в GET /v1/workday/records. Сообщение называет обе возможные причины и действие: попросить администратора портала включить учёт рабочего времени. Ответ одинаков на всех операциях раздела, поэтому совет из документации истории рабочих дней — «позовите текущий статус, тот же отказ означает выключенный учёт» — теперь работает буквально. Такой отказ больше не учитывается защитой от циклов ошибок, то есть остаётся понятным при любом числе повторов, а не подменяется на 429 ERROR_LOOP_DETECTED.

Влияние на интеграторов

Действий не требуется: отказ как был, так и остаётся ответом класса 4xx. Интеграция, которая различала это состояние по тексту сообщения Битрикс24, может перейти на код 409 TIMEMAN_MODULE_NOT_ENABLED. Прежние 404 ENTITY_NOT_FOUND и 422 BITRIX_ERROR документацией раздела для этого состояния не обещались.

Затронутые эндпоинты: POST /v1/workday/open, POST /v1/workday/close, POST /v1/workday/pause, GET /v1/workday/status, GET /v1/workday/settings, GET /v1/workday/schedule, GET /v1/workday/records

BC-0917-11: команда в галактике без статуса выхода больше не считается успешной

Поддержка старого формата до: не предусмотрена

Было

POST /v1/infra/servers/:id/exec на приложении в галактике (kind: "GALAXY_APP"): если хост отвечал, но статус выхода не присылал, вызов возвращал success: true и exitCode: -1. Завершение при этом утверждалось там, где платформа его не наблюдала, а команда, действительно вышедшая с кодом -1, была от этого случая неотличима.

JSON
{ "success": true, "data": { "exitCode": -1, "stdout": "", "stderr": "", "duration": 0, "truncated": false } }

Стало

Тот же случай возвращает EXEC_NO_EXIT с success: false — так же, как это давно работает на отдельной виртуальной машине. Ответ остаётся HTTP 200. Полей exitCode, duration и truncated в нём нет: их значения платформе неизвестны, а накопленный до обрыва вывод приходит в data.

JSON
{ "success": false, "error": { "code": "EXEC_NO_EXIT", "message": "Exec stream ended without an exit status from the agent" }, "data": { "stdout": "", "stderr": "" } }

Что делать интеграторам

Окно поддержки прежнего формата не предусмотрено: он утверждал исход, которого не было, и сохранять его означало бы и дальше отдавать ложный успех.

Обработайте код EXEC_NO_EXIT там, где вызываете команду на приложении в галактике: случай «поток кончился без статуса выхода» теперь приходит именно так, а не успехом с exitCode: -1.

⚠️ Это не делает exitCode: -1 в ответе однозначным во всех случаях. Остаётся ещё одна ситуация, в которой приходит -1: агент прислал терминальный чанк, но кода в нём не назвал. Она встречается редко, форму ответа этим изменением не меняет и от настоящего выхода с кодом -1 в теле ответа по-прежнему неотличима. Различает их только запись операции: там код сохраняется, лишь когда агент его назвал, иначе приходит null. Поэтому если вам важно отличить «вышла с -1» от «кода не назвали», читайте исход по идентификатору через GET /v1/infra/operations/:operationId, а не по полю ответа.

BC-0917-12: пустое значение параметра в списке операций больше не подменяется умолчанием

Поддержка старого формата до: не предусмотрена

Было

GET /v1/infra/servers/:id/operations с пустым значением параметра — ?limit= — отвечал 200 и отдавал ленту с умолчанием, как будто параметр не передавали вовсе.

GET /v1/infra/servers/srv_123/operations?limit=
→ 200, 5 последних строк

Стало

Тот же вызов отвечает 400 INVALID_LIMIT. Пустое значение — это переданное значение, а не опущенный параметр: оно рождается из шаблона с незаполненной переменной, то есть сузить выдачу хотели и не сумели. Умолчания limit=5 и kind=deploy действуют только тогда, когда ключа в строке запроса нет вовсе.

JSON
{ "success": false, "error": { "code": "INVALID_LIMIT", "message": "limit must be a non-negative safe integer" } }

Что делать интеграторам

Окно поддержки прежнего поведения не предусмотрено: оно отдавало набор строк, за которым вызывающий не приходил, и на канале восстановления это читается как «записей нет».

Проверьте места, где строка запроса собирается шаблоном. Если значение может оказаться пустым, не подставляйте ключ вовсе — тогда действует умолчание. Тот же разбор применяется к ?kind=, но этот параметр в том же выпуске и появился, поэтому прежнего поведения у него не было.

NEW-0917-13: исход команды на сервере читается после обрыва ожидания

У команды POST /v1/infra/servers/:id/exec появился устойчивый идентификатор запуска, по которому исход читается отдельным запросом. Раньше исход существовал только внутри текущего ответа, поэтому транспортный таймаут или разрыв соединения теряли его навсегда, и узнать, выполнилась команда или нет, было нечем. Повтор при этом небезопасен: транспортный таймаут прекращает только ожидание на стороне клиента, а команда на сервере может доработать до конца уже после того, как клиент получил ошибку.

Идентификатор приходит полем operationId в теле (data.operationId при успехе, error.operationId при отказе) и заголовком ответа X-Vibe-Operation-Id. На отдельной виртуальной машине заголовок уходит до начала выполнения команды, поэтому доживает до клиента даже когда тело потеряно, а при ?stream=true то же значение приходит первым кадром event: operation. У приложения в галактике раннего канала нет: та ветка не открывает поток и отдаёт заголовок только с терминальным ответом, поэтому при обрыве ожидания идентификатора не будет — такой запуск ищется в списке операций сервера.

Исход читается через GET /v1/infra/operations/:operationId и хранится 7 суток. Поле kind в ответе этой ручки теперь принимает значение exec наряду с deploy, а у команды приходит exitCode — код возврата, если агент его подтвердил, иначе null. Статус succeeded у команды означает, что она отработала до конца, и ненулевой код возврата приходит с этим же статусом. Статус failed ставится, только когда платформа может доказать, что команда не запускалась, например при EXEC_BUSY. Пока команда идёт, статус — running. Во всех остальных случаях обрыва приходит unknown: команда может продолжать выполняться на сервере, поэтому повторять изменяющую команду по этому статусу нельзя без сверки.

В списке GET /v1/infra/servers/:id/operations появился параметр kind со значением deploy по умолчанию. Без параметра приходит тот же набор строк, что и раньше, — только выкладки, и каждая строка при этом дополнительно несёт поле kind. kind=exec показывает команды, kind=all — смешанную ленту. Недопустимое значение отвечает 400 INVALID_KIND.

Запись несёт исход попытки — статус, код возврата и причину отказа. Вывод команды в ней не сохраняется: если он нужен после обрыва, запускайте команду фоновой задачей и читайте журнал через GET /v1/infra/servers/:id/logs.

FIX-0917-14: у занятого сервера машинное поле восстановления называет ручку с проверкой

Было

При 409 EXEC_BUSY на отдельной виртуальной машине (kind: "STANDALONE") поле error.hint.recoveryAction называло DELETE /v1/infra/servers/:id/lock — вызов, который снимает замок безусловно, не проверяя, идёт ли операция. Поле это единственное в подсказке, по которому клиент действует, не читая прозу, поэтому автоматический вызов мог прервать живую выкладку.

JSON
{ "error": { "code": "EXEC_BUSY", "hint": { "recoveryAction": "DELETE /v1/infra/servers/:id/lock" } } }

Стало

Там же приходит POST /v1/infra/servers/:id/unstick — он сам отвечает 409 OPERATION_IN_PROGRESS, пока замок ещё наблюдается — и когда операция доказанно жива, и когда доказать её смерть нечем, — а снимает замок сам только если это exec-мьютекс, переживший собственный срок; когда признаков замка нет вовсе, отказывать нечему и вызов проходит, и дополнительно перетряхивает туннель агента. Снятие замка из контракта не исчезло: оно осталось в тексте error.hint.recovery вторым шагом. Право на него даёт только независимо подтверждённый простой — состояние сервера и логи. Отсутствие error.hint.holder таким подтверждением не является: чтение держателя fail-open, поэтому «держателя нет» приходит и тогда, когда прочитать его не удалось.

JSON
{ "error": { "code": "EXEC_BUSY", "hint": { "recoveryAction": "POST /v1/infra/servers/:id/unstick" } } }

Заодно текст подсказки перестал предлагать проверять «нет ли идущей операции» по списку операций. Список отдаёт только запуски вашего же ключа, поэтому операция, начатая другим ключом того же портала, в нём не видна, и пустой список ничего не доказывал. Признак держателя приходит в том же ответе — error.hint.holder.

Влияние на интеграторов

Клиент, который исполняет recoveryAction как есть, менять ничего не должен — он начнёт вызывать защищённую ручку сам. Если вызов DELETE /lock записан у вас в коде постоянной строкой, перечитайте error.hint.recovery: там сказано, при каком условии этот шаг уместен.

FIX-0917-15: приложения с интерфейсом больше не создаются в режиме «только API»

Было

POST /v1/apps отвечал 201 и возвращал mobile: true, но платформа Вайбкод не сообщала порталу режим установки, и портал подставлял свой умолчательный — «только API». Приложение с интерфейсом сохранялось без интерфейса: REST отвечал, а самого приложения не было ни в меню портала, ни в мобильном клиенте.

Стало

Режим передаётся явно. Приложение с интерфейсом устанавливается как приложение с интерфейсом; приложение без пунктов меню по-прежнему устанавливается как «только API».

Влияние на интеграторов

Ответ 201 и его состав не меняются, действий не требуется. Приложение, созданное раньше, платформа не переделывает — снимите режим «только API» в карточке приложения на портале либо создайте приложение заново.

BC-0917-16: создание приложения Коворк/Код: живое платное место и проверка скоупов

Поддержка старого формата до: не предусмотрена

Было

Аккаунт без доступа к платформе получал на POST /v1/cowork/applications отказ ещё до обращения к Битрикс24, даже когда у пришедшего было живое платное место Коворк/Код: 402 с причиной доступа либо 403, если план аккаунта прочитать не удалось.

Скоуп модуля-эксклюзива, недоступного аккаунту, эта ручка принимала молча: запрос отвечал 201, и ключ уезжал со скоупом, который соседние ручки выдачи ключей тому же аккаунту отказывают.

Стало

Живое платное место открывает эту ручку: приложение создаётся и ключ выдаётся. Прочие ручки ВЫПИСКИ ключа отвечают как раньше — правило касается только этой.

⚠️ У приложения, созданного здесь по месту, появляется одно ограничение на СОСЕДНЕЙ операции: передать его владение коллеге без своего права больше нельзя — 403 RECIPIENT_LACKS_COWORK_SEAT. Право у получателя то же самое, что открыло ручку вам; если аккаунт к этому времени проходит доступ сам, ограничение снимается.

Затронутые эндпоинты: POST /v1/cowork/applications — послабление по месту; POST /api/applications/{id}/transfer — новый отказ выше. Вторая ручка кабинетная, в Vibe API её нет, поэтому страницы у неё тоже нет.

Скоуп недоступного аккаунту модуля теперь отклоняется явно: 403 SCOPE_NOT_AVAILABLE_ON_PORTAL, как на соседних ручках выдачи ключей. Отказ приходит до создания записи, приложение не заводится и ключ не выписывается.

Послабление по месту приезжает РАСКАТКОЙ и включается по аккаунтам. Пока она не дошла до вашего аккаунта, вызов отвечает прежним отказом — 402 с причиной доступа либо 403; это признак раскатки, а не ошибки интеграции. Проверка скоупов раскаткой не гейтится и действует сразу.

Место снимает ТОЛЬКО отказ по доступу аккаунта к платформе, и только его. Исчерпанный баланс им не лечится — счёт по-прежнему отвечает ACCOUNT_FROZEN, эта проверка идёт раньше. Не снимаются местом и недоступный регион с коробочным аккаунтом без подтверждённого партнёрства — такому аккаунту ждать раскатки незачем.

Что делать интеграторам

Передавайте в b24Scopes только те скоупы, которые доступны аккаунту. Состав доступных модулей читается из карточки аккаунта; ранее выданные ключи не меняются. Если запрос получил 403 SCOPE_NOT_AVAILABLE_ON_PORTAL, уберите названный скоуп из тела и повторите запрос с новым ключом идемпотентности.

FIX-0917-17: порог простоя на сервере с расписанием больше не молчит

Было

PATCH /v1/infra/servers/{id}/sleep на сервере, которому назначено расписание работы, отвечал 200, записывал порог и не менял ничего: расписание сильнее порога, и машина продолжала работать по своим окнам. Счёт приходил прежним, а по ответу это было не отличить от применённой настройки. Сгенерированные расписанием строки пробуждения при этом попадали в GET /v1/infra/servers/{id}/wake-schedules наравне с обычными, хотя снять или поправить их было нельзя — следующая синхронизация расписания возвращала их обратно.

Стало

Запись порога снимает расписание: число переводит сервер в режим «засыпает, когда нет запросов», null — в «круглосуточно». Ответ остаётся 200 и несёт новое поле workScheduleCleared — по нему видно, что расписание было снято. Сгенерированные расписанием строки пробуждения в списке окон больше не показываются, а обращение к такой строке по идентификатору в PATCH/DELETE отвечает 404 NOT_FOUND: этими строками управляет расписание сервера, а не список окон. Заведённые вручную окна не затрагиваются.

BC-0917-18: схема счёта достроена: одиннадцать полей ответа объявлены, семь из них — только для чтения, contacts помечено как не возвращаемое

Поддержка старого формата до: не предусмотрена

Было

GET /v1/invoices/{id} (а также список, поиск и include) отдавал тринадцать полей, которых не было ни в схеме, ни в справочнике, ни в машинном описании OpenAPI: contactIds, observers, taxValue, locationId, webformId, lastActivityBy, utmSource, utmMedium, utmCampaign, utmContent, utmTerm, а также parentId2 и parentId7. Страница полей описывала восемь из них строками из живых метаданных портала — с типами Битрикс24 (user, crm_contact, double…) и без признака «только чтение»; пяти utm* не было нигде. Явный select любого из первых одиннадцати отвечал 200 с предупреждением UNKNOWN_SELECT_FIELD, хотя в GET /v1/invoices/{id} значение приходило. Семь из них Битрикс24 проставляет сам и присланное значение не хранит — taxValue, lastActivityBy и пять utm* — но создание, изменение и импорт отвечали успехом (201/200), а значение исчезало. Страница полей обещала, что contacts у счёта приходит в ответе и работает в select; на деле ключа в ответе нет никогда, а попытка записать contacts отвечала сырой ошибкой ORM Битрикс24 422 BITRIX_ERROR. Пустое locationId приходило пустой строкой.

Стало

Одиннадцать полей объявлены в схеме, справочнике, машинном описании и на странице полей с типами по живому замеру: contactIds, observers (массивы; изменение заменяет набор, пустой список снимает привязки), locationId (строка), webformId (число) — доступны на запись; taxValue, lastActivityBy, utmSource, utmMedium, utmCampaign, utmContent, utmTerm — только для чтения. Явный select этих полей больше не даёт предупреждения. Значение, присланное в поле только для чтения, — которое Битрикс24 и раньше не сохранял — теперь отклоняется: создание и изменение — 400 READONLY_FIELD, импорт — 400 IMPORT_ITEM_VALIDATION, пакетный запрос сущности — 400 BATCH_ITEM_VALIDATION, общий пакетный запрос — READONLY_FIELD под вызовом в data.errors. Поле contacts объявлено как не возвращаемое (notReturned) и только для чтения: страница и GET /v1/invoices/fields честно говорят, что ключа в ответе не бывает, а попытка записи отклоняется тем же гардом и с теми же кодами по дверям, что у полей только для чтения (создание и изменение — 400 READONLY_FIELD, импорт — 400 IMPORT_ITEM_VALIDATION, пакет сущности — 400 BATCH_ITEM_VALIDATION, общий пакет — READONLY_FIELD под вызовом) вместо сырой ошибки портала; привязанные контакты — в contactIds. Пустое locationId приходит как null, как и остальные пустые строки записи. Как у любого объявленного скалярного поля, объект или массив в locationId и webformId (и нечисловая строка в webformId) теперь отклоняются 400 INVALID_PARAMS до отправки на портал — раньше такое значение уезжало в Битрикс24 без проверки. Пустой список в contactIds или observers снимает привязки на PATCH /v1/invoices/{id}; пакетный запрос сущности и общий пакетный запрос пустой список передать не могут (подкоманда уходит строкой запроса, где у пустого массива нет записи — ключ пропадал, а ответ был успешным) и теперь отклоняют его: 400 BATCH_ITEM_VALIDATION и INVALID_PARAMS под вызовом в data.errors. Списки и поиск без select по-прежнему несут пользовательские поля ufCrm_* и связи parentId2/parentId7; явный select пяти utm* на списке и поиске Битрикс24 игнорирует — эти поля приходят только в полной записи. В filter и order эти поля, как и прежде, не принимаются (400 UNKNOWN_FILTER_FIELD / 400 UNKNOWN_SORT_FIELD). Поля связей parentId2 и parentId7 остаются динамическими: их тип и подписи по-прежнему берутся из живых метаданных портала, фильтр по ним работает, сортировка — нет.

Что делать интеграторам

Если вы отправляли taxValue, lastActivityBy или utm* при создании, изменении или импорте счёта — уберите их из тела: Битрикс24 их не хранил и раньше, налог задаётся через товарные позиции, автора активности и UTM-метки проставляет портал. Если вы отправляли contacts — замените на contactIds (полный список привязок). Если вы проверяли locationId на пустую строку — проверяйте на null. Если в select были поля из списка выше — предупреждение пропало, менять ничего не нужно; utm* на списке и поиске читайте без select. Если вы снимали привязки или наблюдателей пустым списком через пакетный запрос — это никогда не срабатывало; делайте это одиночным PATCH /v1/invoices/{id}.

Затронутые эндпоинты: GET /v1/invoices/{id}, GET /v1/invoices, POST /v1/invoices/search, GET /v1/invoices/fields, POST /v1/invoices, PATCH /v1/invoices/{id}, POST /v1/invoices/import, POST /v1/{entity}/batch, POST /v1/batch.

FIX-0917-19: Idempotency-Key защищает создание и переиспользование galaxy-приложений

Было

Первое galaxy-создание не резервировало Idempotency-Key: потеря ответа и повтор могли создать второй ресурс. Переиспользование слота также не сохраняло ключ и могло повторно запустить сборку.

Стало

После активации новой защиты POST /v1/infra/servers и POST /api/servers резервируют ключ при создании и переиспользовании galaxy-приложения. Ответ остаётся HTTP 201: повтор в течение 15 минут возвращает тот же ресурс с Idempotent-Replayed: true, без повторной сборки и выдачи SSH-секретов. Разные ключи могут указывать на один переиспользованный слот. После окна занятый ключ получает 409 IDEMPOTENCY_KEY_ALREADY_USED либо обычный отказ политики. Записанные ключи защищены и после выключения новой защиты; прежние galaxy-создания без записи ключа восстановить нельзя.

Влияние на интеграторов

Используйте тот же ключ при потере ответа. После ошибки сборки вызывайте явный deploy. До активации первое galaxy-создание сохраняет прежнее поведение; проверьте список ресурсов перед повтором.

BC-0917-20: ошибка режима ключа указывает на доступный переключатель

Поддержка старого формата до: не предусмотрена

Было

Текстовые подсказки о 403 WRITE_BLOCKED_READONLY_KEY в самоописании ключа и описаниях операций отправляли любой ключ в /keys. Ключи авторизации приложений на этой странице не показываются, поэтому переключить их режим по подсказке было невозможно.

Стало

Подсказки теперь учитывают вид вызывающего ключа. Форма ответа не изменилась: готовый путь по-прежнему находится в обязательном поле error.details.switchUrl и ведёт на /keys для личного ключа, /management-keys для management-ключа, /applications для ключа с карточкой Application и /apps для самостоятельного OAuth-приложения.

Влияние на интеграторов

Для WRITE_BLOCKED_READONLY_KEY менять обработку ответа не нужно. Если интерфейс показывает действие, используйте error.details.switchUrl как готовый путь.

Миграция ротации OAuth: конкурентная операция над тем же приложением теперь может вернуть 409 REISSUE_IN_PROGRESS, пока изменение выполняется, или 409 KEY_NOT_ACTIVE, если исходный ключ уже изменился. Перечитайте состояние ключа и повторяйте ротацию только для всё ещё действующей исходной строки. Прежний небезопасный конкурентный сценарий без явного отказа не поддерживается.

Затронутые эндпоинты: GET /v1/me, GET /v1/openapi.json, POST /v1/placements/bind, POST /v1/placements/unbind, PATCH /v1/apps/:id, POST /v1/apps/:id/relink-oauth, POST /v1/keys/:id/rotate.

BC-0917-21: ответы по контактам и сделкам без служебной строки поискового индекса Битрикс24

Поддержка старого формата до: не предусмотрена

Было

GET /v1/contacts/{id} и GET /v1/deals/{id} (а также список, поиск, include, пакетные запросы на чтение и ответы на создание, изменение, перенос сделки по стадиям и конвертацию лида) отдавали поле searchContent, которого нет ни в описании полей, ни в справочнике: служебную строку полнотекстового индекса Битрикс24 — номер записи, название транслитом, имя ответственного, стадия и даты, склеенные в одну строку. Запросить это поле через select или отфильтровать по нему было нельзя (400 UNKNOWN_SELECT_FIELD, 400 UNKNOWN_FILTER_FIELD); сортировка по нему у контактов отклонялась (400 UNKNOWN_SORT_FIELD), у сделок — проходила. Поле просто приходило в нагрузку. У лидов и предложений такая же строка из ответа уже исключена.

Стало

Поле searchContent исключено из ответов по контактам и сделкам; ответ на чтение остаётся 200, остальные поля не меняются. select и filter по этому полю, как и прежде, не принимаются; сортировка по нему (sort, order в запросе списка и в теле поиска) у сделок теперь тоже отвечает 400 UNKNOWN_SORT_FIELD, как у контактов. Параметр order внутри подвызова общего пакетного запроса по-прежнему передаётся в Битрикс24 как есть — используйте там sort. У компаний поле остаётся объявленным и описанным как служебное — там ничего не меняется.

Что делать интеграторам

Если вы читали searchContent из ответов по контактам или сделкам — поля больше нет; полнотекстовый поиск ведите фильтрами по обычным полям (name, lastName, title с $contains); ответственный — это assignedById, его имя — по этому идентификатору из GET /v1/users/{id}. Если вы сортировали сделки по searchContent — сортируйте по объявленному полю, например id. Если вы не обращались к этому полю — менять ничего не нужно.

Затронутые эндпоинты: GET /v1/contacts/{id}, GET /v1/contacts, POST /v1/contacts/search, POST /v1/contacts, PATCH /v1/contacts/{id}, GET /v1/deals/{id}, GET /v1/deals, POST /v1/deals/search, POST /v1/deals, PATCH /v1/deals/{id}, POST /v1/deals/{id}/move, POST /v1/leads/{id}/convert, POST /v1/{entity}/batch, POST /v1/batch.

FIX-0917-22: обновление smart-processes в batch использует документированный идентификатор

Было

POST /v1/batch мог отклонить корректное обновление smart-processes из-за неверного идентификатора, хотя в запросе был передан документированный публичный entityId.

Стало

POST /v1/batch корректно обновляет smart-processes, когда в запросе передан тот же документированный публичный entityId.

Влияние на интеграторов

Клиентам ничего менять не нужно.

FIX-0917-23: схема фильтров поиска учитывает возможности сущности

Было

Сгенерированная схема POST /v1/{entity}/search объявляла MongoDB-операторы фильтрации для всех сущностей, включая сущности без поддержки фильтров и сущности только с точным совпадением.

Стало

OpenAPI-схема выводит допустимые поля и формы фильтра из возможностей сущности. Для фильтров только с точным совпадением доступны скалярные значения и, где они поддерживаются, непустой массив или $in. Неподдерживаемые поля и операторы больше не объявляются допустимыми.

Влияние на интеграторов

Поведение API не изменилось. Генераторы клиентов и средства проверки запросов теперь точнее отражают уже действующие ограничения runtime.

FIX-0917-24: подсказка при недоступном каталоге пакетов Galaxy

Было

При недоступности каталога Node.js или Debian во время сборки Galaxy API возвращал 502 GALAXY_APP_BUILD_FAILED без категории и подсказки.

Стало

Такие отказы возвращают прежний 502 GALAXY_APP_BUILD_FAILED с категорией INSTALL_REGISTRY_UNAVAILABLE и подсказкой для повторного деплоя.

BC-0917-25: семь полей реквизитов клиента у предложения снова возвращаются и объявлены только для чтения

Поддержка старого формата до: не предусмотрена

Было

С записи BC-0914-21 семь колонок реквизитов клиента clientTitle, clientAddr, clientContact, clientEmail, clientPhone, clientTpId, clientTpaId были исключены из ответа GET /v1/quotes/{id} (а также списка, поиска и include) как служебные: через API они не заполнялись. Замер прямым вебхуком на тестовых порталах Битрикс24 показал иное: привязка компании и контакта их не заполняет, item-API, через который пишет платформа Вайбкод, их не принимает — но устаревшие методы crm.quote.add / crm.quote.update (ими пользуются старые интеграции) их сохраняют, и портал возвращает эти значения при чтении. На таких порталах читатель терял реквизиты клиента, а select=clientTitle отвечал 400 UNKNOWN_SELECT_FIELD. Присланное на запись значение принималось с ответом 201 / 200 и подсказкой UNRECOGNIZED_WRITE_FIELD, хотя портал его отбрасывал.

Стало

Семь полей client* объявлены в описании и справочнике как поля только для чтения: они возвращаются в ответах на чтение (ответ остаётся 200), принимаются в select, filter и order, а присланное на запись значение отклоняется вместо тихой потери: создание и изменение — 400 READONLY_FIELD, импорт — 400 IMPORT_ITEM_VALIDATION, пакетный запрос сущности — 400 BATCH_ITEM_VALIDATION, общий пакетный запрос — READONLY_FIELD под вызовом в data.errors. У предложений, в которые снимок никто не записывал (в том числе созданных через API), эти поля приходят как null, как и остальные незаполненные строковые поля.

Что делать интеграторам

Если вы отправляли client* при создании или изменении предложения — уберите их из тела (в том числе в пачках и импорте): портал их никогда не сохранял, теперь запрос отклоняется. Реквизиты клиента для новых предложений берите из привязанных контакта и компании по contactId и companyId. Если вы читаете предложения, заведённые старыми интеграциями, — снимок реквизитов снова доступен в этих полях.

Затронутые эндпоинты: GET /v1/quotes/{id}, GET /v1/quotes, POST /v1/quotes/search, GET /v1/quotes/fields, POST /v1/quotes, PATCH /v1/quotes/{id}, POST /v1/{entity}/import, POST /v1/{entity}/batch, POST /v1/batch.

NEW-0917-26: новое значение PARKED у состояния места Коворка

Поле состояния места теперь может принимать значение PARKED вдобавок к прежним — и в subscription.state ответа GET /v1/cowork/state, и в state ответа GET /v1/cowork/me. Это одна и та же колонка подписки, отданная двумя ручками. Место получает его, когда владелец перестал быть активным участником портала: за такое место не списывается, доступа оно не даёт, оплаченный срок и тариф на нём сохраняются, а администратор компании может отдать его другому сотруднику.

Стоянка не бессрочна, и это важно знать тому, кто на неё опирается: когда оплаченный срок такого места заканчивается, оно уходит в PAUSED и дальше живёт по прежним правилам приостановленного места. То есть PARKED означает «оплаченный остаток ещё есть», а не «место будет ждать вечно».

Прежние значения и их смысл не менялись, старые запросы работают как работали. Клиенту достаточно трактовать незнакомое значение как «доступа сейчас нет» — ровно так же, как PAUSED и CANCELLED.

BC-0917-27: Однозначный выбор записи и страницы при получении CRM-документов

Поддержка старого формата до: не предусмотрена

Было

GET /v1/crm-documents принимал повторённый entityId с ответом HTTP 200 и возвращал документы записи, указанной последней. Одновременная передача entityId и entityID также принималась, а исход зависел от порядка имён. Скобочные формы — entityId[]=7, entityId[0]=7, entityId[a][b][c]=7 — выглядели как непереданный параметр: фильтр по записи молча исчезал, и ответ приходил по всем записям указанного типа. Параметр start вёл себя так же: повтор давал последнее значение, а скобочная форма молча возвращала первую страницу вместо запрошенной.

Стало

Повтор параметра, оба имени вместе и скобочные формы возвращают HTTP 400 INVALID_ENTITY_ID для выбора записи и HTTP 400 INVALID_START для выбора страницы, даже если значения совпадают. Отказ приходит до обращения к Битрикс24. Одиночное положительное целое под именем entityId или entityID, одиночный start и пустое значение entityId= работают как прежде. Параметры select и order этим изменением не затронуты — их поведение не менялось.

Что делать интеграторам

Передавайте entityId (или entityID) и start не более одного раза, каждый одним значением. Не используйте массивы и объекты для выбора записи и страницы. Кодируйте пользовательский ввод при построении строки запроса: незакодированное значение может внести в запрос любой дополнительный параметр, включая те, что проверку однозначности проходят. Если идентификатор записи может оказаться незаполненным, опускайте параметр целиком вместо подстановки пустого значения — пустое значение означает выборку по всем записям типа.

BC-0917-28: карточка шаблонов документов объявляет оба права для примера с fileId

Поддержка старого формата до: не предусмотрена

Было

Карточка POST /v1/doc-templates показывала пример создания из файла Диска через fileId, но в машинном поле requiredScope называла только documentgenerator. Клиент, который брал права из api-reference.json, мог выпустить ключ без disk и получить 403 SCOPE_DENIED при повторении опубликованного примера.

Стало

Runtime не меняется: тело с base64-полем file по-прежнему требует только documentgenerator, а форма fileId требует documentgenerator и disk. Для этой карточки api-reference.json больше не публикует неполный requiredScope; вместо него приходит requiredScopes: ["documentgenerator","disk"], а badges карточки показывают оба права.

Что делать интеграторам

Если вы читаете api-reference.json, сначала проверяйте requiredScopes и запрашивайте весь список прав из него. Используйте requiredScope только когда requiredScopes отсутствует.

FIX-0917-29: параметр в скобочной форме получает 400 вместо 500

Было

Если параметр пришёл не строкой, а массивом или объектом (?portalId[]=x в query или массив в JSON-теле), GET /v1/keys, POST /v1/keys, POST /v1/feedback и PATCH /v1/feedback/{id} отвечали 500 с кодом внутренней ошибки. GET /v1/feedback под management-ключом отвечал 500 на ?portalId[]=x.

Стало

Такой запрос получает штатный 400 с тем же кодом, что и отсутствующее или неверное поле: MISSING_PORTAL_ID для portalId, VALIDATION_ERROR для category и resolution. Необязательный фильтр portalId у GET /v1/feedback в такой форме не применяется: ответ 200, как без фильтра. Для корректных строковых значений ответ не изменился, успешный HTTP 200 сохраняется.

2026-09-16

FIX-0916-1: партнёрским аккаунтам подняли бесплатные лимиты Коворка

Было

Бесплатное место Коворка считалось по общим окнам независимо от того, чей это аккаунт. GET /v1/cowork/me и GET /v1/cowork/state отдавали доли, посчитанные от общей сетки, а на исчерпании окна запрос к AI получал 402.

Стало

У партнёрского (NFR) аккаунта бесплатное место считается по своим окнам — они задаются на платформе и накладываются только вверх. Форма ответа прежняя: quotaPct и windows те же поля, но доли теперь считаются от партнёрского потолка, и 402 по исчерпанию приходит позже. Платное место партнёра не затронуто — оно считается по своему тарифу, и цена места не меняется.

Признак партнёрства платформа читает из лицензии аккаунта. У коробки он появляется после перерегистрации модуля Вайбкод на портале.

Влияние на интеграторов

Делать ничего не нужно: поля и статусы прежние, успешный ответ остаётся успешным. Если клиент держит у себя копию потолка, взятую из доли и расхода, её стоит пересчитывать по свежему ответу, а не кэшировать между сессиями.

NEW-0916-2: чтение сотрудников портала и их мест Коворка

Появился GET /v1/platform/cowork/members — сотрудники портала с текущими планами Коворка, потреблением и рекомендацией по тарифу. Метод рассчитан на кассу, которая рисует калькулятор мест: списка сотрудников, их планов и расхода у неё нет, всё это есть только на платформе Вайбкод.

Портал адресуется параметром portalNetworkId — идентификатором портала в Bitrix24.Network, тем же, что в строке GET /v1/platform/revenue/balances. Запасной ключ — portalDomain. Незнакомый портал ошибкой не считается: ответ приходит с portal.known равным false и пустыми списками, потому что так выглядит первый покупатель.

Блок portal несёт usersTotal и usersInVibecode, блок seats — выданные места assigned, места с истекающим сроком expiringWithin30Days и unassigned для мест про запас. Строка data несёт userId (идентификатор человека в Bitrix24.Network — то же значение, которое приезжает при входе с кассы), b24UserId, email, name, position, departmentIds, isAdmin, inVibecode, блок plan (code, status, source, months, validFrom, validUntil), блок usage (requestsMonth, quotaVibesMonth, usedVibesMonth, lastActiveAt) и recommendation (plan, reasonCode, confidence, facts). Сотрудник, которого на платформе Вайбкод ещё нет, приходит с userId равным null и опознаётся по b24UserId.

Отбор — filter со значениями ALL, PAID, EXPIRING, SUSPENDED, NO_PLAN, NOT_IN_VIBECODE, HAS_RECOMMENDATION; поиск по имени и должности — search. Страница: limit до 500 (по умолчанию 100), продолжение — cursor из nextCursor, время снимка — поле capturedAt. Незнакомый параметр запроса отбивается кодом INVALID_FILTER, а не игнорируется молча.

Поле degraded говорит, полон ли список. true означает, что сотрудников портала прочитать не удалось и в ответе только те, кто уже работает на платформе Вайбкод; тогда usersTotal — нижняя граница, а не точное число.

Авторизация — платформенный интеграционный ключ в заголовке Authorization: Bearer, доступ cowork:read, его выдаёт администратор платформы при выписке ключа. Доступ заведён отдельным от revenue:*: в ответе персональные данные сотрудников, а не суммы, и ключ бухгалтерии не должен получать их заодно.

FIX-0916-3: одновременная замена ключа приложения выдаёт ровно один ключ

Было

Ограничение «одна замена на приложение» держалось не всё время работы запроса, а лишь до того, как соседняя замена финишировала. Два одновременных вызова POST /v1/cowork/applications/{id}/key с РАЗНЫМИ значениями Idempotency-Key могли попасть в это окно и выдать по ключу: документированный отказ APPLICATION_KEY_REPLACE_IN_PROGRESS второму не приходил, слот карточки доставался тому, кто финишировал вторым, а ключ, выданный первому, молча переводился на суточный срок как заменённый — его владелец об этом не узнавал.

Стало

Ограничение действует всё время работы запроса. Тот, чья карточка успела уйти под чужую замену, получает 409 APPLICATION_KEY_REPLACE_IN_PROGRESS и ничего не чеканит — ровно тот отказ, который у метода уже описан. Ключ выдаётся один, и он же лежит в слоте.

Влияние на интеграторов

Менять ничего не нужно: код APPLICATION_KEY_REPLACE_IN_PROGRESS и совет по нему у метода прежние, новых кодов не добавилось. Изменилась частота: отказ приходит теперь и в той гонке, где раньше проскакивала вторая выдача. Обрабатывайте его как штатный исход параллельного вызова — прочитайте карточку приложения и посмотрите, нужен ли ещё один ключ. Если вы шлёте замену параллельно сами себе, надёжнее сериализовать вызовы у себя.

NEW-0916-4: детальная карточка сервера показывает порт, найденный агентом туннеля

GET /v1/infra/servers/:id в полной карточке сервера дополнительно возвращает detectedPort и detectedPortObservedAt: последний достоверно наблюдавшийся целевой порт туннеля и время наблюдения. До первого наблюдения оба поля равны null; неуспешное наблюдение не стирает прежнее. Сам GET не опрашивает машину, поэтому актуальность значения нужно оценивать вместе со временем и статусом подключения.

Влияние на интеграторов

Действий не требуется: новые поля аддитивны. Для диагностики фактической цели туннеля используйте detectedPort, а localPort продолжайте трактовать как настройку или fallback.

FIX-0916-5: код COWORK_HARNESS_DISABLED больше не приходит на ключах подписки Коворка

Было

PATCH /v1/keys/:id и POST /v1/keys/:id/rotate отвечали 403 с кодом COWORK_HARNESS_DISABLED, пока выдача ключей подписки для сторонних агентов была закрыта на платформе.

Стало

Этот код не приходит. Остальные проверки выдачи прежние: доступ владельца ключа к Коворку, разрешение администратора портала на сторонние клиенты и состояние подписки — каждая по-прежнему отвечает 403 со своим кодом, успешный ответ 200 остаётся успешным. Менять в интеграции ничего не нужно, обработку кода COWORK_HARNESS_DISABLED можно снять.

FIX-0916-6: Чтение объектов ограничивается отдельно для каждого портала

Было

GET /v1/storage/objects/:key и HEAD /v1/storage/objects/:key не ограничивали поток повторных запросов.

Стало

Каждый метод получил собственный бюджет 600 запросов в минуту на портал, общий для всех API-ключей этого портала. Обычные успешные ответы не изменились. После исчерпания бюджета API отвечает 429 RATE_LIMITED: действующее значение лимита приходит в x-ratelimit-limit, задержка перед повтором — в Retry-After.

Влияние на интеграторов

Менять обычные запросы не требуется. При 429 RATE_LIMITED выдерживайте Retry-After перед повтором.

FIX-0916-7: API показывает фактическое состояние туннеля

Было

После потери туннеля сервер мог оставаться CONNECTED в ответах GET и /refresh, поэтому клиент не видел действие repair, а /exec, /upload и /logs доходили до ошибки отсутствующего туннеля.

Стало

Список, карточка и refresh показывают фактическое состояние туннеля, включая DISCONNECTED и действие repair. Эта поправка статуса не записывается в базу. Перед exec, upload, logs и deploy API проверяет доступность сервера. Отсутствие туннеля считается подтверждённым только по непустому актуальному снимку соединений; пустой или недоступный снимок сохраняет записанный статус. Exec, upload и deploy могут восстановить подтверждённо отсутствующий туннель, а GET logs возвращает 409 SERVER_NOT_READY с указанием явного POST /repair, не запуская ремонт. Успешные ответы и их HTTP 200 статус сохраняются.

Затронутые эндпоинты: GET /v1/infra/servers, GET /v1/infra/servers/:id, POST /v1/infra/servers/:id/refresh, POST /v1/infra/servers/:id/exec, POST /v1/infra/servers/:id/upload, POST /v1/infra/servers/:id/deploy, GET /v1/infra/servers/:id/logs.

Влияние на интеграторов

Считайте DISCONNECTED и repair актуальным состоянием. После 409 SERVER_NOT_READY повторяйте операцию с обычным backoff и учитывайте Retry-After, когда заголовок присутствует. Для GET logs сначала вызовите указанный в hint POST /repair: сам GET ремонт не запускает. Форматы ответов и успешный HTTP 200 не меняются.

NEW-0916-8: ключи выгодных часов и компенсаций Коворка приходят всегда

В GET /v1/cowork/me и GET /v1/cowork/state ключи приходят в каждом успешном ответе: offPeak (оба ответа), touSavedPct (state), relief (оба ответа), boostPct и boostExpiresAt (me). Проверку наличия этих ключей можно снять — ответ 200 по-прежнему успешен, значения внутри блоков прежние, и null внутри блока сохраняет свой отдельный смысл. currentWindowEndsInHours в GET /v1/off-peak приходит в каждом успешном ответе, как и прежде: от включения по аккаунтам он не зависел никогда.

offPeakHint в отказе 402 cowork_quota_exhausted на POST /v1/chat/completions больше не зависит от включения возможности для аккаунта, но по-прежнему приходит, только когда момент разблокировки попадает в час со скидкой. Проверка наличия этого ключа остаётся.

FIX-0916-9: хранилище: файл, загруженный повторно под тем же ключом, больше не числится несколькими объектами

Было

До 04.09.2026 повторная загрузка файла под тем же ключом через личный ключ разработчика или от имени сервера создавала ещё один объект вместо замены. В ответе GET /v1/storage/objects один файл повторялся несколько раз под разными идентификаторами, а GET /v1/storage/objects/{key} и DELETE /v1/storage/objects/{key} работали с одной из копий, поэтому после удаления файл продолжал числиться в списке.

Стало

Копии приватных файлов свёрнуты: у ключа остаётся один объект с идентификатором самой ранней неудалённой копии и метаданными последней загрузки (sizeBytes, sha256, contentType, contentUpdatedAt). Удаление по ключу срабатывает с первого раза. Байты файлов не менялись. Копии публичных файлов пока остаются на месте, чтобы выданные на них ссылки GET /v1/public-storage/{portalId}/{objectId} продолжали работать. Объекты, загруженные ключом приложения, не затронуты.

Влияние на интеграторов

Менять ничего не нужно. Приватный файл читается и удаляется по ключу, а ключ не изменился. Идентификаторы свёрнутых копий пропадают из листинга.

FIX-0916-10: будильник на сервере с режимом работы больше не отбивается как конфликт с круглосуточным

Было

POST /v1/infra/servers/{id}/wake-schedules отвечал 400 ALWAYS_ON_CONFLICT любому серверу, у которого не задан порог засыпания по простою, — в том числе машине, которой владелец уже назначил режим работы по расписанию. Добавить такой машине дополнительное пробуждение было невозможно.

Стало

Сервер в режиме работы «по расписанию» (SCHEDULE) круглосуточным не считается: сон у него объявлен владельцем явно, окнами расписания. Дополнительный будильник на такой машине создаётся обычным порядком, ответ 201 не изменился. Для машины без расписания — в том числе в режиме ALWAYS — поведение прежнее: 400 ALWAYS_ON_CONFLICT, как и раньше.

BC-0916-11: Однозначный тип сущности при получении CRM-документов

Поддержка старого формата до: не предусмотрена

Было

GET /v1/crm-documents принимал повторённый entityTypeId с ответом HTTP 200 и мог вернуть документы другой сущности. Одновременная передача entityTypeId и entityTypeID также принималась.

Стало

Повтор параметра, оба имени вместе и скобочные формы возвращают HTTP 400 INVALID_ENTITY_TYPE, даже если значения совпадают. Для одиночной скобочной формы код ошибки изменён с MISSING_PARAMS на INVALID_ENTITY_TYPE. Одиночное положительное целое под именем entityTypeId или entityTypeID работает как прежде.

Что делать интеграторам

Передавайте ровно один параметр entityTypeId или entityTypeID с одним положительным целым значением. Не используйте массивы или объекты для типа сущности. Кодируйте пользовательский ввод при построении строки запроса.

NEW-0916-12: у событий календаря появился include: owner, host, attendee

Событие календаря теперь объявляет три связи на сотрудников, и их можно затребовать параметром include у GET /v1/calendar-events и GET /v1/calendar-events/{id}: owner — владелец календаря (по полю ownerId), host — организатор встречи (по meetingHost), attendee — приглашённые (по attendeeList). Раньше связей у сущности не было объявлено ни одной, поэтому любое имя отвергалось с 400 INVALID_INCLUDE, а список допустимых имён в тексте отказа был пустым.

Запрос вида GET /v1/calendar-events/{id}?include=owner,host отвечает 200 и кладёт карточки сотрудников в _included. Запрос без include работает как прежде. Связь читает карточку сотрудника, поэтому ключ обязан нести право user — иначе запрос отвечает 403 SCOPE_DENIED.

Имя вне этого списка отвергается по-прежнему, но отказ теперь называет доступные связи: Unknown include 'section'. Available: owner, host, attendee. Секции календаря связью не объявлены намеренно: в Битрикс24 нет чтения секции по идентификатору, она доступна только списком.

NEW-0916-13: пользовательские поля сотрудников через /v1/userfields/users

Пользовательские поля сотрудника (сущность users, префикс имени UF_USR_) теперь заводятся и правятся через API Вайбкод. Раньше платформа умела только читать их: поле сотрудника уже приезжало в GET /v1/users/fields со своим типом и списком значений, а на попытку создать такое же поле POST /v1/userfields/users отвечал 400 UNKNOWN_ENTITY со списком из шести сущностей CRM.

Появились шесть маршрутов: GET /v1/userfields/users (список), GET /v1/userfields/users/{id} (одно поле), POST /v1/userfields/users (создание), PATCH /v1/userfields/users/{id} (правка), DELETE /v1/userfields/users/{id} (удаление). Нужен скоуп user.userfield — голого user для этой сущности недостаточно. Тело создания то же, что у полей CRM: fieldName, userTypeId, label и остальные свойства поля. Имя поля сотрудника несёт префикс UF_USR_ и передаётся целиком — платформа его не дописывает.

GET /v1/userfields/users/types отвечает 400 UNSUPPORTED_ACTION: у Битрикс24 нет метода, перечисляющего типы полей сотрудника, поэтому userTypeId при создании указывается явно. Прежнее поведение шести сущностей CRM на /v1/userfields/{entity} не изменилось ни в одном ответе.

Та же сущность появилась в MCP: тула manage_userfield принимает entity: "users".

FIX-0916-14: отказ агрегации объясняет ограничение чтения без пагинации

Было

В POST /v1/{entity}/aggregate сообщение AGGREGATION_LIMIT_EXCEEDED предлагало постраничную выгрузку даже для сущностей, которые этот путь агрегации читает без пагинации.

Стало

Для таких сущностей сообщение объясняет защиту от неограниченного чтения при числовой агрегации и группировке. Оно предлагает count без groupBy, не обещая отсутствие чтения данных при подсчёте. Статус HTTP 422, код ошибки и лимит 5000 записей сохранены.

Влияние на интеграторов

Изменился только текст error.message для этого случая. Обрабатывайте отказ по коду AGGREGATION_LIMIT_EXCEEDED.

BC-0916-15: previousKey в ответе замены ключа приложения

Поддержка старого формата до: не предусмотрена

Было

POST /v1/cowork/applications/{id}/key на ветке rotated возвращал previousKey с обязательной датой graceUntil — моментом, до которого прежний ключ ещё аутентифицируется.

Стало

previousKey несёт { id, graceUntil, status }. Поле graceUntil теперь может быть null, и ровно тогда status равен BLOCKED: прежний ключ был заблокирован, не работал ещё до замены, и отсрочки ему не выдавалось. У живого прежнего ключа всё как раньше — дата и status ACTIVE. Ответ остаётся HTTP 201.

Что делать интеграторам

Допустите null в graceUntil и печатайте текст по status: при BLOCKED прежний ключ уже не работает, ждать нечего, и приложению нужен новый секрет прямо сейчас. Код, читающий graceUntil как всегда присутствующую дату, на таком ответе сломается.

FIX-0916-16: приложение с заблокированным ключом чинится заменой

Было

POST /v1/cowork/applications/{id}/key отвечал 409 KEY_ROTATE_NOT_ACTIVE, если личный ключ приложения был заблокирован. Освободить место под ключ было нечем: удаление отбивалось живым сервером, а включить ключ обратно нельзя — блокировка терминальна. Приложение чинилось только пересозданием.

Стало

Замена проходит. Блокировка при этом не отменяется: прежняя строка остаётся заблокированной, отсрочки на сутки ей не выдаётся, а выписанные ею живые токены доступа гасятся, а не переезжают на новый ключ. Отказ KEY_ROTATE_NOT_ACTIVE теперь означает только отозванный или просроченный ключ. Тексты отказов замены переписаны: три из четырёх называют конкретный раздел кабинета и действие, а отказ по системному ключу раздела не называет — его в кабинете нет, — и вместо этого говорит, что делать: обратиться в поддержку. Если живые токены заблокированного ключа погасить не удалось, замена не выдаётся за успех: ответ — 500 KEY_ROTATE_REVOKE_FAILED с details.orphanKeyId (id выписанного, но никому не отданного ключа), секрет не выдан. Дальнейшее решает одно поле. details.strandedSlots нет — слоты вернулись на прежний ключ целиком: отзовите ключ из orphanKeyId и повторите с НОВЫМ Idempotency-Key. Поле есть — откат удался не целиком, перечисленные категории всё ещё указывают на осиротевший ключ: повторять НЕЛЬЗЯ, отзывать его тоже нельзя — нужна поддержка. Одна запись там не категория слота: previousKeyGrace означает, что прежний ключ, заблокированный уже посреди замены, сохранил выданный ротацией суточный срок и перестанет работать на этом дедлайне. Повтор здесь хуже, чем бесполезен: он заменит тот ключ, на который карточка указывает СЕЙЧАС, и может ответить 201 с рабочим секретом, а живые токены утёкшего ключа так и не будут погашены — инцидент останется открытым за экраном, который говорит «готово».

Влияние на интеграторов

Ответ на успешную замену не изменился, кроме previousKey (отдельная запись). Клиент, ветвившийся на 409 KEY_ROTATE_NOT_ACTIVE как на «ключ заблокирован», должен убрать эту ветку: заблокированный ключ теперь заменяется, а этот код означает только отозванный или просроченный.

FIX-0916-17: POST /v1/keys/{id}/rotate принимает заблокированный ключ

Было

Ротация заблокированного ключа отвечала 403 KEY_BLOCKED. Ключ, который удерживал живой сервер, не чинился ничем: удалить его не давал сервер, перевыпустить — эта проверка.

Стало

Ротация проходит на тех же условиях, что и замена ключа приложения: блокировка остаётся, прежней строке не выдаётся отсрочка, её живые токены доступа гасятся. Если погасить их не удалось, ротация не выдаётся за успех: эндпоинт отвечает 500 KEY_ROTATE_REVOKE_FAILED с details.orphanKeyId (id уже выписанного, но не отданного ключа), а сырой секрет в таком ответе не возвращается. Если откат ротации сам прошёл не до конца, к ответу добавляется details.strandedSlots — что осталось указывать на осиротевший ключ; при чистом откате этого поля в ответе нет. Одна запись там не слот: previousKeyGrace означает, что прежний ключ сохранил суточный срок, выданный ротацией (так бывает, когда ключ заблокировали уже посреди неё и вернуть прежний срок не удалось), — на этом дедлайне он перестанет работать. Разблокировка по-прежнему невозможна — PATCH заблокированного ключа отвечает 403 KEY_BLOCKED, как и раньше.

Влияние на интеграторов

Клиент, который раньше получал на заблокированном ключе только 403, теперь получает 201 либо 500 KEY_ROTATE_REVOKE_FAILED. Ветвитесь по details.strandedSlots: поля нет — откат прошёл целиком, отзовите осиротевший ключ из details.orphanKeyId и повторите вызов — Idempotency-Key эндпоинт не поддерживает, так что повтор это обычный новый запрос, и он попробует погасить токены заново. Поле есть — повторять НЕЛЬЗЯ: часть хозяйства осталась на осиротевшем ключе, и повтор заменит уже его, а живые токены утёкшего ключа так и не будут погашены — ответ может оказаться даже успешным, но компрометация останется. Отзывать осиротевший ключ тоже нельзя, пока на него что-то указывает, — нужна поддержка. Успешные ответы не изменились.

NEW-0916-18: названия отделов в чтении сотрудников портала

В ответе GET /v1/platform/cowork/members появился блок departments — справочник отделов портала, идентификатор отдела → название. Один блок на ответ, а не поле в каждой строке: дерево у портала общее, а сотрудник состоит в нём не обязательно один раз.

Поле department в строке сотрудника теперь несёт название, когда оно однозначно: у человека ровно один отдел и он нашёлся в справочнике. У сотрудника из двух отделов поле остаётся null — выбирать из них «первый» значило бы выдать за факт результат сортировки, которую никто не закреплял.

Рядом появилось поле departmentIdsKnown — прочитан ли состав отделов этого сотрудника. Без него пустой departmentIds двусмыслен: он приходит и когда человек действительно ни в одном отделе не состоит, и когда мы про его отделы ничего не знаем — строка не попала в наложение Битрикс24 или наложение не доехало вовсе. Разница важна ровно там, где список группируют по отделам: при departmentIdsKnown равном false пустой массив означает «не знаем», и относить такого сотрудника к «без отдела» нельзя. Разбирать неоднозначные случаи следует по departmentIds строки и словарю departments, но только когда departmentIdsKnown равно true.

Пустой объект и null в поле departments значат разное. departments равен {}, когда отделов у компании нет: состав отделов прочитан у каждого сотрудника в ответе, и ни один из них ни в одном отделе не состоит. departments равен null, когда названий мы не знаем — наложение Битрикс24 не доехало, состав отделов прочитан не у всех, прочитать справочник не удалось, не хватило прав или Битрикс24 не ответил в срок. Сводить эти два ответа к одному значению нельзя: в первом случае показывать нечего, во втором стоит повторить запрос позже.

Существующие поля не изменились, запросы, собранные до этой записи, продолжают работать.

NEW-0916-19: каталог моделей: фильтр по возможности для ключей Коворка/Кода

GET /v1/models принимает необязательный параметр capability — явная выборка моделей по одной объявленной возможности вместо перебора полного списка на стороне клиента.

Параметр читается только у ключа со скоупом vibe:cowork. У остальных ключей он игнорируется: ответ остаётся прежним при любом значении, включая неизвестное. Набор допустимых значений закрыт и расширяется вместе с возможностями, которые платформа обслуживает для Коворка/Кода.

Ответ без параметра не изменился ни для одного ключа.

BC-0916-20: название приложения из одних пробелов отклоняется на всех путях записи

Поддержка старого формата до: не предусмотрена

Было

Название, состоящее только из пробелов, табуляций или переводов строки (например " "), проходило проверку «не короче одного символа». Приложение создавалось или переименовывалось, а его пункт меню в Битрикс24 получал пустое название.

Стало

Такое значение отклоняется с 400 VALIDATION_ERROR до обращения к Битрикс24 — во всех запросах, где задаётся отображаемое имя приложения или подписи встройки:

Название, в котором есть хотя бы один видимый символ, принимается как раньше и сохраняется без изменений, включая пробелы по краям. На уже сохранённые значения правило не распространяется: публикация приложения со старым пустым названием проходит как прежде.

Что делать интеграторам

Передавайте в title и catalogTitle название хотя бы с одним видимым символом. Если название формируется автоматически и может оказаться пустым, подставьте осмысленное значение по умолчанию до отправки запроса. Особое внимание — на сценарии переименования и повторной публикации: раньше они пустое название принимали.

FIX-0916-21: вызов на прежний адрес переехавшего портала отбивается названным отказом

Было

Портал коробки переехал на новый адрес, а вебхук ключа остался выписан на прежний. Вызов отбивался защитой (секрет не уходит туда, где портала больше нет), но клиент получал 500 с телом {"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error"}} — ни причины, ни подсказки, что делать.

Стало

Тот же вызов отвечает 409:

JSON
{"success":false,"error":{"code":"PORTAL_ADDRESS_CHANGED","message":"Portal address changed: this credential is issued for the portal's previous address. Re-issue the key webhook to continue"}}

Действие то же, что и раньше: перевыпустить вебхук ключа («Ключи» → «Перевыпустить»). Ключ, его строка и права не меняются.

2026-09-15

BC-0915-1: Black Hole классифицирует ответ приложения по автору, а не по статусу; новый код BH_APP_TIMEOUT

Поддержка старого формата до: не предусмотрена

Было

Шлюз перехватывал ответ приложения по HTTP-статусу: 502 и 504 — всегда, 503 — если ответ не был Content-Type: application/json. Перехваченный ответ подменялся на 503 с кодом BH_APP_STARTING и заголовком Retry-After: 3, тело и заголовки приложения при этом терялись.

Стало

Шлюз классифицирует ответ по его автору, а не по статусу. Ответ, который написало само приложение, доходит до вызывающего дословно — статус, заголовки, тело. Это касается 502, 504 и 503 любого типа, кроме двух форм, неотличимых от отказа самого агента: 503 с Content-Type: text/html и 502 совсем без Content-Type по-прежнему заменяются экраном BH_APP_STARTING. Помимо этих двух форм подменяется только то, что приложение не писало: кадра из туннеля не было вовсе, либо ответил сам агент — сообщил, что на порту никто не слушает, либо отказал сам (например его текстовый отказ по перегрузке, 503 Too many concurrent requests с Content-Type: text/plain, под эти две формы не подпадает и тоже доходит дословно — это не ошибка вашего приложения, а насыщение агента параллельными запросами).

Собственное окно шлюза, в которое от приложения не пришло вовремя полного ответа (30 секунд без единого кадра, либо уже начатый ответ замолчал дольше 15 секунд), больше не выдаёт себя за BH_APP_STARTING — теперь это отдельный код 504 BH_APP_TIMEOUT без заголовка Retry-After: запрос мог быть доставлен приложению и выполниться, поэтому автоматический повтор небезопасен. От собственного 504 приложения (который тоже проходит дословно) он отличается только телом — error.code: BH_APP_TIMEOUT в JSON-конверте шлюза.

Что делать интеграторам

Обрабатывайте 504 BH_APP_TIMEOUT отдельно от 503 BH_APP_STARTING: перед повтором записи проверьте эффект операции по устойчивому идентификатору, Retry-After в ответе не будет. Если код полагался, что 502/503/504 от вашего приложения всегда приходит как BH_APP_STARTING без содержимого, — обновите обработку: теперь такие ответы (кроме двух форм выше) проходят с реальным телом и заголовками приложения. Чтобы собственная ошибка приложения не подменялась шлюзом, не отвечайте на неё 503 с Content-Type: text/html и не отвечайте 502 вовсе без заголовка Content-Type.

Подробнее — Среда выполнения приложения и Что безопасно повторять.

FIX-0915-2: подписка на события портала принимает коды событий с точками

Было

POST /v1/infra/servers/:id/event-subscriptions отклонял официальные коды событий Битрикс24 с точками, например CATALOG.PRODUCT.ON.ADD, ответом 400 INVALID_EVENT. Подписаться на такие события было нельзя.

Стало

Метод принимает оба формата кода события: без точек (ONTASKADD) и с точками (CATALOG.PRODUCT.ON.ADD). Для кодов без точек ответ по-прежнему HTTP 200. Код не переписывается: точки не удаляются, и в доставленных событиях приходит тот же код. Строчные коды по-прежнему получают 400 INVALID_EVENT.

Влияние на интеграторов

Менять ничего не нужно. Теперь можно подписываться на события Битрикс24, коды которых содержат точки.

FIX-0915-3: модели bitrixgpt-* больше не называют базовую модель в ответе

Было

Рассказывая о себе, модель семейства bitrixgpt-* могла назвать в тексте ответа стороннюю базовую модель и её разработчика — и в обычном ответе (choices[].message.content), и в потоковом (choices[].delta.content).

Стало

Правило распознаёт утверждение модели о себе от первого лица: связку, у которой название стоит предикатом («я — …»), и формы самоописания («моя базовая модель …», «меня разработал …»). В таком утверждении название базовой модели заменяется на публичное имя модели, а разработчик — на Bitrix24. Правило работает одинаково в обычном и потоковом режиме и применяется по фактически обслужившей модели, а не по полю model из запроса.

Ответ остаётся HTTP 200, его структура не меняется. Упоминание чужой модели в обычном тексте ответа (например «Llama — модель с открытыми весами») не переписывается: правило срабатывает только на высказывание модели о самой себе и не разбирает произвольные формулировки — оно не заменяет продуктовую настройку идентичности. Не затрагиваются модели, у которых происхождение указано в публичном идентификаторе, ключи с собственными провайдерами (BYOK), поля reasoning_content и tool_calls.

На запросе с response_format (json_object или json_schema) правило не работает вовсе — одинаково в обычном и потоковом режиме. Остальная обработка ответа при этом не меняется: как и раньше, роутер извлекает блок рассуждений в reasoning_content.

FIX-0915-4: ссылка на страницу цен у аккаунтов Беларуси, Казахстана и Узбекистана ведёт на сайт их страны

Было

Аккаунт из Беларуси, Казахстана или Узбекистана без платного или демо-тарифа получал отказ BY_PAID_ONLY, KZ_PAID_ONLY или UZ_PAID_ONLY со ссылкой на страницу цен российского сайта Битрикс24: в полях details.upgradeUrl и alternatives[0].url ответа 402 метода POST /v1/infra/servers, в слоте capabilities.servers.create ответа GET /v1/me и в тексте userMessage. Купить там тариф для аккаунта из этих стран нельзя. Ссылка activation.tariffInfoUrl в ответе GET /v1/cowork/state у аккаунтов Казахстана и Узбекистана вела туда же.

Стало

Ссылка ведёт на страницу цен сайта страны аккаунта: https://www.bitrix24.by/prices/, https://www.bitrix24.kz/prices/ или https://www.bitrix24.uz/prices/. То же у любого другого отказа такого аккаунта, в котором есть ссылка на страницу цен. Коды отказа, статусы и структура ответа не изменились, поменялось только значение адреса. У аккаунтов других стран адрес прежний.

FIX-0915-5: приложения Black Hole на коробочном портале получают номер сотрудника при входе по прямой ссылке

Было

Сотрудник коробочного портала открывал приложение Black Hole по прямой ссылке, не входя в кабинет платформы. Если почта в его учётной записи Битрикс24 Нетворк не совпадала с почтой на портале, заголовок X-Vibe-User-Id приходил в приложение со значением net_<id>, как у посетителя не из портала. Именной доступ, выданный по номеру сотрудника, на такого посетителя не действовал.

Стало

Такой посетитель узнаётся по номеру сотрудника, уже записанному в его учётной записи на платформе Вайбкод. X-Vibe-User-Id приходит числом — тем же, что и при открытии приложения из портала, — и именной доступ по номеру сотрудника действует.

Влияние на интеграторов

Действий не требуется. Если приложение хранило данные таких посетителей под значением net_<id>, эти же люди теперь приходят с числовым идентификатором — тем, с которым они уже открывали приложение из портала.

FIX-0915-6: отказ на замороженном счёте не обещает оплату резервной модели из кошелька

Было

Ключ подписки Коворк/Код с исчерпанной квотой на счёте, замороженном за долг, получал от POST /v1/chat/completions отказ 402 ACCOUNT_FROZEN с текстом «резервная модель оплачивается из кошелька». Списания за ответ резервной модели не существует: он не расходует ни квоту подписки, ни баланс кошелька, а инференс платформа оплачивает сама. Интегратор, показывавший этот текст пользователю, объяснял отказ несуществующим списанием и отправлял искать деньги там, где их не снимают.

Стало

Текст называет настоящую причину: квота подписки исчерпана, а ответ резервной модели на замороженном счёте не выдаётся; продолжить работу можно после пополнения баланса. Код отказа ACCOUNT_FROZEN, статус 402 и форма тела прежние.

Влияние на интеграторов

Менять ничего не нужно: код отказа и статус не изменились. Обработчик, который показывает пользователю error.message, станет говорить правду про деньги.

FIX-0915-7: интеграционный ключ платформы переживает перевод создателя в администраторы

Было

Ключ /v1/platform/* закрывался при любом снижении платформенной ступени создателя, включая перевод из суперадминистратора в администратора. Администратор при этом выпускает такой же ключ сам, с теми же доступами, — то есть закрытие не отнимало полномочий, а только останавливало работающий канал: запросы по ключу начинали получать 401, и узнать об этом заранее было нечем.

Стало

Ключ закрывается, только когда его создатель остаётся без платформенной ступени вовсе: вывод из команды, понижение до сотрудника, блокировка, приём заявки на стирание аккаунта, истечение срока ступени администратора. Перевод между суперадминистратором и администратором ключ не трогает — в обе стороны.

Остальное поведение прежней записи в силе: закрытый ключ отвечает 401, о закрытии сообщает письмо остальным администраторам платформы, а новый ключ выпускается через POST /api/platform/integration-keys. Обработку 401 как сигнал «нужен новый ключ» интеграции стоит сохранить.

FIX-0915-8: отказ в доступе к приложению Black Hole объясняет, под какой учётной записью узнали посетителя

Было

Посетитель, которому отказано в доступе к приложению Black Hole, видел страницу отказа на домене самого приложения. Она не называла, под какой учётной записью его узнала платформа, поэтому «я вошёл не тем аккаунтом» было не отличить от «меня не добавили в список доступа». Разобраться можно было только выйдя из учётной записи и войдя заново.

Стало

Обычная вкладка браузера переводится на страницу платформы «Нет доступа к приложению». Она называет имя и почту самого посетителя — те, под которыми его узнали, — и домен портала приложения, если посетитель состоит в этом портале. Ничего об авторе приложения и о списке доступа страница не показывает.

Открытие внутри Битрикс24 ничего не меняет: во встроенном окне остаётся прежняя страница отказа, потому что уводить встроенное приложение на другой адрес нельзя. Программные вызовы тоже не меняются — они по-прежнему получают ответ 403 с кодом BH_ACCESS_DENIED.

Влияние на интеграторов

Действий не требуется.

NEW-0915-9: самоописание API называет интеграцию 1С

Самоописание платформы теперь ведёт агента в раздел интеграции 1С. В ответе GET /v1/guide появился блок onecApi — три операции раздела с описанием полей запроса, состояний операции и ссылками на документацию, а в GET /v1/me добавлена строка в api._rules с тем же маршрутом чтения данных. Прежние поля обоих ответов не изменились.

Страницы раздела описывают предусловия, порядок обмена и коды отказов — Интеграция 1С.

FIX-0915-10: ключ внешнего участника привязан к своему серверу

Было

Запрос с ключом внешнего участника мог содержать в адресе сервер, отличный от сервера, для которого выпущен ключ. При действующем доступе к указанному серверу API обрабатывал такой запрос вместо ответа с ошибкой несовпадения.

Стало

Теперь запрос, в адресе которого указан сервер, отличный от сервера ключа, получает EXTERNAL_COLLABORATOR_KEY_SERVER_MISMATCH без сведений о запрошенном сервере.

Влияние на интеграторов

Корректные вызовы с ключом своего сервера не меняются. Для работы с несколькими серверами используйте ключ соответствующего внешнего членства.

Затронутые эндпоинты: GET /v1/infra/servers/:id, PATCH /v1/infra/servers/:id, DELETE /v1/infra/servers/:id, POST /v1/infra/servers/:id/deploy, POST /v1/infra/servers/:id/exec, POST /v1/infra/servers/:id/upload, GET /v1/infra/servers/:id/logs, POST /v1/infra/servers/:id/wake, GET /v1/infra/servers/:id/sources, POST /v1/infra/servers/:id/sources, GET /v1/infra/servers/:id/sources/:versionId, PATCH /v1/infra/servers/:id/sources/:versionId, DELETE /v1/infra/servers/:id/sources/:versionId, GET /v1/infra/servers/:id/sources/:versionId/download.

BC-0915-11: публикация приложения больше не скрывает отказ привязки места

Поддержка старого формата до: не предусмотрена

Было

Публикация приложения отвечала HTTP 200 и переводила приложение в PUBLISHED, даже если Битрикс24 отклонил все запрошенные привязки. Обобщённый HTTP 401 ключа разработчика в прямой привязке мог ошибочно превращаться в INT_TARIFF_REQUIRED.

Стало

HTTP 401 ключа разработчика считается отказом учётных данных и при наличии OAuth-токена автоматически повторяется по OAuth, только если ответ Битрикс24 не называет окончательную неавторизационную причину — например, подписочный отказ или нехватку прав. Явные неавторизационные отказы остаются окончательными при любом HTTP-статусе. Если ни один транспорт не подтвердил привязку, публикация отвечает HTTP 502 PLACEMENT_BIND_FAILED, не переводит приложение в PUBLISHED и возвращает безопасные машинные код и статус Битрикс24 в error.failures. Если в том же запросе не подтверждено снятие старого места, его код приходит в error.failedUnbinds. Сохранённый список уже содержит успешно привязанную часть. Прямая привязка без успешного OAuth-отхода отвечает HTTP 403 B24_ACCESS_DENIED, а не тарифным пейволом.

Что делать интеграторам

Считайте публикацию успешной только при HTTP 200. При PLACEMENT_BIND_FAILED перечитайте приложение, сохраните уже привязанные места из data.placements следующего запроса чтения и повторите публикацию только после устранения перечисленных причин. Для B24_ACCESS_DENIED обновите учётные данные или передайте действующую OAuth-сессию. Решение об OAuth fallback платформа уже приняла до ответа; сохраняйте error.failures для диагностики, но не запускайте свой повтор по внутреннему коду Битрикс24.

NEW-0915-12: внешний API приложений открыт всем порталам

Адрес ANY /v1/applications/{id}/api/** доступен на всех порталах: платформа принимает HTTP-запрос снаружи — от робота бизнес-процесса Битрикс24 или от другого приложения, — доставляет его приложению по его туннелю и возвращает ответ приложения как есть. Принимаются GET, POST, PUT, PATCH, DELETE и HEAD; всё после /api/ приложение получает как свой путь запроса, строка запроса и тело передаются дословно. Путь проверяется в исходном и в однократно раскодированном виде, его предел — 2048 символов, недопустимая форма пути или строки запроса даёт 400 APP_API_BAD_PATH.

Ключ. Адрес принимает только ключ внешнего API, выпущенный для этого приложения в его карточке на платформе Вайбкод. Передаётся заголовком X-Api-Key либо Authorization: Bearer — формы равноправны. Личный ключ и ключ авторизации приложения получают 403 APP_API_NOT_GRANTED, а ключ внешнего API на любом другом адресе платформы — 403 APP_API_KEY_OUT_OF_SCOPE. Отсутствующий или отозванный ключ — 401 MISSING_API_KEY / 401 INVALID_API_KEY, ключ в режиме чтения на изменяющем вызове — 403 WRITE_BLOCKED_READONLY_KEY. Вызовы тарифицируются владельцу приложения как запросы API: замороженный счёт даёт 402 ACCOUNT_FROZEN, исчерпанная квота — 429 QUOTA_EXCEEDED.

Условия на стороне приложения. Канал открыт, когда в карточке приложения включён переключатель «Внешний API», у приложения есть сервер и сервер не засыпает. Иначе приходят 409 APP_API_NOT_ENABLED, 409 APP_API_NO_SERVER и 409 APP_API_NOT_ALWAYS_ON; неизвестное или удалённое приложение — 404 APP_API_APP_NOT_FOUND. Включённый переключатель открывает держателям ключа весь HTTP-контур приложения: отбора маршрутов на стороне платформы нет, аутентификацию и разграничение доступа своих маршрутов приложение делает само, и заголовок Authorization для этого остаётся свободным — платформа срезает его только тогда, когда в нём лежит её собственный ключ.

Что получает приложение. Исходные метод, путь, строку запроса, тело и заголовки вызывающего, кроме ключа платформы X-Api-Key, Cookie, Host, Content-Length, служебных заголовков соединения, всего префикса X-Vibe- и заголовков доверия к прокси — семейств X-Forwarded, X-Original, X-Rewrite, CF, Fastly и точечных имён, из которых библиотеки читают адрес клиента (Forwarded, X-Real-Ip, Client-Ip и подобные). Взамен платформа ставит X-Vibe-Request-Id, X-Vibe-Caller-Kind: external-api, X-Vibe-Caller-Portal-Id и X-Vibe-Caller-Key-Id; заголовков X-Vibe-User-* и X-Vibe-Authorization на этом пути нет — внешний вызов сессии не несёт.

Что получает вызывающий. Статус, тело и заголовки ответа приложения как есть, кроме Set-Cookie, Content-Length, служебных заголовков соединения и префикса X-Vibe-; ответы 3xx не разворачиваются. Отказ платформы от ответа приложения отличает заголовок X-Vibecode-Proxy-Error: 1 — он стоит на каждом ответе, который построила платформа, и приложение поставить его не может. Тело отказа — конверт V1 {"success": false, "error": {"code", "message"}}. Сквозной идентификатор вызова X-Vibe-Request-Id на ответе описан записью NEW-0914-13.

Пределы. Тело запроса — 4 МиБ, больше даёт 413 APP_API_PAYLOAD_TOO_LARGE. Тело ответа приложения — 4 МиБ, больше даёт 502 APP_API_RESPONSE_TOO_LARGE без Retry-After: повтор такой ответ не исправит. Частота — 120 запросов в минуту на ключ, действующее значение читайте из заголовка X-RateLimit-Limit; сверх него 429 APP_API_RATE_LIMITED с Retry-After. Приложение, которое не отвечает, недоступный или насыщенный канал — 503 APP_API_UNAVAILABLE с Retry-After в секундах; ответ, не пришедший за общий предел вызова в 30 секунд, — 503 APP_API_TIMEOUT с Retry-After (запись BC-0914-25). Ответ приложения вне контракта, например статусом 1xx, — 502 APP_API_BAD_ENVELOPE. Долгую работу выносите за пределы вызова: отвечайте сразу и досылайте результат отдельно.

NEW-0915-13: вложения в комментариях задач и скачивание файла комментария

Комментарий задачи теперь несёт список вложений. Ключ присутствует всегда: без файлов это пустой список. У каждого вложения есть идентификатор файла и путь для скачивания, а также имя и размер, когда Битрикс24 их отдал. Путь приходит без домена — склейте его с тем же базовым адресом API, к которому обращались. Комментарий, у которого нет текста, кроме приложенного файла, теперь различим по непустому списку вложений — раньше он приходил с пустым текстом и ничем не выдавал файл. Состав выдачи при этом не изменился: такой комментарий возвращался и прежде.

Скачать байты файла позволяет новая операция GET /v1/tasks/:taskId/comments/:id/files/:fileId/download. Ей достаточно доступа task: расширять права ключа до всего Диска не требуется, а отдать она способна только файл того комментария, который этому ключу и так доступен на чтение. Файл другого комментария, равно как и файл с совпавшим по числу идентификатором, операция не отдаёт. Отдаются именно байты — адрес файла на портале в ответ не попадает.

Список полей GET /v1/tasks/:taskId/comments/fields пополнился записью attachments с описанием формы элемента. Та же запись появилась в GET /v1/guide — в entities[task-comments].fields и fieldsDetailed, где форма элемента отдана машиночитаемо, ключом itemSchema. Это важно для агента без токена портала: живой список полей ему недоступен, а гайд отдаётся и так.

NEW-0915-14: типы цен каталога доступны через API

Идентификаторы типов цен зависят от портала, а узнать их через V1 было нечем: в скоупе catalog был обёрнут только склад. Из-за этого доки предлагали считать базовой ценой 1, и интеграции, сделавшие так, получали от Битрикс24 422 с сообщением про неверную группу цен.

Добавлена сущность только для чтения GET /v1/catalog-price-types — список, запись по идентификатору, поиск и справочник полей. В ответе приходят id, name, base, xmlId, sort, createdBy, modifiedBy, dateCreate и timestampX. Базовый тип цены портала — запись со значением base равным Y, и её id не обязан быть равен единице. Скоуп прежний, catalog, но его мало: catalog.priceType.* в Битрикс24 доступен только владельцу ключа с правами администратора портала. Запись не поддерживается, типы цен заводятся в интерфейсе Битрикс24.

Ответ 422 от POST /v1/catalog-prices на неизвестный catalogGroupId теперь несёт подсказку с указанием на новый эндпоинт. Утверждение «базовая цена — 1» убрано из документации цен каталога.

Затронутые эндпоинты: GET /v1/catalog-price-types, GET /v1/catalog-price-types/:id, POST /v1/catalog-price-types/search, GET /v1/catalog-price-types/fields, POST /v1/catalog-prices

BC-0915-15: ключ «только чтение» снова выкладывает приложение на свой сервер

Поддержка старого формата до: не предусмотрена

Было

Запись BC-0825-2 закрыла ключу в режиме «только чтение» любую запись на платформенных ручках, включая доставку кода на собственный сервер. POST /v1/infra/servers/{id}/deploy, POST /v1/infra/servers/{id}/exec, POST /v1/infra/servers/{id}/upload, POST /v1/infra/servers/{id}/icon и POST /v1/infra/servers/{id}/unstick отвечали 403 WRITE_BLOCKED_READONLY_KEY до выполнения операции.

Стало

Эти ручки такому ключу проходят, вместе с POST /v1/infra/servers/{id}/unstick — снятием залипшего exec-лока, восстановлением ровно для них. ⚠️ Это НЕ DELETE /v1/infra/servers/{id}/lock: тот снимает зависший лок операции и был исключением и раньше. Рамка решения: режим «только чтение» ограничивает данные Битрикс24, а собственное приложение вызывающего остаётся в его распоряжении. Группа неделима — выложить произвольный архив и означает исполнить произвольный код, поэтому разрешение на deploy без exec было бы мнимым ограничением.

Остальное поведение режима не изменилось. По-прежнему отвечают 403 WRITE_BLOCKED_READONLY_KEY создание сервера POST /v1/infra/servers, управление жизненным циклом (POST /v1/infra/servers/{id}/stop, start, reboot, POST /v1/infra/servers/{id}/wake, сон и расписания пробуждения), удаление сервера, а также ИИ, хранилище, ключи и места встраивания.

⚠️ Но перечень «что по-прежнему отказывает» не читайте как закрытый: у центрального ограничения есть и другие исключения, заведённые раньше этого изменения. POST /v1/apps таким ключом отвечает 201 — приложение и парный ключ заводятся в режиме «только чтение», а 403 приходит лишь на попытке выписать «чтение и запись». DELETE /v1/cowork/key проходит как аварийный выход, ровно как и DELETE /v1/infra/servers/{id}/lock выше. Полный состав исключений платформенного гейта приходит в writeRestriction.exceptions ответа GET /v1/me — сверяйтесь с ним, а не с этим абзацем.

Три эффекта такого ключа теперь видны порталу, и это цена решения. Первая выкладка сервера с публичным субдоменом заводит карточку приложения в каталоге портала, а её видят все сотрудники — иконка меняет картинку той же карточки. И выкладка на спящий сервер поднимает машину сама, то есть начинает расходовать баланс Вайбкод. Отдельный вызов пробуждения при этом остался запрещён: разбудить сервер, ничего на него не выкладывая, таким ключом нельзя.

И третье, самое важное для того, кто выдаёт ключи: режим «только чтение» перестал быть границей для владельца сервера. Выкладка и exec исполняют код в контейнере, где лежат выданные платформой креденшлы приложения — персональный API-ключ приложения (он в режиме «чтение и запись») и токены приложения к порталу. Прочитав окружение, держатель ключа «только чтение» получает право писать данные Битрикс24. Это принятая цена решения, а не недосмотр: ручка сама данные портала не пишет, но даёт доступ к тем, кто пишет.

Список исключений центрального ограничения приходит в ответе GET /v1/me полем writeRestriction.exceptions, а доступность операции — блоком capabilities. Слот capabilities.servers.deploy под ключом «только чтение» больше не приходит с available: false.

⚠️ Изменилась и форма одного соседнего ответа. GET /v1/infra/servers/{id}/logs на спящем галактик-приложении отдаёт 200 с блоком recovery, и поле recovery.recoveryAction стало УСЛОВНЫМ: оно отсутствует всякий раз, когда вызывающий пробуждение позвать не может. ⚠️ Список условий НЕ закрыт — ветвитесь по НАЛИЧИЮ поля, а не по перечню. ⚠️ Обратное не гарантировано: наличие поля означает лишь, что вызывающему не отказано по identity и режиму ключа; биллинг и административный запрет пробуждения — отдельные оси, и названный адрес может ответить 402 или 403. Сегодня в него входят: режим «только чтение», ключ Коворка/Кода, выключённый на аккаунте пилот галактик, ключ обслуживания агента (чтение логов ему открыто, пробуждение — нет) и владелец, у которого идёт удаление аккаунта; плюс доступ к приложению по связи, когда сервером владеет другой ключ. Раньше поле приходило всегда и называло адрес пробуждения, который такому ключу отвечал 403. Пустую строку вместо адреса не отдаём намеренно: машина попыталась бы её исполнить, а отсутствие ключа читается как «пробуждения тут нет, смотри соседние поля».

Условным стало и третье поле того же блока — recovery.poll, адрес опроса готовности. Он пропадает, когда GET /v1/infra/servers/{id} отказывает самому вызывающему: у ключа обслуживания агента этого маршрута нет в перечне, а у владельца с идущим удалением аккаунта он живёт в замороженном контуре, в отличие от чтения журнала. В этом состоянии подсказка велит просто перечитать журнал позже.

В том же состоянии поле recovery.wakeSchedule стало вердиктом про ВЫЗЫВАЮЩЕГО, а не только про приложение: оно приходит available: false, как правило с кодом той двери, которая отбила пробуждение (WRITE_BLOCKED_READONLY_KEY, INFRA_FORBIDDEN_FOR_COWORK_KEY, GALAXY_DISABLED, AGENT_MAINTENANCE_KEY_OUT_OF_SCOPE, user_self_deletion_pending, NOT_FOUND — набор открыт, неизвестный код обрабатывайте как отказ). Прежде поле судило одну пригодность приложения и могло прийти available: true рядом с подсказкой «окно вам тоже откажут» — клиент, ветвящийся по нему, шёл в гарантированный 403. Создание окна — та же запись, и отбивают её в основном те же двери, что и пробуждение. Исключение — дверь окна, которая проверяется раньше двери пробуждения: тогда code и текст hint называют эту дверь окна.

⚠️ Но НЕ только они, и «кто может будить — у того ничего не изменилось» неверно: у окна есть ДВЕ СВОИ двери, и каждая отвечает СВОИМ кодом. Первая: маршрут создания окна может быть вне перечня, разрешённого вашему ключу, тогда как само пробуждение внутри него — так устроен ключ внешнего участника; код EXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE. Вторая: маршруту окна нужен скоуп vibe:infra, которого чтение логов и пробуждение НЕ требуют, поэтому до этого отказа доходит и обычный ключ владельца без скоупа; код INFRA_SCOPE_REQUIRED, и лечится он выдачей скоупа, а не сменой режима ключа. Любая из двух даёт available: false ДАЖЕ тому, кому пробуждение разрешено, и адрес окна тогда не называется ни в одном поле ответа. Ветвитесь по available, а не по тому, доступно ли вам пробуждение.

Появилось новое условное поле recovery.deliveryWakesHost — второй путь подъёма. Приходит, когда пробуждение вызывающему недоступно, а деплой при этом доступен, и несёт action (вызов деплоя) и cost (его цену словами). Основание: ключу «только чтение» деплой на свой сервер РАЗРЕШЁН этим же изменением, а деплой на спящее галактик-приложение поднимает хост сам — значит для него «поднять нечем» было бы неправдой. Цена названа рядом с адресом намеренно: это выкладка кода и расход баланса, а не пробуждение, и звать её ради одного лишь чтения журнала не надо.

⚠️ Поле условно ВДВОЙНЕ, и это важно для клиента: послабление снимает у деплоя гейт РЕЖИМА и только его. Ключу Коворка/Кода, порталу с выключенным пилотом галактик и ключу обслуживания агента POST …/deploy отказывают те же двери, что и пробуждению, — им поле не приходит вовсе. Проверяйте его наличие, а не предполагайте: нет поля — открытого пути нет.

Вместе с полями в этом состоянии изменился и ТЕКСТ ответа. Проза hint и recovery.reason больше не называют ни адрес пробуждения, ни создание окна по расписанию. Вместо адреса hint называет РОВНО ТО условие, которое сработало у этого вызывающего (а не список через «или»), и второй путь подъёма вместе с ценой. Вызывающему, который пробуждение позвать может, текст и состав полей не изменились — кроме случаев, когда создание окна ему закрыто отдельно. Тогда recovery.wakeSchedule приходит available: false с кодом той двери, а адрес окна не называется ни одним полем. Таких дверей две, и обе не совпадают с дверями пробуждения: маршрут окна вне перечня, разрешённого ключу (так устроен ключ внешнего участника — пробуждение ему открыто, окно нет) — EXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE; и отсутствие у ключа скоупа vibe:infra, который проверяется ПЕРВОЙ строкой обработчика окна, тогда как чтение логов и пробуждение его не требуют, — INFRA_SCOPE_REQUIRED. Код приходит от той двери, которая сработала у вас, а не от соседней: перечень маршрутов проверяется раньше всех прочих дверей, а скоуп — позже дверей режима, заморозки, Коворка и пилота, но РАНЬШЕ проверки владения сервером. Поэтому вызывающему, который пришёл по связи приложения и при этом не несёт vibe:infra, придёт INFRA_SCOPE_REQUIRED, а не NOT_FOUND.

Что делать интеграторам

Читать recovery.recoveryAction в ответе логов только после проверки, что ключ есть: без проверки клиент разыменует отсутствующее поле и уйдёт либо в исключение, либо в запрос по адресу undefined.

Не вытаскивать адрес пробуждения из hint регулярным выражением: в этом состоянии его там нет, и выражение вернёт пустой результат, а не отказ.

Не ветвиться по recovery.wakeSchedule.available, считая его вердиктом про приложение: теперь он учитывает и право вызывающего, и у отбитого приходит false там, где раньше приходил true. Клиент, который на true создавал окно, отказа больше не получит — он его и не запросит.

Проверять НАЛИЧИЕ recovery.deliveryWakesHost, а не предполагать его: поле приходит только тем, кому деплой действительно доступен. И прежде чем звать action, прочитать соседний cost: вызов выкладывает код и тратит баланс. Как способ «просто разбудить, чтобы прочитать журнал» он не годится.

Проверять НАЛИЧИЕ recovery.poll — третьего условного поля того же блока. Адрес опроса готовности пропадает, когда GET /v1/infra/servers/{id} отказывает самому вызывающему: у ключа обслуживания агента этого маршрута нет в перечне, а у владельца с идущим удалением аккаунта он живёт в замороженном контуре. Клиент, написанный по прежнему контракту, где поле приходило всегда, разыменует отсутствующее поле и уйдёт либо в исключение, либо в опрос адреса undefined. В этом состоянии готовность не опрашивают вовсе — подсказка велит перечитать журнал позже.

Клиенту, который вызывает эти ручки, менять нечего — отказ просто перестал приходить.

Действие требуется администратору портала, который выдавал ключи «только чтение» именно потому, что такой ключ не мог выкладывать код. Проверьте, кому выданы такие ключи. Режим доступа для этой цели больше не подходит: он ограничивает данные Битрикс24, а не доставку.

Важно: снятие права vibe:infra доставку НЕ закрывает — оно проверяется только на создании сервера и на окнах пробуждения. Доступ к доставке даёт любая из трёх веток: сервер заведён этим ключом; сервер привязан к приложению, у которого этот ключ основной; владелец ключа состоит в команде разработки сервера с правом на код.

Из-за этого надёжный рычаг один — отозвать ключ. Перепривязка сервера к другому ключу доставку НЕ закрывает: она меняет владельца сервера, но не трогает ни привязку приложения, ни членство в команде разработки, поэтому прежний ключ продолжит выкладывать код через эти две ветки.

NEW-0915-16: Уведомления об изменении баланса портала

Добавлены balanceChanged и balance.get для синхронизации баланса портала с модулем. Уведомления схлопываются; следующая попытка резервируется через час. При задержках выполнения фактический интервал между отправками может быть короче часа. Постановка после денежного коммита допускает потерю сигнала; следующее изменение позволяет получить актуальный баланс. version обозначает поколение уведомления, а не ревизию баланса: модуль последовательно перечитывает и сохраняет ответы, включая ответы с прежним поколением. Флаг portal-balance-events выключен по умолчанию.

FIX-0915-17: адрес переключателя режима для ключа авторизации приложения учитывает, где живёт карточка приложения

Было

Отказ по режиму доступа WRITE_BLOCKED_READONLY_KEY для ключа авторизации приложения (vibe_app_*) всегда отдавал в details.switchUrl кабинет приложений "/applications". Но карточка есть там не у каждого приложения: у приложения, не заведённого в разделе «Приложения», её там нет, и держатель такого ключа попадал на страницу без переключателя.

Стало

Для ключа авторизации приложения details.switchUrl указывает на страницу, где лежит карточка именно этого приложения: кабинет приложений "/applications", если приложение заведено в этом разделе, иначе раздел ключей авторизации "/apps". Там карточка открывается из меню строки, пункт «Информация», и блок Режим доступа стоит в ней сразу под ключом. Для личного ключа ("/keys") и менеджмент-ключа ("/management-keys") адрес не изменился. Текст сообщения по-прежнему называет тот же адрес, что и поле, а код отказа и сам ответ остаются прежними — изменилось только значение switchUrl.

Влияние на интеграторов

Клиент, который читает switchUrl из ответа, ничего менять не должен. Подробности — на страницах Режим доступа и Ошибки авторизации.

NEW-0915-18: открытые линии: чаты по карточке CRM и подключение оператора к диалогу

Добавлены три эндпоинта, все требуют скоуп imopenlines и работают на любом портале.

GET /v1/openlines/crm/chats?crmEntityType=lead|deal|company|contact&crmEntityId=N отдаёт чаты открытой линии, привязанные к объекту CRM: { "success": true, "data": [ { "chatId": 2043, "connectorId": "telegrambot", "connectorTitle": "Telegram" } ] }. Необязательный activeOnly=false добавляет к открытым диалогам завершённые. У объекта без диалогов data пустой. Значение crmEntityType вне списка отбивается ответом 400 INVALID_PARAMS до вызова Битрикс24.

POST /v1/openlines/sessions/intercept с телом { "chatId": 2043 } переводит диалог на текущего оператора и отвечает { "success": true, "data": { "chatId": 2043, "intercepted": true } }. POST /v1/openlines/sessions/join с тем же телом добавляет оператора в диалог ещё одним участником и отвечает { "success": true, "data": { "chatId": 2043, "joined": true } }. Оба принимают chatId числом и строкой формы chat2043, оба пишут в живой диалог с клиентом и потому недоступны ключу «только чтение» — он получает 403 WRITE_BLOCKED_READONLY_KEY.

Прежде найти диалог открытой линии по карточке лида, сделки, компании или контакта было нечем, а из операторских действий API давал только POST /v1/openlines/operator/answer и POST /v1/openlines/operator/finish. Прежние запросы работают как работали.

Документация — Чаты по карточке CRM.

2026-09-14

BC-0914-1: освобождение канала может отказать для защиты работающей операции

Поддержка старого формата до: не предусмотрена

Было

POST /v1/infra/servers/:id/unstick без force мог прервать длительную операцию, которую не обнаружила проверка перед сбросом канала.

Стало

Метод тщательнее проверяет занятость сервера. При обнаруженной блокировке операции он возвращает 409 OPERATION_IN_PROGRESS; если проверка недоступна — 503 LOCK_STATE_UNAVAILABLE. В обоих случаях канал не сбрасывается. force=true или force=1 сохраняет возможность намеренно прервать операцию.

Что делать интеграторам

Обрабатывайте оба отказа. При 409 OPERATION_IN_PROGRESS проверьте состояние операции и дождитесь её завершения. При 503 LOCK_STATE_UNAVAILABLE повторите запрос позже. Добавляйте force=true или force=1 только по осознанному решению прервать операцию, а не автоматически в ответ на отказ.

BC-0914-2: удаление и список объектов хранилища получили лимит запросов на портал

Поддержка старого формата до: не предусмотрена

Было

DELETE /v1/storage/objects/{key} и GET /v1/storage/objects принимали запросы без ограничения частоты. Клиент, который повторял один и тот же вызов в цикле, мог часами держать тысячи запросов в минуту и замедлял этим работу API для всех порталов той же площадки. Запрос HEAD к списку объектов обслуживался как полноценный GET: набор читался целиком, а отдавались только заголовки.

Стало

На каждую из двух операций действует лимит 600 запросов в минуту на портал; все API-ключи одного портала делят один лимит, и у каждой из двух операций он считается отдельно. Действующее значение берите из заголовка ответа X-RateLimit-Limit, а не из числа в этом тексте: оно может оказаться ниже. При превышении API Вайбкод отвечает 429 RATE_LIMITED с заголовками X-RateLimit-Remaining, X-RateLimit-Reset и Retry-After; срока в теле ответа нет — берите его из заголовка. Метод HEAD на адресе списка больше не обслуживается — вместо него используйте GET с limit=1. Успешные ответы обеих операций, их коды и формат данных не изменились, поведение остальных методов хранилища не изменилось.

Что делать интеграторам

  1. Обработка 429 на удалении и списке. Обе операции получили лимит, которого у них не было. Если ваш код удаляет объекты пачкой или в цикле либо часто перечитывает список — добавьте обработку 429 RATE_LIMITED с паузой по заголовку Retry-After. Бюджет общий на портал, поэтому выпуском второго ключа его не расширить.
  2. Не задавайте потолок числом в коде. Читайте X-RateLimit-Limit из ответа.
  3. Замените HEAD на список на GET /v1/storage/objects?limit=1.
  4. Проверьте цикл повторов. Повторное удаление уже удалённого объекта отвечает 410 STORAGE_OBJECT_DELETED — это признак того, что объект удалён, а не что операцию надо повторить. Код, повторяющий вызов на 410, теперь упрётся ещё и в лимит.

FIX-0914-3: правка и удаление смарт-процесса отвечают кодом отказа портала, а не внутренней ошибкой

Было

PATCH /v1/smart-processes/{id}, DELETE /v1/smart-processes/{id} и пакетная правка или удаление через POST /v1/smart-processes/batch (action: update / delete) перед записью запрашивают у портала внутренний номер смарт-процесса по его entityTypeId. Если Битрикс24 отвечал на этот запрос отказом — превышен лимит обращений, портал недоступен или не ответил вовремя, — клиент получал код INTERNAL_ERROR со статусом, повторяющим статус портала (429, 502, 503 или 504), и без заголовка Retry-After. Тот же отказ портала на следующем шаге тех же операций приходил как 429 RATE_LIMITED, 502 BITRIX_UNAVAILABLE или 503 BITRIX_TIMEOUT — клиент, который повторяет вызов по этим кодам, при INTERNAL_ERROR попытку не повторял.

Стало

Отказ портала на этом шаге отвечает так же, как на любом другом обращении к порталу: 429 RATE_LIMITED (в том числе для лимита, который портал присылает со статусом 503) и 503 BITRIX_TIMEOUT — с заголовком Retry-After, 502 BITRIX_UNAVAILABLE — без него. Запись при таком отказе на портал не отправляется. Успешный ответ по-прежнему 200 (у DELETE /v1/smart-processes/{id} — 204). 404 SMART_PROCESS_NOT_FOUND на неизвестный порталу entityTypeId и 400 на entityTypeId, который не является положительным целым числом (INVALID_ENTITY_TYPE_ID у одиночных операций, BATCH_ITEM_VALIDATION у пакетной) — без изменений. Подробности по кодам — на странице Ошибки.

NEW-0914-4: создание лида предупреждает, когда портал сконвертировал его сам

На портале в простом режиме CRM (без лидов) Битрикс24 конвертирует лид сразу при создании: он приходит CONVERTED и закрытым, а из него создаются контакт и сделка, которых в запросе не было, — запрошенная стадия не сохраняется. Вайбкод в запросе ничего не меняет: так ведёт себя сам портал. Теперь POST /v1/leads в этом случае добавляет в meta.warnings предупреждение LEAD_AUTO_CONVERTED с номерами созданных порталом контакта (contactId) и сделки (dealId); null — если сущность не создана или не найдена: контакт, привязанный вами через contactId, портал переиспользует. Ответ по-прежнему 201 с созданной записью; лид, оставшийся открытым, ответ не меняет. Предупреждения нет, если CONVERTED запрошен явно.

BC-0914-5: отказ при смене политики называет нужный ключ

Поддержка старого формата до: не предусмотрена

Было

PATCH /v1/infra/servers/:id/access-policy отвечал 404 NOT_FOUND, когда ключ был связан с приложением и сервером, но сервером управлял другой ключ.

Стало

В этом случае метод отвечает 403 SERVER_MANAGING_KEY_REQUIRED и предлагает использовать управляющий ключ сервера или кабинет. Права не расширились; для посторонних ключей ответ остаётся 404 NOT_FOUND.

Что делать интеграторам

Обрабатывать SERVER_MANAGING_KEY_REQUIRED отдельно от отсутствующего сервера и повторять смену политики с управляющим ключом.

BC-0914-6: поиск компаний не принимает служебный индекс, контакты поддерживают фильтр по категории

Поддержка старого формата до: не предусмотрена

Было

Фильтр компаний по searchContent принимался с HTTP 200 и предлагался в подсказке UNKNOWN_FILTER_FIELD, хотя служебный индекс не предназначен для поиска обычного текста. Фильтр контактов по categoryId отклонялся с HTTP 400, несмотря на поддержку в Битрикс24.

Стало

Фильтр компаний по searchContent возвращает 400 UNKNOWN_FILTER_FIELD, поле исключено из списка допустимых фильтров. Чтение поля и его выбор через select сохранены. Фильтр контактов по categoryId принимается и перечисляется в подсказке Also filterable.

Что делать интеграторам

Замените текстовый поиск по searchContent фильтром title с $contains. Ограничение действует и для запросов с подготовленной строкой служебного индекса. Для фильтра контактов используйте точное написание categoryId, например { "filter": { "categoryId": 0 } }.

Затронутые эндпоинты: GET /v1/companies, POST /v1/companies/search, GET /v1/contacts, POST /v1/contacts/search. Эти правила действуют также в фильтрах пакетных запросов и агрегации.

BC-0914-7: список объектов хранилища не считает весь набор по умолчанию

Поддержка старого формата до: не предусмотрена

Было

GET /v1/storage/objects возвращал обязательное точное поле total на каждой странице. Список с prefix сортировался по id в убывающем порядке, а ключ приложения без пользовательского токена мог включать персональные объекты сотрудников.

Стало

По умолчанию ответ содержит только data и cursor. Точное поле total возвращается только с withTotal=true. Без prefix сохраняется порядок id по убыванию, с prefix действует побайтовый порядок key по возрастанию и id по убыванию. Новый непрозрачный курсор привязан к владельцу, префиксу и порядку. Ключ приложения без пользовательского токена перечисляет только общие объекты приложения, а ключ с пользовательским токеном — только объекты соответствующего сотрудника Битрикс24.

Что делать интеграторам

Завершайте обход только при cursor: null и передавайте непустой курсор без разбора и изменений. Обход с prefix, начатый старым курсором, перезапустите без курсора. Передавайте withTotal=true только в тех запросах, где нужен точный подсчёт. Персональные объекты сотрудника запрашивайте с его пользовательским токеном.

BC-0914-8: создание шаблона документа из файла на Диске требует скоуп disk

Поддержка старого формата до: не предусмотрена

Было

POST /v1/doc-templates принимал ID объекта Диска в fileId с одним скоупом documentgenerator и отвечал 201, но созданный шаблон не позволял сформировать документ: последующий вызов возвращал 422 BITRIX_ERROR с b24Code=FILE_NOT_PROCESSABLE.

Стало

Вариант с fileId создаёт пригодный для генерации документов шаблон и требует у ключа оба скоупа: documentgenerator и disk. Файл может занимать до 2 МиБ. Вариант с содержимым .docx в base64-поле file не требует disk и продолжает работать со скоупом documentgenerator.

Что делать интеграторам

Добавьте скоуп disk в ключи, которые создают шаблоны через fileId. Для вызовов с base64-полем file менять ключ не нужно.

NEW-0914-9: Демо Vibe+ открывает агентов на международной платформе

Активное демо Vibe+ открывает агентов и управляемых ботов с общим лимитом одной виртуальной машины. Платная редакция Vibe+ сохраняет доступ. Управляемые боты также требуют включённой функции на платформе. После окончания демо доступ отзывается, сущность сохраняется, а машина замораживается при плановой проверке. Запуск, пробуждение, перезагрузка, починка, смена конфигурации и восстановление демо-машины, включая создание машины из её бэкапа, могут вернуть VIBE_DEMO_EXPIRED (HTTP 403). Другие регионы и существующие машины без отметки демо сохраняют прежнюю модель доступа.

FIX-0914-10: поля очереди открытой линии отказывают понятно вместо ошибки портала

Было

Справочник полей и бессессионное знакомство агента перечисляли у конфигураций открытых линий все 95 полей, включая queue, queueFull, queueUsersFields и queueOnline. Имя одного из этих четырёх в select, filter или sort у GET /v1/openline-configs и POST /v1/openline-configs/search уезжало в Битрикс24 и возвращалось ответом портала 422 BITRIX_ERROR с текстом An unknown field 'QUEUE' is passed in the PARAMS field 'select' — в написании, которого клиент не отправлял, и без указания, где поле всё-таки читается.

Стало

Обе ручки отказывают до вызова Битрикс24: 400 UNSUPPORTED_LIST_FIELD. Сообщение называет написание, которое прислал клиент, позицию (select, filter или sort), все четыре поля границы и маршрут, который их отдаёт, — GET /v1/openline-configs/:id. Та же граница теперь записана в описаниях этих полей в GET /v1/openline-configs/fields и в разделе сущности у GET /v1/guide. Набор полей в ответах списка и поиска не изменился: этих четырёх там не было и раньше.

Влияние на интеграторов

Действий не требуется. Запрос, который раньше получал 422, теперь получает 400 с адресом рабочего маршрута; успешные вызовы не затронуты.

FIX-0914-11: партнёрская NFR-лицензия распознаётся как коммерческая

Было

При расхождении данных о лицензии в Битрикс24 GET /v1/me мог вернуть для партнёрской NFR-лицензии data.tariff.code со значением бесплатного тарифа и data.tariff.isCommercial: false. Коммерческие возможности при этом отображались недоступными.

Стало

GET /v1/me распознаёт такую лицензию по коду nfr, возвращает data.tariff.isCommercial: true и оценивает возможности как для коммерческого тарифа.

Влияние на интеграторов

Изменять клиент не требуется. После обновления снимка тарифа прежний ошибочный запрет снимается автоматически.

FIX-0914-12: вызов метода 1С принимает ответ модуля 1С в его фактическом формате

Было

Вызов POST /v1/onec/tools/{method}/call доходит до модуля 1С. Модуль отвечает на getData документом {"data": [...], "errors": []} — массив строк и параллельный массив ошибок. Платформа ждала только конверт {"success": true, "data": {...}} и отвергала такой ответ целиком: операция заканчивалась статусом failed с кодом ONEC_INVALID_INLINE_RESULT, хотя модуль вернул HTTP 200 и настоящие данные. Строки терялись, и отличить эту ситуацию от недоступности 1С по ответу API было нельзя.

Стало

Платформа принимает оба конверта. Ответ {"data": [...], "errors": []} переводится в стандартный результат операции: сам массив становится rows, а columns — это объединение ключей всех строк страницы в порядке первого появления. Если ключей не нашлось ни в одной строке — в том числе когда страница пуста, — платформа возвращает в columns набор, запрошенный в вызове, а при его отсутствии пустой список. Признак truncated выводится из заполненности страницы — страница, в которой строк не меньше, чем запрошено в limit, помечается усечённой и отдаёт nextOffset, равный смещению запроса плюс число возвращённых строк; более короткая страница считается последней и отдаёт nextOffset: null. Непустой errors становится доменной ошибкой операции: платформа берёт код из первого элемента, когда он назван одним из документированных (METHOD_NOT_FOUND, ACCESS_DENIED, INVALID_FILTER, TIMEOUT и прочие), иначе сообщает INTERNAL. Ответ, уже соответствующий контракту, обрабатывается ровно как раньше.

NEW-0914-13: ответ внешнего API приложений несёт X-Vibe-Request-Id

Ответ канала ANY /v1/applications/{id}/api/** теперь может нести заголовок X-Vibe-Request-Id со сквозным идентификатором вызова — тем же, который платформа записывает в свой журнал. Указывайте его значение при обращении в поддержку.

Заголовок есть ровно тогда, когда идентификатор был выдан, то есть вызов дошёл до туннеля и туннель ответил: на ответе самого приложения с любым его статусом, а также на отказах 503 APP_API_UNAVAILABLE и 502 APP_API_RESPONSE_TOO_LARGE, пришедших ответом туннеля, и на 502 APP_API_BAD_ENVELOPE.

Заголовка нет там, где идентификатор ещё не существовал или ответ туннеля не дошёл: 401 и 403 по ключу, 429 APP_API_RATE_LIMITED, 409 APP_API_NOT_ENABLED, 403 APP_API_NOT_GRANTED, 404 APP_API_APP_NOT_FOUND, 400 APP_API_BAD_PATH, 413 APP_API_PAYLOAD_TOO_LARGE, а также 504 APP_API_TIMEOUT и те 503 APP_API_UNAVAILABLE / 502 APP_API_RESPONSE_TOO_LARGE, которые платформа вынесла сама (приложение недоступно, канал насыщен, туннель не отвечает, ответ не влез в потолок на нашей стороне).

Из этого следует правило для клиента: различает не код ответа, а наличие заголовка — один и тот же 503 APP_API_UNAVAILABLE приходит и с ним, и без. Заголовок необязательный, обрабатывайте его отсутствие как штатный случай.

Всё остальное в ответе прежнее — статус, тело и остальные заголовки не изменились, менять в клиенте ничего не нужно.

BC-0914-14: у `Idempotency-Key` на создании сервера появилось окно реплея в 15 минут

Поддержка старого формата до: не предусмотрена

Было

POST /v1/infra/servers реплеил создание по Idempotency-Key бессрочно: пока строка сервера жива, тот же ключ возвращал 201 с заголовком Idempotent-Replayed независимо от того, сколько времени прошло. Реплей при этом минует предпроверку прав на создание целиком — включая отказы, не зависящие от числа машин: недоступный регион, нечитаемый тариф портала, тариф вне разрешённого списка. Портал, потерявший право создавать серверы, всё равно получал 201 по старому ключу.

Стало

Реплей действует 15 минут с момента создания машины. После окна тот же ключ реплей не даёт: запрос идёт как новое создание и проходит все проверки прав — то есть портал без права создания получит обычный отказ (402 / 403), а не машину.

Повторить создание старым ключом нельзя и тогда, когда права в порядке: ключ израсходован и отвечает 409 IDEMPOTENCY_KEY_ALREADY_USED. Текст ответа говорит, что именно произошло: сервер удалён — берите новый ключ; сервер существует — сначала найдите его через GET /v1/infra/servers, иначе оплатите вторую машину рядом с первой.

Одно ограничение остаётся прежним и не связано с окном: ПЕРВОЕ создание в галактике ключом не защищено — он там не резервируется вовсе. Это отдельная известная дыра, она была и до изменения. При этом ключ, уже израсходованный прежним standalone-созданием, на galaxy-пути теперь отвергается с 409, а не игнорируется молча.

Что делать интегратору: на повтор после долгой паузы сначала проверьте GET /v1/infra/servers, не создал ли сервер первый запрос, и повторяйте только если его там нет. Если всё же повторяете вслепую — шлите ТОТ ЖЕ Idempotency-Key, а не новый: для standalone-создания он вернёт либо сам сервер, либо 409 IDEMPOTENCY_KEY_ALREADY_USED, чей текст скажет, существует сервер или удалён. ⚠️ На galaxy-размещении и этого мало: первое создание там ключ не резервирует, поэтому повтор даже с тем же ключом может дать второй ресурс — для галактики проверка через GET обязательна, а не желательна.

FIX-0914-15: OpenAPI различает личный ключ и ключ OAuth-приложения

Было

Все операции в GET /v1/openapi.json несли одну схему авторизации security: [{ apiKey: [] }], а её описание говорит, что заголовок X-Api-Key принимает и личный ключ (vibe_api_…), и ключ приложения (vibe_app_…), и управляющий ключ (vibe_live_…). Для маршрутов, которые Битрикс24 привязывает к ПРИЛОЖЕНИЮ, это неправда: операции сущностей bizproc-templates, bizproc-robots, bizproc-activities отвечают личному ключу 403 OAUTH_REQUIRED, а GET /v1/triggers — 403 BITRIX_ACCESS_DENIED. Коды отказа в спеке уже названы, но семейство ключа оставалось машинно неотличимым, и сгенерированный по спеке клиент узнавал его только по отказу.

Стало

Такие операции объявляют собственное требование авторизации — пару схем oauthAppKey (ключ приложения vibe_app_… в заголовке X-Api-Key) и oauthSession (сессионный токен vibe_session_… из POST /v1/oauth/token в заголовке Authorization: Bearer); обе схемы стоят в одном объекте требования, то есть нужны оба заголовка сразу. Операции, которым личный ключ подходит, остались на прежней схеме — в том числе POST /v1/triggers/fire и GET /v1/workflows из того же скоупа bizproc. Заодно поправлено описание 403 у POST /v1/triggers/fire: оно утверждало, что запуск триггера тоже требует контекста приложения, хотя личный ключ там работает.

Влияние на интеграторов

Поведение API не изменилось: те же вызовы отвечают теми же кодами, что и раньше, ответ 200 остаётся 200. Изменился только машинный контракт, поэтому клиент, сгенерированный заново, попросит для этих операций ключ приложения с сессионным токеном ещё на этапе сборки, а не в рантайме. Список таких сущностей отдаёт GET /v1/me в поле api.entityApi.oauthOnlyEntities.

FIX-0914-16: нативные имена пользовательских полей сохраняются в узкой выборке

Было

При запросе CRM-сущности с select=id,UF_CRM_... значение пользовательского поля могло отсутствовать в ответе, хотя было сохранено в Битрикс24. Правило в GET /v1/me не объясняло, что динамические имена не проверяются как неизвестные поля схемы.

Стало

Нативное имя UF_CRM_... переводится в написание, которое принимает выбранный метод Битрикс24, и проецирует соответствующий ключ пользовательского поля. Это включает имена с цифровым хвостом, которые заводит интерфейс Битрикс24: в camelCase у них сохраняется подчёркивание перед цифрами (UF_CRM_1698325419 → ufCrm_1698325419). GET /v1/me перечисляет классы динамических и method-specific имён, которые нельзя проверить по статической схеме; отсутствие такого ключа в узкой выборке само по себе не означает пустое сохранённое значение или корректное для метода написание.

Влияние на интеграторов

Менять запросы не нужно. Узкая выборка с нативным именем UF_CRM_... теперь возвращает сохранённое значение поля; ключ в ответе по-прежнему имеет написание, принятое выбранным методом Битрикс24.

FIX-0914-17: подключение приложений через Connect к коробочным порталам

Было

Подключение приложения через Connect к коробочному порталу без привязки к Битрикс24.Network останавливалось с PORTAL_NOT_LINKED.

Стало

Коробочные порталы выпускают ключ через коннектор портала или ключ разработчика пользователя, без Битрикс24.Network. Если ключ разработчика не настроен, экран согласия объясняет, что нужно настроить. Права ключа соответствуют согласию пользователя. Для облачных порталов сохраняется выпуск через Битрикс24.Network; для прав только vibe:* вебхук не требуется.

Влияние на интеграторов

Настройки Connect-приложений и OAuth-процесс менять не требуется. Пользователю коробочного портала может понадобиться настроить свой ключ разработчика в кабинете Вайбкод.

Затронутые эндпоинты: GET /v1/connect/authorize, POST /v1/connect/device/authorize, POST /v1/connect/token.

FIX-0914-18: карточка выделенного сервера возвращает установленный рантайм после успешного деплоя

Было

После успешного POST /v1/infra/servers/:id/deploy с полем runtime на выделенном сервере запрос GET /v1/infra/servers/:id отдавал data.runtimeId: null, хотя шаг runtime проходил и приложение отвечало. Документация это поле уже обещала заполненным, поэтому повторный деплой «ради установки рантайма» выглядел необходимым, хотя рантайм уже стоял. У galaxy-приложения то же поле после успеха заполнялось.

Стало

После успешного деплоя на выделенный сервер GET отражает runtime из этого запроса, например python311. Поле runtime на выделенном сервере остаётся необязательным, и запрос без него прежнее значение не затирает. Провалившийся деплой поле по-прежнему не заполняет, даже если шаг runtime успел пройти. Ответ деплоя остаётся HTTP 200, набор шагов не меняется, runtimeStatus остаётся устаревшим и пустым. Уже накопившиеся пустые значения этим исправлением не заполняются — заполнит первый успешный деплой с runtime.

BC-0914-19: поле model в чате больше не подбирает модель по подстроке

Поддержка старого формата до: не предусмотрена

Было

POST /v1/chat/completions принимал в model часть идентификатора и выбирал первую по порядку каталога модель, в идентификаторе которой встречалась эта подстрока. Запрос мог выполниться и тарифицироваться на другой модели, чем была названа, а о подмене говорило только поле model ответа.

Стало

model сопоставляется с каталогом только целиком. Подходит полный идентификатор или тот же идентификатор без префикса провайдера: bitrixgpt-5.5 находит bitrix/bitrixgpt-5.5. Модель, найденная по короткому имени, отмечается в ответе заголовком X-Model-Resolved с полным идентификатором. Идентификатор, который лишь входит в более длинный, и короткое имя, подходящее нескольким моделям, получают 404 ai_model_not_found.

Что делать интеграторам

Передавайте в model полный идентификатор из GET /v1/models. Если в ответах приходит заголовок X-Model-Resolved, замените короткое имя на его значение.

FIX-0914-20: пользовательские поля смарт-процесса отвечают кодом отказа портала, а не «не найден»

Было

Пять операций с пользовательскими полями смарт-процесса — GET, POST /v1/items/{entityTypeId}/userfields и GET, PATCH, DELETE /v1/items/{entityTypeId}/userfields/{id} — перед действием запрашивают у портала внутренний номер смарт-процесса по его entityTypeId. Если Битрикс24 отвечал на этот запрос отказом — превышен лимит обращений, портал недоступен или не ответил вовремя, — клиент получал 404 SMART_PROCESS_NOT_FOUND, как будто смарт-процесса нет. Ответ «не найден» повторять не принято, а клиент мог решить, что смарт-процесс удалён, и создать его заново.

Стало

Отказ портала на этом шаге отвечает так же, как на любом другом обращении к порталу: 429 RATE_LIMITED (в том числе для лимита, который портал присылает со статусом 503) и 503 BITRIX_TIMEOUT — с заголовком Retry-After, 502 BITRIX_UNAVAILABLE — без него. Запрос к полям при таком отказе на портал не отправляется. Успешный ответ по-прежнему 200 (у POST — 201, у DELETE — 204 без тела). 404 SMART_PROCESS_NOT_FOUND означает, что портал такого entityTypeId не знает или не вернул его внутренний номер, — а не то, что портал был недоступен. Псевдоним /v1/userfields/invoices (Смарт-счета) внутренний номер не запрашивает и не менялся. Кроме того, у всех операций, запрашивающих внутренний номер смарт-процесса (включая PATCH, DELETE /v1/smart-processes/{id} и пакетную правку или удаление через POST /v1/smart-processes/batch), ответ портала со статусом 5xx больше не принимается за «не найден», даже если его текст содержит такие слова — например, страница «Страница не найдена», которую отдаёт прокси недоступной коробки: такой ответ приходит как 502 BITRIX_UNAVAILABLE. Подробности по кодам — на странице Ошибки.

BC-0914-21: ответ по предложению без служебных полей Битрикс24, поле contacts помечено как не возвращаемое

Поддержка старого формата до: не предусмотрена

Было

GET /v1/quotes/{id} (а также список, поиск и include) отдавал 17 полей, которых нет ни в описании, ни в справочнике: служебную строку полнотекстового индекса Битрикс24 searchContent (в ней — название транслитом и имя ответственного), дубли дат «для отображения» (dateCreateShort, dateModifyShort, begindateShort, closedateShort), внутренние коды contentType, termsType, commentsType, entityTypeId, пустой hasProducts и семь колонок реквизитов клиента clientTitle, clientAddr, clientContact, clientEmail, clientPhone, clientTpId, clientTpaId — через API они не заполняются даже у предложения с привязанными компанией и контактом, а присланное значение отбрасывается. Объявленное поле contacts не приходило никогда, при этом select=contacts принимался и отвечал 200 без этого ключа. У GET /v1/invoices/{id} в ответ попадал служебный entityTypeId, а lastActivityTime приходил со смещением +03:00, тогда как остальные даты записи — в UTC.

Стало

Семнадцать служебных полей предложения из ответа исключены; ответ на чтение остаётся 200. Поле contacts в описании и справочнике помечено как не возвращаемое (notReturned): item-API Битрикс24 не отдаёт его у предложений, привязанные контакты — в contactIds; попытка записать contacts по-прежнему отвечает 400 READONLY_FIELD, а select=contacts теперь отвечает 400 SELECT_FIELD_NOT_RETURNED вместо 200 без ключа. У счёта entityTypeId из ответа исключён, lastActivityTime объявлен полем только для чтения и приходит в UTC, как остальные даты записи; в filter и order он, как и прежде, не принимается, а присланное на запись значение, которое Битрикс24 и раньше не сохранял, теперь отклоняется: создание и изменение — 400 READONLY_FIELD, импорт — 400 IMPORT_ITEM_VALIDATION, пакетный запрос сущности — 400 BATCH_ITEM_VALIDATION, общий пакетный запрос — READONLY_FIELD под вызовом в data.errors.

Что делать интеграторам

Если вы читали служебные поля предложения (searchContent, *Short, contentType, termsType, commentsType, entityTypeId, hasProducts, client*) — их больше нет; полнотекстовый поиск ведите фильтром title, реквизиты клиента берите из привязанных контакта и компании по contactId и companyId. Если в select предложения был contacts — уберите его (в том числе в подвызовах пакетных запросов): поле никогда не возвращалось, привязки — в contactIds. Если вы отправляли lastActivityTime счёта — уберите его из тела: его проставляет Битрикс24. Отправка client* при создании или изменении, как и раньше, игнорируется порталом — платформа предупреждает об этом подсказкой UNRECOGNIZED_WRITE_FIELD.

Затронутые эндпоинты: GET /v1/quotes/{id}, GET /v1/quotes, POST /v1/quotes/search, GET /v1/quotes/fields, GET /v1/invoices/{id}, GET /v1/invoices, POST /v1/invoices/search, GET /v1/invoices/fields, POST /v1/invoices, PATCH /v1/invoices/{id}, POST /v1/{entity}/batch, POST /v1/batch.

NEW-0914-22: рассуждение прошлых ответов в истории чат-комплишена

POST /v1/chat/completions принимает у сообщений ассистента поле reasoning_content — рассуждение модели из прошлого ответа, возвращённое в историю диалога. Часть моделей с рассуждением в диалоге с функциями читает рассуждения прошлых ходов ассистента и без них теряет нить между вызовами. Раньше поле молча отбрасывалось.

Поле доходит до модели, у которой owned_by в GET /v1/models равно bitrix, openrouter или vibecode. Моделям OpenAI, Anthropic и других OpenAI-совместимых провайдеров оно не передаётся: их API такое поле не принимают. У сообщений других ролей поле игнорируется, null равнозначен отсутствию поля, пустая строка допустима. Модель, которая читает рассуждение, учитывает его во входных токенах. Значение не строкой или длиннее 4 000 000 символов отклоняется с 400 invalid_request.

FIX-0914-23: описания частоты называют суммарный лимит и указывают на заголовок с действующим значением

Было

Страницы справки и машинная схема GET /v1/openapi.json называли предел частоты одним числом — 60 запросов в минуту у POST /v1/search, 20 у POST /v1/research, 30 у POST /v1/batch, 600 и 120 у чтения уведомлений, 120 у настроек календаря, 30 у записей рабочего дня, 20 у ресурсов бронирования, 10 и 30 у ключей AI-провайдеров, 10 у SSH-доступа, значка приложения, порта, расписаний пробуждения и операций Deploy API, 6 у снятия зависшей блокировки, 60 у опроса статуса операции, 20 у комментариев к обращению и 60 у публичной ссылки хранилища. Запросы обслуживает несколько процессов платформы, каждый держит свою долю предела, поэтому ни одно из этих чисел клиенту не приходило: заголовок x-ratelimit-limit отдавал меньше, и клиент, закрепивший число из описания, получал 429 раньше, чем ожидал. Тем же одним числом описывались Partner Connect и коннектор 1С: 30 запросов в минуту у GET /v1/connect/authorize, 60 у POST /v1/connect/token, 60 у POST /v1/connect/revoke и 120 у чтения операций коннектора 1С. Так же одним числом был описан внешний API приложения (ANY /v1/applications/:id/api/*) — 120 запросов в минуту на ключ, и в таблице ограничений на странице справки, и в подсказках для агентов /v1/guide и /v1/me.

Стало

Каждое такое описание называет число суммарным и рядом указывает источник действующего значения — заголовок ответа x-ratelimit-limit, — а также говорит, что предел делится между процессами платформы. То же исправление внесено в описания отказа 429 машинной схемы. Отдельно помечен предел, который НЕ делится и приходит клиенту ровно как назван: выпуск токенов доступа TOKEN_MINT_RATE_LIMIT (50 в час на API-ключ). Предел активации пробного периода Маркетплейса пометки не несёт и по устройству в ней не нуждается: его потолок домножен на число процессов платформы, поэтому клиент получает ровно напечатанные 3 в час при любом их числе. В схеме он огласован двумя формами — «3 per hour per portal» и «3 per hour per Bitrix24 account». Указание на заголовок получили и описания выше — Partner Connect (страница справки и схема, включая отзыв ключа) и коннектор 1С. Оговорку получил и внешний API приложения — во всех трёх местах сразу. Рядом помечен ещё один предел, который между процессами НЕ делится: зона ограничения на краю (nginx) у POST /v1/connect/token — 20 запросов в минуту на адрес вызывающего. Она считает до распределения запроса по процессам, отвечает конвертом RFC 6749 и заголовка x-ratelimit-* не ставит, поэтому его отсутствие и отличает её от предела самого маршрута.

Влияние на интеграторов

Поведение эндпоинтов не менялось, ответы прежние — исправлено описание, которое расходилось с ними. Клиент, читавший x-ratelimit-limit, не меняет ничего. Клиент, закрепивший число из описания в коде, должен перейти на заголовок: суммарное число делится между процессами и меняется без записи в журнале.

FIX-0914-24: ступень рассуждения по умолчанию передаётся модели явно

Было

Если в запросе к POST /v1/chat/completions не было ни reasoning_effort, ни reasoning, ни chat_template_kwargs, в модель с декларацией рассуждения ничего не добавлялось, и режим определял провайдер модели. При обновлении модели на его стороне этот режим мог смениться. Поле reasoning ответа при этом сообщало ступень default из декларации.

Стало

Платформа передаёт модели ступень default из поля reasoning ответа GET /v1/models явно, поэтому поведение запроса без параметра не зависит от обновлений на стороне провайдера. Явно запрошенная ступень по-прежнему важнее default. Поле reasoning ответа и заголовки X-Reasoning-Applied и X-Reasoning-Native описывают переданную ступень. У моделей с reasoning: null ничего не меняется.

Влияние на интеграторов

Менять ничего не нужно. Чтобы включить рассуждение в запросе, передайте reasoning_effort или объект reasoning. Ступень, которая применяется без параметра, показывает reasoning.default модели.

BC-0914-25: внешний API приложений отвечает на таймаут статусом 503, а не 504

Поддержка старого формата до: не предусмотрена

Было

Приложение не ответило за общий предел вызова — ANY /v1/applications/{id}/api/** отдавал 504 с кодом APP_API_TIMEOUT и без заголовка Retry-After.

Стало

Тот же исход приходит статусом 503 с кодом APP_API_TIMEOUT и заголовком Retry-After в секундах. Код отказа не изменился, поэтому таймаут по-прежнему отличим от APP_API_UNAVAILABLE, который означает недоступный или насыщенный канал. Статуса 504 этот адрес больше не отдаёт вовсе: увидели его — он пришёл от промежуточного узла на пути к платформе, а не от платформы Вайбкод.

Что делать интеграторам

Переведите ветку обработки таймаута со статуса на код отказа: читайте error.code, а не 504. Клиент, который уже ветвится по error.code, менять не нужно. Клиент, который считал 503 безусловно повторяемым, получит по таймауту Retry-After — но повтор неидемпотентного вызова по-прежнему опасен: приложение могло довести работу до конца после того, как канал перестал ждать.

2026-09-13

FIX-0913-1: описание API называет отказы, общие для всех операций

Было

Машинное описание (/v1/openapi.json) не называло отказы, которые отдают не сами ручки, а общие слои платформы, — и клиент, собранный по описанию генератором, встречал их как неизвестный ответ.

Отказ по балансу 402 ACCOUNT_FROZEN (счёт заморожен за долг) возможен на любой операции с ключом, кроме нескольких информационных и служебных, — а описан был у 22 операций из 842. Ответы 429 RATE_LIMITED (Битрикс24 ограничил частоту), 429 ERROR_LOOP_DETECTED, 429 QUEUE_OVERFLOW, 429 QUEUE_TIMEOUT, 429 OPERATION_TIME_LIMIT, 429 TIMEOUT_QUARANTINE, 502 BITRIX_UNAVAILABLE (портал недоступен) и 503 BITRIX_TIMEOUT (портал не ответил в срок) возможны на каждой операции, обращающейся к порталу, — 502 BITRIX_UNAVAILABLE был описан у 11 операций, 503 BITRIX_TIMEOUT у 4, остальные почти нигде. Операции шаблонов, роботов и действий бизнес-процессов (/v1/bizproc-templates, /v1/bizproc-robots, /v1/bizproc-activities) отвечают личному ключу 403 OAUTH_REQUIRED — описание молчало; GET /v1/triggers описывал только успех, тогда как личному ключу портал отвечает 403 BITRIX_ACCESS_DENIED. Ответ 400 INVALID_PARAMS на нечисловой номер записи в пути не был описан у чтения записи ни одной сущности с числовым номером (GET /v1/tasks/{id}, GET /v1/workgroups/{id} и остальных) и у большинства подчинённых ручек задач, скрама, таймлайна и журнала таймлайна.

Стало

Поведение ручек не менялось — исправлено описание. Успешные ответы по-прежнему те же 200, 201 и 204, что и были: меняется описание отказов, не ответ.

402 ACCOUNT_FROZEN описан ровно у тех операций, которые общий гейт заморозки может отбить: у всех операций с ключом, кроме GET /v1/me, GET /v1/me/sources, GET /v1/guide, четырёх вызовов переписки по обращению (GET /v1/feedback, POST /v1/feedback, GET /v1/feedback/{id}, POST /v1/feedback/{id}/comments) и DELETE /v1/cowork/key. В описании названо, что отказ приходит до любых проверок самой операции и всегда в общем конверте ответа (на OpenAI-совместимых операциях ИИ тоже), и что по мере раскатки сужения отказ останется только у операций, оплачиваемых кошельком, — успешный ответ на замороженном счёте не означает, что счёт в порядке (состояние — в infraState ответа GET /v1/me).

429, 502 и 503 с перечисленными выше кодами описаны у операций, обращающихся к порталу: у сгенерированных операций сущностей (список, чтение, создание, правка, удаление, поиск, пакет, импорт, агрегация, связи, товарные позиции, действия — кроме GET /v1/{entity}/fields, которая при отказе портала отвечает 200 со статическим набором полей и предупреждением fields_partial), у пакетного вызова POST /v1/batch (там свой набор: лимит Битрикс24 на самом конверте приходит не как 429, а как 502 BITRIX_UNAVAILABLE в штатной форме или 422 BITRIX_ERROR, с кодом портала в error.bitrixError.error; 429 там — лимит пакетов и очередь) и у рукописных семей: подчинённые ручки задач, скрам, таймлайн, журнал таймлайна, рабочий день, звонки, бизнес-процессы (/v1/workflows), пользовательские поля CRM (/v1/userfields), история стадий, загрузка файлов, поиск дублей, GET /v1/addresses/fields, POST /v1/doc-templates. Там, где у операции уже был свой 429 (лимит чтений или пакетов, свой лимит ручки), свой 502 или 503, прежний текст сохранён и дополнен. У каждой такой операции описан и 422 BITRIX_ERROR — общий ответ на ошибку портала, не подпадающую под более узкий код.

У всех операций трёх сущностей бизнес-процессов описан 403 OAUTH_REQUIRED; у записывающих — рядом с WRITE_BLOCKED_READONLY_KEY. GET /v1/triggers и POST /v1/triggers/fire описывают 401, 403 (SCOPE_DENIED, BITRIX_ACCESS_DENIED, у запуска — и WRITE_BLOCKED_READONLY_KEY), 422 и, у запуска, 400 (MISSING_PARAMS, INVALID_ENTITY_TYPE, INVALID_ENTITY_TYPE_ID).

400 INVALID_PARAMS на нечисловой номер записи описан у чтения, правки и удаления записи каждой сущности с числовым номером и у подчинённых ручек задач, скрама, таймлайна, журнала таймлайна, расшифровки звонка (GET /v1/activities/{activityId}/transcript), участников рабочей группы и конвертации лида. Свои коды того же отказа названы там, где ручка отвечает ими: INVALID_DYNAMIC_PARAM (все операции смарт-процессов по {entityTypeId}), INVALID_ENTITY_TYPE_ID (запись смарт-процесса, пользовательские поля смарт-процессов, конфигурация карточки CRM), INVALID_ROW_ID (товарная позиция), INVALID_ANCHOR (связи реквизитов), VALIDATION_ERROR (настраиваемые дела), INVALID_ID (фото сотрудника), INVALID_BOT_ID (передача бота), UNKNOWN_ENTITY (пользовательские поля CRM по {entity}).

Что описание ПО-ПРЕЖНЕМУ не называет — сознательно: отказ по суточной квоте вызовов (429 QUOTA_EXCEEDED) — общий для всего API и не входит в это изменение; у пакетных чтений, которые сообщают отказ портала внутри успешного ответа по каждому под-вызову (POST /v1/mail/mailboxes/batch, POST /v1/tasks/{taskId}/comments/batch), отказы портала на уровне ответа не описаны — их там нет.

Общие ответы вынесены в переиспользуемые компоненты описания AccountFrozen, PortalRateLimited, PortalUnavailable, PortalTimeout и OauthAppKeyRequired. Интерактивный справочник (/docs → API reference) перегенерирован из исправленного описания в обоих сегментах. Подробности по кодам — на странице Ошибки.

NEW-0913-2: приложение Partner Connect запрашивает право vibe:ai само

vibe:ai теперь перечисляется в правах Connect-приложения при регистрации и правке — как любое право Битрикс24, без заявки и проверки. Дальше право идёт обычным путём: приложение передаёт его в параметре scope у /v1/connect/authorize, пользователь видит строку «AI-модели» на экране согласия и подтверждает набор целиком, и выданный ключ несёт это право. Вызовы AI-роутера таким ключом работают, расход покрывает месячный AI-лимит аккаунта.

Автоматически право не добавляется: приложение, которое его не запросило, работает как раньше, и у ранее выданных ключей набор прав не меняется. Остальные платформенные права — vibe:search, vibe:infra, vibe:storage, vibe:feedback — выдаёт платформенный администратор, и запрос с ними при саморегистрации отклоняется кодом SCOPE_NOT_ALLOWED. Документ /.well-known/oauth-authorization-server перечисляет vibe:ai в поле scopes_supported.

FIX-0913-3: товарные позиции несуществующей записи отвечают «не найдено», как соседние методы

Было

GET /v1/deals/{id}/products по несуществующему id отвечал 403 BITRIX_ACCESS_DENIED — так Битрикс24 отвечает на запрос товарных позиций у владельца, которого нет, и Вайбкод передавал этот ответ как есть. Соседние методы на тот же id — сама запись, её контакты, запись товарных позиций — отвечали 404 ENTITY_NOT_FOUND. Клиент, получив «доступ запрещён», шёл проверять права вместо того, чтобы проверить id. То же у лидов, предложений, счетов и элементов смарт-процессов.

Стало

Получив от Битрикс24 отказ на чтение товарных позиций, Вайбкод один раз проверяет саму запись — тем же вызовом, что и GET /v1/{entity}/{id}. Записи нет — ответ 404 ENTITY_NOT_FOUND, как у соседних методов. Запись есть, но закрыта правами сотрудника, — по-прежнему 403 BITRIX_ACCESS_DENIED. Успешные запросы дополнительного вызова не делают. В OpenAPI у метода объявлен ответ 404; страница счетов больше не описывает 403 как ответ на несуществующую запись.

Затронутые эндпоинты: GET /v1/deals/{id}/products, GET /v1/leads/{id}/products, GET /v1/quotes/{id}/products, GET /v1/invoices/{id}/products, GET /v1/items/{entityTypeId}/{id}/products.

FIX-0913-4: заморозка счёта проверяется у ключа приложения и без сессии пользователя

Было

Ключ приложения (vibe_app_*), присланный без заголовка Authorization: Bearer с сессией пользователя, на заморозку счёта не проверялся: вызов шёл дальше, к собственным проверкам операции. На замороженном счёте такой ключ проходил в двери, которые оплачиваются балансом Вайбкод, — создание приложения с парным ключом, загрузку и скачивание вложений обращений, правку обращения, поиск и глубокий поиск, список серверов — тогда как личный ключ и тот же ключ приложения с сессией получали 402 ACCOUNT_FROZEN.

Стало

Заморозка — состояние счёта, а не сессии: на замороженном счёте ключ приложения без сессии отвечает 402 ACCOUNT_FROZEN ровно там же, где личный ключ, — состав закрытых и открытых вызовов описан в разделе «Что закрывает заморозка счёта». Открытые под заморозкой вызовы — самоописание, справочник, спека, переписка по обращениям — открыты и для такого ключа. На счёте без заморозки ответы не меняются: успешный ответ сохраняется.

Влияние на интеграторов

Клиент ничего не меняет. Если интеграция ходила ключом приложения без сессии и на замороженном счёте вдруг получила 402 ACCOUNT_FROZEN — это не поломка ключа, а долг: пополните баланс, состояние счёта видно в блоке infraState ответа GET /v1/me.

FIX-0913-5: пакетный вызов отвечает 429 ERROR_LOOP_DETECTED, а не 500, когда платформа приостановила его после серии отказов

Было

Когда пакетные вызовы одного ключа раз за разом отклонялись порталом и платформа Вайбкод временно приостанавливала их (см. ERROR_LOOP_DETECTED), сам POST /v1/batch отвечал 500 INTERNAL_ERROR без заголовка Retry-After и без подсказки, — тогда как любая другая операция в том же состоянии отвечает 429 ERROR_LOOP_DETECTED. Клиент читал внутреннюю ошибку платформы там, где действовал её же защитный механизм, и не знал, сколько ждать.

Стало

POST /v1/batch отвечает 429 ERROR_LOOP_DETECTED с заголовком Retry-After, а в error.message и error.hint — число отказов за последний час и с какой частотой пробные запросы пропускаются для проверки, что серия закончилась. Отдельные вызовы внутри пакета и раньше получали этот код в data.errors.<id> — изменился только ответ на весь пакет.

Влияние на интеграторов

Ничего менять не нужно. Обработчик, который на 429 ERROR_LOOP_DETECTED выдерживает паузу из Retry-After, теперь срабатывает и на пакетном вызове. Если на 500 от пакетного вызова была настроена тревога — она перестанет срабатывать на этом состоянии.

2026-09-12

BC-0912-1: Назначения сотрудников и отделов для интеграции 1С

Поддержка старого формата до: не предусмотрена

Администратор может назначить аккаунту 1С несколько сотрудников и отделов через GET /api/onec/access-assignments и PUT /api/onec/access-assignments/{onecUserId}. Отдел включает все подотделы; персональное назначение имеет приоритет. Существующие персональные связи сохраняются.

Было

POST /v1/onec/tools/{method}/call использовал персональную связь 1:1. Изменения выполнялись через /api/onec/user-mappings.

Стало

После включения отделовых назначений вызов учитывает актуальную принадлежность сотрудника. Разные подходящие аккаунты дают 409 ONEC_MAPPING_CONFLICT, недоступное назначение — 403 ONEC_ACCOUNT_UNAVAILABLE, неподтверждённый справочник — 503 ONEC_DIRECTORY_UNAVAILABLE. Изменение версии во время проверки даёт 409 ONEC_MAPPING_VERSION_CONFLICT. Операция не переназначается другому аккаунту; результат остаётся доступным только исходному пользователю и ключу.

Старые записи через /api/onec/user-mappings после перехода дают 409 ONEC_MAPPING_API_UPGRADE_REQUIRED. Выключение функции после первого сохранения не возвращает прежнюю модель: создание операции, приём результата и новые записи отвечают 503 ONEC_ASSIGNMENTS_DISABLED до повторного включения; P&P отвечает 200 без выдачи команды, HTTP откладывает доставку. Для порталов без перехода сохраняется прежнее поведение.

Что делать интеграторам

Использовать новый административный API после включения функции. При 503 на /api/onec-module/v1/commands/{id}/result повторять тот же body, commandId и Idempotency-Key с учётом Retry-After: 5; срок операции не продлевается. До включения проверить права user.get/department.get и повтор результата на тестовом портале.

NEW-0912-2: Параметр device_id в ссылке подтверждения device-flow

POST /v1/connect/device/authorize дописывает device_id в ссылку verification_uri_complete, когда присланное в теле запроса значение укладывается в форму идентификатора: до 128 символов, только латинские буквы, цифры и знаки . : - _. Значение попадает в ссылку как есть, но по правилам URL, поэтому : в адресе выглядит как %3A — приёмник декодирует его обратно штатным разбором query-строки.

Значение вне этой формы отбрасывается молча: ручка по-прежнему отвечает 200, ссылка приходит без параметра, признака отказа в ответе нет. Так сделано намеренно — поле необязательное, и отказ ломал бы вход тем, у кого идентификатор съехал.

Нужно для сведения статистики входа: ссылку подтверждения человек нередко открывает не в том браузере, где начал вход, — копирует её или отдаёт системному браузеру, — и без параметра внутри самой ссылки связать шаги одного входа нечем.

Изменение аддитивное: запрос без device_id получает ссылку в прежнем виде, остальные поля ответа не меняются, действий на стороне клиента не требуется.

BC-0912-3: бессмысленное тело у правки сообщения, участников чата и поста отклоняется, а не выполняется

Поддержка старого формата до: не предусмотрена

Было

Три ручки мессенджера и ленты принимали тело без нужных данных и отвечали успехом, хотя делали не то, что просил клиент. PATCH /v1/chats/{dialogId}/messages/{messageId} без message — пустым, из одних пробелов, из одних тегов переноса [BR] или с текстом под другим именем поля ({"text": …}) — отправлял в Битрикс24 пустой текст и отвечал 200 «Message updated», а Битрикс24 по тексту, пустому по его собственному правилу, в этом методе удаляет сообщение (проверено 2026-09-12: после такого вызова сообщение показывается как «Это сообщение было удалено»). Тело запроса при этом уходило на портал целиком, а портал сворачивает имена параметров без учёта регистра и берёт последний ключ: {"MESSAGE": "текст", "Message": ""} тоже удалял сообщение (проверено 2026-09-12). DELETE /v1/chats/{chatId}/users и POST /v1/chats/{chatId}/users передавали userId и элементы users в Битрикс24 как есть, а портал приводит их к числу по правилам PHP: массив [47] становится 1, слово — 0. Вызов {"userId": [47]} отвечал 200 и удалял из чата пользователя с ID 1 — владельца портала, а не 47; при добавлении {"users": ["abc"]} отвечал 200 «добавлено», никого не добавив, а вложенный массив ронял портал (проверено 2026-09-12). PATCH /v1/posts/{id} без единого изменяемого поля ({} или текст под именем message) отвечал 200 «Post updated», сделав два вызова в Битрикс24 и не изменив ничего.

Стало

Правка сообщения требует message с текстом по правилу самого Битрикс24: пустой, из одних пробелов, из одних тегов [BR]/[br], не строка и не число, под другим именем поля — 400 MESSAGE_REQUIRED до обращения к порталу, в тексте ошибки названо нераспознанное поле; конечное число принимается и записывается как текст. Удаление сообщения — только через DELETE /v1/chats/{dialogId}/messages/{messageId}. userId при удалении участника и каждый элемент users при добавлении обязаны быть положительным целым — числом или строкой из десятичных цифр без ведущих нулей и пробелов, не больше 2^53−1; иначе 400 INVALID_PARAMS (у users названы индекс и тип элемента), отсутствующий userId или пустой users — 400 MISSING_PARAMS. Значение уходит в Битрикс24 нормализованным числом. Строковые формы, которые портал разбирал сам и которые через прежний проброс проходили (department<id>, structure<id>, network<id>), теперь отклоняются — принимаются только ID пользователей. У правки сообщения и удаления участника на портал уходят только проверенные параметры (MESSAGE; CHAT_ID и USER_ID): прочие ключи тела, в том числе нативные ключи Битрикс24 в верхнем регистре (URL_PREVIEW, IS_EDITED, ATTACH, KEYBOARD), которые прежде доезжали вместе с телом, больше не передаются. Правка поста без единого из полей text, title, recipients, files — 400 MISSING_PARAMS до обращения к порталу, нераспознанное поле названо. Вызов без тела запроса у правки сообщения, отвечавший 500, теперь получает тот же 400 MESSAGE_REQUIRED.

Что делать интеграторам

Проверьте, что правка сообщения всегда несёт текст в поле message, а участники чата передаются числами (или строками из цифр без ведущих нулей) — userId скаляром, users плоским массивом. Правка поста должна нести хотя бы одно изменяемое поле. Если в теле правки сообщения или удаления участника передавались нативные ключи Битрикс24 сверх документированных полей — они больше не действуют. Вызовы, которые прежде отвечали успехом с этими телами, на самом деле удаляли сообщение, трогали не того участника или ничего не меняли — теперь они получают отказ с объяснением.

Затронутые эндпоинты: PATCH /v1/chats/{dialogId}/messages/{messageId}, POST /v1/chats/{chatId}/users, DELETE /v1/chats/{chatId}/users, PATCH /v1/posts/{id}.

BC-0912-4: числовой идентификатор в пути ручек чатов и уведомлений проверяется до обращения в Битрикс24

Поддержка старого формата до: не предусмотрена

Было

Идентификатор из пути читался нестрого: значение с лишним хвостом или дробной частью (125abc, 125.9) уходило в Битрикс24 как 125, и операция выполнялась над записью 125 — ответ 200 или 204, как для правильного вызова. Полностью нечисловое значение (abc) уходило пустым и отклонялось уже Битрикс24 (403 или 422).

Стало

Идентификатор обязан быть положительным целым числом в канонической записи: без знака, ведущего нуля, дроби, экспоненты и пробелов. Иначе ручка отвечает 400 до обращения в Битрикс24: для chatId — INVALID_CHAT_ID (как уже отвечало PATCH /v1/chats/:chatId), для messageId, fileId и id уведомления — INVALID_PARAMS. Текст ошибки называет параметр. Корректные идентификаторы обрабатываются как прежде.

Затронутые эндпоинты: DELETE /v1/notifications/:id, GET /v1/chats/files/:fileId, PATCH /v1/chats/:dialogId/messages/:messageId, DELETE /v1/chats/:dialogId/messages/:messageId, POST /v1/chats/:chatId/leave, POST /v1/chats/:chatId/owner, POST /v1/chats/:chatId/users, DELETE /v1/chats/:chatId/users, POST /v1/chats/:chatId/files, GET /v1/chats/:chatId/folder.

Что делать интеграторам

Передавать идентификатор так, как его вернул API: только цифры. Если идентификатор собирается из строки, убрать хвосты и разделители до подстановки в путь. Клиенты, уже передающие идентификаторы числом, ничего не меняют.

FIX-0912-5: предсуществующая конфигурация NodeSource переживает обрыв установки рантайма

Было

При деплое рантайма node20* на переиспользованную или вручную настроенную виртуальную машину Вайбкод на время установки убирал с рабочего пути ваши файлы /etc/apt/sources.list.d/nodesource.list и nodesource.sources и возвращал их в конце. Если шаг установки обрывался — по таймауту или при разрыве соединения, — файлы на сервере так и оставались удалёнными: дальнейшие установки и обновления Node.js через apt переставали использовать ваш репозиторий до ручного восстановления.

Стало

Обрыв шага установки больше не теряет вашу конфигурацию: она остаётся на сервере, и следующий деплой рантайма возвращает файлы на рабочий путь сам. Если установка NodeSource прошла успешно, на рабочем пути остаётся именно ваш файл, а созданный установщиком удаляется. Восстанавливаются именно эти два файла: /etc/apt/preferences.d/nodejs, nsolid и ключ /usr/share/keyrings/nodesource.gpg установщик перезаписывает, и платформа их не возвращает. Если ваш nodesource.sources создан обычной командой установки NodeSource, он совпадает с файлом установщика содержимое-в-содержимое, платформа их не различает и при обрыве установки может удалить такой файл — восстановить его можно той же командой.

Отдельный случай: если рядом с ним лежит ваш nodesource.list, который активной строкой называет тот же репозиторий NodeSource и тот же выпуск (nodistro), платформа при обычном деплое, без всякого обрыва снимает с рабочего пути свой файл. Он не удаляется, а остаётся рядом под именем с суффиксом .vibe-node20-conflict.disabled; apt его не читает, удалить можно вручную. Если там уже лежит точно такой же файл от прошлой такой починки, повторный просто удаляется с рабочего пути. Ради чего: если ваша строка называет тот же репозиторий с другим ключом, apt отказывается читать список источников целиком — на сервере перестают ставиться любые пакеты. Если ключ тот же, конфликта нет, но свой файл платформа всё равно снимает: на рабочем пути остаётся ваш. Если ваш .list указывает на другой репозиторий — например, на ваше зеркало, — или строка закомментирована, платформа ничего не трогает.

Если вы сами замените файл на рабочем пути между установками, ваш выбор останется нетронутым, а сохранённая копия прежней конфигурации будет лежать рядом под именем с суффиксом .vibe-node20-parked.disabled — apt её не читает, удалить её можно вручную. Одно исключение: если вы вернёте конфигурацию не своим файлом, а самой командой установки NodeSource, платформа не отличит её от собственной, снимет и вернёт на путь прежнюю сохранённую копию. На машинах, где своей конфигурации NodeSource не было, поведение не изменилось.

FIX-0912-6: подписка Маркетплейса перестала быть условием и для аккаунтов на собственном сервере

Было

В блоке activation.marketTrial ответа GET /v1/cowork/state причина not_required_for_data приходила только облачным аккаунтам подписочной модели и только после включения соответствующей настройки платформы. Аккаунт на собственном сервере, которому пробный период предлагался (available: true), получал это предложение и при включённой настройке.

Стало

Причина по-прежнему приходит только аккаунтам подписочной модели и только после включения соответствующей настройки платформы, но теперь — обоих типов: аккаунт на собственном сервере, которому пробный период предлагался бы, получает вместо available: true значение available: false с причиной not_required_for_data — так же, как облачный. Без этой настройки ответ прежний у обоих типов. Набор из восьми причин не изменился, порядок их выбора тоже: причина ступени, если она есть, по-прежнему сильнее. Клиенту делать ничего не нужно — ветка по умолчанию на любое false прячет шаг включения сама.

2026-09-11

FIX-0911-1: срез спецификации OpenAPI по скоупу перестал включать чужие рукописные разделы

Было

Параметр ?scope=<скоуп> у GET /v1/openapi.json резал только сгенерированные сущности. Рукописные разделы — боты, отзывы, инфраструктура, ИИ, файлы платформы, поиск, 1С, места встраивания — оставались в КАЖДОМ срезе целиком, независимо от запрошенного скоупа. Срез ?scope=tasks весил 725 100 байт и содержал 284 пути, включая полные разделы /v1/apps, /v1/placements, /v1/bots, /v1/feedback.

Стало

Срез оставляет сущности запрошенного скоупа, рукописные пути того же раздела и служебные /v1/me, /v1/guide, /v1/batch — остальное вырезается. ?scope=tasks теперь отдаёт 34 пути вместо 284. У рукописных разделов свои значения ?scope=: imbot — боты, vibe:feedback — отзывы, vibe:infra — инфраструктура, vibe:ai — ИИ, vibe:storage — файлы платформы, vibe:search — поиск, vibe:onec — 1С, placement — места встраивания. Кто раньше читал чужой раздел из чужого среза — например, ботов из ?scope=crm, — теперь получит его отдельным запросом по собственному скоупу раздела: несколько разделов сразу требуют по одному GET на каждый, объединение через запятую по-прежнему не поддержано. Полная спецификация без ?scope= и ответ на мусорное, пустое или несводимое к одному скоупу значение не изменились. Семейства apps, applications, cowork, oauth, partner-connect, coupons, portals, app-blueprints, agents собственного значения ?scope= не получили и остаются только в полной спецификации.

FIX-0911-2: отказы POST /v1/doc-templates ведут к полю file, а не на Диск

Было

Отказ MISSING_FILE_OR_FILE_ID и отказ UNSUPPORTED_MEDIA_TYPE предлагали второй путь: загрузить файл на Диск и сослаться на него полем fileId. Шаблон по такому пути создавался, но документ по нему не формировался — POST /v1/documents отвечал 422 BITRIX_ERROR с признаком FILE_NOT_PROCESSABLE.

Стало

Оба сообщения называют рабочий путь: содержимое .docx строкой base64 в поле file. Коды отказов, статусы и условия срабатывания прежние, изменён только текст сообщения. Поле fileId по-прежнему принимается — описание в GET /v1/guide теперь прямо говорит, что документ по такому шаблону не формируется.

NEW-0911-3: у причины недоступности пробного периода Маркета появилось восьмое значение

Поле unavailableReason в блоке activation.marketTrial ответа GET /v1/cowork/state принимает новое значение not_required_for_data в дополнение к семи прежним.

Оно означает: платформа больше не считает подписку Маркетплейса условием для чтения данных компании этим ключом, поэтому предлагать включение пробного периода незачем. Сам аккаунт Битрикс24 в редких случаях всё ещё может отказать — обработку отказа оставьте.

Значение приходит только на облачных аккаунтах подписочной модели и только после включения соответствующей настройки платформы. Остальные значения и их смысл не изменились, поле по-прежнему присутствует всегда, а правило «значение false окончательно, неизвестное значение читайте как „активацию не предлагать“» действует без изменений — благодаря ему клиент, собранный до этой правки, ведёт себя корректно и без обновления.

Подписка по-прежнему нужна для вызовов ботов, развёртывания и пробуждения приложений, создания приложения и замены его ключа, привязки встроек и выдачи ключа агента.

FIX-0911-4: Пакетный вызов сохраняет запрошенные поля

Было

POST /v1/batch с действиями list и search мог пропускать поля из select, которые одиночный запрос возвращал: пользовательские поля задач, например ufCrmTask, и объявленные алиасы CRM, например statusId, amount и currency. Отбор задач по UF_CRM_TASK также терял поле в ответе одиночного запроса.

Стало

Одинаковый именованный select сохраняет одинаковые поля при одиночном и пакетном чтении. Пользовательские поля задач сохраняются и при выборе по имени UF_CRM_TASK, включая значение null.

Влияние на интеграторов

Можно использовать именованный select вместо обхода через select: ["*"]. Написание пользовательских полей остальных сущностей не меняется.

FIX-0911-5: расшифровка аудио отличает ошибку во входных данных от сбоя сервиса

Было

POST /v1/audio/transcriptions на файл, содержимое которого заведомо не аудио — текст, документ, картинка или архив, — отвечал 502 ai_provider_unavailable, как на сбой сервиса распознавания. Клиенты повторяли такой запрос, и каждый повтор снова заканчивался ошибкой. Неизвестный ID в поле model уходил на распознавание и возвращался как 400 ai_provider_rejected.

Стало

Содержимое, которое заведомо не аудио, отклоняется с 400 invalid_audio до распознавания, списания нет. Неизвестный ID модели отклоняется с 404 ai_model_not_found до распознавания — тем же кодом, что на POST /v1/embeddings. Ответ HTTP 200 на аудиофайлы и известные модели не меняется.

Влияние на интеграторов

Повторять запрос с 400 invalid_audio без замены файла бессмысленно: такую ошибку стоит показать пользователю. Клиент, который не повторяет ответы 4xx, продолжает работать без изменений. Если обработчик сверяет конкретные коды, добавьте в него invalid_audio и ai_model_not_found.

FIX-0911-6: ответ на запись подсказывает, какие поля не были распознаны

Было

Опечатка в имени поля в теле POST /v1/{entity} или PATCH /v1/{entity}/{id} проходила молча: ответ успешный, а поле — например, сумма с ошибкой в названии — нигде не появлялось.

Стало

Ответ одиночной записи остаётся успешным с тем же статусом, а в meta.warnings приходит по записи UNRECOGNIZED_WRITE_FIELD на каждый ключ тела, которого нет ни в описании сущности, ни в списке полей вашего портала; field называет ключ. Это подсказка, не отказ: запись создаётся или меняется как прежде, meta появляется только когда есть что сказать, не больше десяти подсказок на ответ. Если список полей портала недоступен, подсказки не будет — платформа Вайбкод не предупреждает наугад.

NEW-0911-7: Поле flow_ref в ответе запроса кода устройства

POST /v1/connect/device/authorize отдаёт новое необязательное поле flow_ref — идентификатор ЭТОЙ попытки входа, одинаковый у клиента и у платформы Вайбкод.

Нужен только для сведения статистики. Одно устройство входит много раз — переподключение, смена аккаунта, повтор после отказа, — и без общего имени попытки события одного входа не отличить от событий соседнего. Клиент штампует flow_ref в свои события и получает точное соответствие вместо подбора по времени.

Поле секретом не является, прав доступа не несёт, и восстановить по нему user_code нельзя. Клиент, который его не читает, работает как прежде.

NEW-0911-8: выгрузка остатков: идентификатор клиента, адресация одного портала и дельта

В строку GET /v1/platform/revenue/balances добавились четыре поля. clientType и clientId — идентификатор клиента в словаре биллинга Битрикс24, то же значение, что приходит на платформу Вайбкод в метаданных вебхука об оплате; пара приходит целиком либо целиком пустая. portalNetworkId — идентификатор портала в Bitrix24.Network: в отличие от домена он не меняется при переезде портала. updatedAt — когда счёт менялся последний раз.

У коробочных порталов идентификатор клиента заполнен всегда. У облачных он известен только по их же оплатам, поэтому у портала без платежей пара приходит пустой — сопоставлять такие строки можно по portalNetworkId или по домену.

Появились параметры запроса: changedSince (только счета, изменившиеся не раньше указанного момента, UTC ISO-8601 с Z), portalId, portalDomain, а также clientId вместе с clientType (b24 или box). Ежедневный обход этим сокращается примерно впятеро, а карточка одного портала перестаёт тянуть всю популяцию.

Курсор теперь привязан к набору фильтров: продолжить им обход с другим фильтром нельзя, такой запрос отвечает 400 INVALID_CURSOR — начинайте обход заново. Курсор, выданный до этого изменения, продолжает работать для обхода без фильтров. Прежние поля строки и порядок обхода не изменились.

BC-0911-9: сумма налога у сделок и предложений объявлена только для чтения

Поддержка старого формата до: не предусмотрена

Было

Поле taxValue у сделок и предложений было объявлено доступным для записи и принималось с ответом 200/201, но Битрикс24 его не сохранял: значение оставалось 0 при создании — в том числе в ручном режиме суммы, — при изменении и при импорте. Отличить это от успешной записи по ответу было нельзя.

Стало

Поле объявлено назначаемым сервером: Битрикс24 считает его по товарным позициям. Попытка записи отклоняется до обращения к Битрикс24 на каждой двери: создание и изменение возвращают 400 READONLY_FIELD, импорт — 400 IMPORT_ITEM_VALIDATION, пакетный запрос сущности — 400 BATCH_ITEM_VALIDATION, а в общем пакетном запросе отказ приходит под соответствующим вызовом в data.errors с кодом READONLY_FIELD, пока остальные вызовы конверта исполняются. Описание поля прямо говорит, где задаётся налог: через товарные позиции. Замер 2026-09-11 на живых порталах обеих платформ: создание и изменение в обоих режимах суммы, импорт в обоих режимах.

Что делать интеграторам

Уберите taxValue из тела запросов на создание, изменение и импорт сделок и предложений — в том числе там, где запись читается целиком и отправляется обратно. Значение и раньше не сохранялось; налог задаётся товарными позициями, а поле остаётся в ответах на чтение.

Затронутые эндпоинты: POST /v1/deals, PATCH /v1/deals/{id}, POST /v1/quotes, PATCH /v1/quotes/{id}, POST /v1/{entity}/import, POST /v1/{entity}/batch, POST /v1/batch.

FIX-0911-10: описание API мессенджера совпадает с тем, что отвечают ручки

Было

Машинное описание (/v1/openapi.json) ручек чатов, уведомлений, базы знаний и ленты расходилось с их поведением, и клиент, собранный по нему генератором, ломался на первом же вызове.

GET /v1/chats/find описывался с параметрами entityTypeId и entityId (числа) — ручка читает entityType и entityId (строки, например CRM и DEAL|123) и на описанную форму отвечала 400 MISSING_PARAMS всегда. GET /v1/chats/search описывался с параметром query — ручка читает search. POST /v1/posts описывался с телом message — ручка требует text.

Тридцать четыре операции этих семей не описывали ни одного отказа: только 200, 201 или 204, хотя ручки отвечают 400, 401, 403, 404 и 422. Генератор клиента не строил для отказов ни типов, ни обработчиков. Четыре операции (POST /v1/chats/{chatId}/files, POST /v1/posts, POST /v1/posts/{id}/comments, DELETE /v1/posts/{id}/comments/{commentId}) описывали успех как 200, тогда как ручки отвечают 201 и 204.

Стало

В части параметров и кодов успеха поведение ручек не менялось — исправлено описание (что поменялось в поведении, названо ниже отдельно). Параметры GET /v1/chats/find — entityType и entityId, строки, обязательные; GET /v1/chats/search — search; тело POST /v1/posts — text (обязательное), title, recipients, files. Успех четырёх операций описан тем кодом, которым ручка и отвечает: 201 и 204. Успешный ответ по-прежнему 200, 201 или 204 — ровно там, где и был: меняется описание, не ответ.

У каждой операции семей чатов, уведомлений, базы знаний и ленты, а также у GET /v1/bots, POST /v1/bots и GET /v1/bots/revision, описаны те отказы, которыми она отвечает, — из набора: 401 (MISSING_API_KEY, INVALID_API_KEY, TOKEN_MISSING), 403 (SCOPE_DENIED, WRITE_BLOCKED_READONLY_KEY на записях, BITRIX_ACCESS_DENIED), 404 (ENTITY_NOT_FOUND и коды конкретных ручек), 422 (BITRIX_ERROR), а где ручка проверяет вход сама — 400 с её кодами (MISSING_PARAMS, MESSAGE_REQUIRED, INVALID_PARAMS, INVALID_POST_ID и другие).

Попутно выправлено поведение — оба выправления только в сторону клиента, ни один прежде успешный вызов не отказывает: POST /v1/chats/messages/bulk — чистое чтение историй пачкой — отвечал ключу «только чтение» 403 WRITE_BLOCKED_READONLY_KEY: общий контейнер batch считался записью; теперь такой ключ читает пачкой так же, как и по одному. Часть записывающих ручек чатов и ленты, вызванных вовсе без тела запроса (и без заголовка Content-Type), отвечала 500; теперь такой вызов идёт тем же путём, что и с пустым телом {}: там, где поле обязательно, ручка отказывает сама (DELETE /v1/chats/{chatId}/users — 400 MISSING_PARAMS про userId, POST /v1/posts — про text), а у создания чата, где все поля необязательны, запрос уходит в Битрикс24 как есть. Правка сообщения (PATCH /v1/chats/{dialogId}/messages/{messageId}) в это исправление не входит и выправляется отдельным изменением.

Семья ботов описана целиком: у каждой операции над конкретным ботом (/v1/bots/{botId}…, кроме удаления, повторной авторизации, переподписки и передачи бота, у которых свой набор) объявлены 400 INVALID_BOT_ID, 404 BOT_NOT_FOUND, 410 BOT_DISABLED и 422 — отказы, которые даёт общий шаг поиска бота и обращение к порталу, — а также 401.

Интерактивный справочник (/docs → API reference) перегенерирован из исправленного описания в обоих сегментах: карточки GET /v1/chats/find, GET /v1/chats/search и пример POST /v1/posts показывают рабочие параметры.

Что описание этих операций ПО-ПРЕЖНЕМУ не называет — сознательно, потому что решение общее для всего API, а не для мессенджера: отказ по балансу (402 ACCOUNT_FROZEN) и ответы 429 RATE_LIMITED, 502 BITRIX_UNAVAILABLE, 503 при недоступности или перегрузке Битрикс24 — они возможны на любой операции, обращающейся к порталу, и будут объявлены одним правилом на всё описание отдельным изменением. Обработчик клиента должен быть к ним готов уже сейчас.

2026-09-10

BC-0910-1: сетевой сбой на пути к провайдеру приходит своим кодом ai_provider_network

Поддержка старого формата до: не предусмотрена

Было

Когда платформа не могла соединиться с кластером моделей или соединение обрывалось до его ответа, POST /v1/chat/completions, POST /v1/embeddings и POST /v1/audio/transcriptions отвечали 502 с общим кодом ai_provider_unavailable — тем же, что и при ошибке, которой ответил сам кластер. По коду ответа два случая не различались.

Стало

Тот же ответ 502 несёт код ai_provider_network. Тип server_error и повторяемость не изменились. Поля providerStatusCode у этого ответа нет: кластер не ответил вовсе. Код ai_provider_unavailable остаётся за случаями, когда кластер ответил ошибкой. Исходный статус приходит в providerStatusCode всегда, когда кластер ответил HTTP-статусом, — в том числе когда поток уже открыт, а кластер отказал на его установке. Поля нет там, где статуса не было: ошибка пришла кадром из тела уже открытого потока (такой кадр несёт поле только с кодом rate_limit_exceeded) или сбой случился при обработке ответа кластера. В потоковом режиме тот же код приходит в кадре ошибки перед data: [DONE] вместе с полями retryable и retryAfter. Распознавание речи сообщает сетевой сбой тем же кодом ai_provider_network.

Что делать интеграторам

Клиент, который решает о повторе по статусу 502, полю retryable или типу server_error, ничего не меняет. Клиент, который распознаёт сетевой сбой по коду ai_provider_unavailable, добавляет к проверке код ai_provider_network. Прежний код в этом сценарии с релиза не возвращается: переходного периода нет, потому что один и тот же ответ не может нести два кода.

FIX-0910-2: ошибки AI-провайдера приходят с паузой из его заголовка и в одном формате для всех потоков

Было

Когда кластер моделей отвечал 429, пауза для клиента бралась из текста ответа кластера, а без числа в тексте ставилась фиксированная в десять секунд. Заголовок Retry-After самого кластера при этом терялся. В потоковом режиме резервной модели Cowork/Code ошибка провайдера приходила кадром { "error": { "message": "fallback stream error", "type": "server_error" } } без полей code, retryable, retryAfter и providerStatusCode, поэтому отказ кластера по лимиту был неотличим от его внутренней ошибки. Кадр { "error": … }, который кластер присылает внутри уже открытого потока, уходил клиенту как обычный фрагмент ответа, а вызов записывался в журнал как успешный.

Стало

Пауза берётся из заголовка Retry-After кластера. Синхронный ответ rate_limit_exceeded несёт её в своём заголовке Retry-After, потоковый — в поле retryAfter кадра ошибки перед data: [DONE]. Число из текста ответа и значение по умолчанию используются только когда заголовка нет. Кадр ошибки потока резервной модели Cowork/Code собирается так же, как кадр основного потока: поля code, type, retryable, retryAfter и providerStatusCode присутствуют по тем же правилам. Кадр ошибки, пришедший внутри открытого потока, превращается в стандартный кадр ошибки платформы: ответ остаётся потоком, после уже переданных фрагментов приходит кадр с code, type, retryable и retryAfter, затем data: [DONE], а вызов записывается в журнал как ошибка. Кадр, поднятый из тела открытого потока, несёт providerStatusCode только при коде rate_limit_exceeded: из тела платформа принимает лишь статус 429, остальные ошибки из тела приходят кодом ai_provider_unavailable без этого поля. Отказ кластера HTTP-статусом на установке потока приходит кадром ai_provider_unavailable с providerStatusCode, как и синхронный ответ.

Влияние на интеграторов

Менять ничего не нужно. Клиент, который уже читает заголовок Retry-After и поле retryAfter, получает более точную паузу. Клиент потока резервной модели Cowork/Code может теперь различать отказы по code и providerStatusCode так же, как в основном потоке.

Затронутые эндпоинты: POST /v1/chat/completions, POST /v1/embeddings, POST /v1/audio/transcriptions.

FIX-0910-3: MCP-инструменты отмечают отказы API флагом ошибки

Было

За пределами entity-инструментов ответ API с success: false мог возвращаться как MCP-результат без флага ошибки.

Стало

Такие ответы отмечаются isError: true, включая локальные отказы и сетевые ошибки с success: false. Текст JSON с диагностикой сохраняется. Успешные ответы не меняются, поле isError в них отсутствует.

Влияние на интеграторов

MCP-клиенты, учитывающие isError, теперь распознают эти ответы как ошибки инструментов, а не успешные вызовы. Интеграции, которые игнорируют этот флаг и проверяют success в тексте JSON, продолжают работать как прежде: формат и содержимое JSON не изменились.

FIX-0910-4: сообщение об отказе QUEUE_TIMEOUT называет фактическое ожидание

Было

В отказе 429 QUEUE_TIMEOUT поле userMessage называло 30 секунд ожидания в очереди к Битрикс24. Число было вписано в текст сообщения и не совпадало с ожиданием, которое платформа Вайбкод применяла на самом деле.

Стало

userMessage называет то ожидание, которое применилось к этому вызову. Ответ по-прежнему приходит с кодом QUEUE_TIMEOUT, полем retryAfter и заголовком Retry-After — срок повтора берётся из них, а не из текста.

Влияние на интеграторов

Менять в коде ничего не нужно: форма ответа не изменилась. Клиенту, который подбирал свой таймаут по этому сообщению, число теперь показывает реальный предел ожидания в очереди.

NEW-0910-5: внешнее членство видно в V1: значение external у access.via и блок externalServers в /v1/me

Поле access.via в ответе GET /v1/infra/servers/{id} получило новое значение external (наряду с owner, collaborator и galaxy-reference). Оно означает, что вызывающего пригласили на этот сервер из ДРУГОГО аккаунта Битрикс24: поверхность кода (деплой, exec, загрузка файлов, логи, чтение и скачивание исходников, пробуждение) открыта и перечислена в access.allowedEndpoints, а кабинета аккаунта-владельца у него нет вовсе. Текст access._note у такой строки свой: он говорит про границу приглашения, а не про команду разработки, — прежний общий текст отправлял агента искать интерфейс, которого ему не положено. Остальные значения — owner, collaborator и galaxy-reference — не изменились.

GET /v1/me получил в блоке data.infra новую секцию externalServers рядом с collaboratorServers. В ней перечислены серверы на чужих аккаунтах Битрикс24, куда владельца ключа позвали внешним участником: total, count и items со строками id, name, role, companyPortalDomain (аккаунт, которому принадлежит сервер) и expiresAt (null — срока нет); items ограничен сотней строк, и total больше count означает, что список усечён. Секции две, а не одна, потому что различается credential: обычный ключ до чужой машины не дотягивается, у каждого внешнего членства свой ключ, который человек ведёт в собственном аккаунте, в разделе «Внешний доступ». Ни та, ни другая секция в infra.limits не входит — машины чужие.

Секция НЕОБЯЗАТЕЛЬНАЯ: ключ появляется в ответе, только если для аккаунта Битрикс24 компании, которой принадлежит сервер, включена кросс-аккаунтная коллаборация и по нему есть хотя бы одно живое внешнее членство. Отсутствие секции означает «ни одного не видно», а не «ни одного не существует»: строка выпадает из ответа и тогда, когда коллаборацию выключили на стороне компании-владельца, хотя членство физически ещё живо. Прямой запрос GET /v1/infra/servers/{id} подтверждает доступ ИМЕННО СЕЙЧАС: ответ 200 означает, что членство живо И кросс-аккаунтная коллаборация у компании-владельца включена. Ответ 403 EXTERNAL_COLLABORATOR_NOT_A_MEMBER означает «доступа сейчас нет» и не различает две разные причины — членства нет или владелец выключил коллаборацию: средствами клиента сегодня их не разделить.

Оба изменения аддитивные: прежние поля ответов не тронуты, и клиент, который о них не знает, продолжает работать как раньше.

FIX-0910-6: имя в X-Vibe-User-Name при открытии из каталога — из карточки сотрудника

Было

При открытии приложения из каталога платформы Вайбкод заголовок нёс имя учётной записи на платформе: у части сотрудников это логин, адрес почты или Unknown. При открытии того же приложения плиткой из Битрикс24 приходило имя из карточки сотрудника.

Стало

На обоих путях заголовок несёт имя из карточки сотрудника, сохранённое при авторизации приложения. Имени в сохранённых данных нет — цепочка прежняя: имя учётной записи, затем адрес почты, затем Unknown.

Влияние на интеграторов

Менять код не нужно. У части приложений значение заголовка изменится в сторону имени сотрудника. Имя остаётся отображаемым значением: как идентификатор учётной записи и основание для проверок доступа используйте X-Vibe-User-Id.

FIX-0910-7: ответ модели, размещённой у стороннего провайдера, называет публичный id модели

Было

Для модели каталога, размещённой у стороннего OpenAI-совместимого провайдера под публичным id, поле model в ответе POST /v1/chat/completions и в каждом событии потока повторяло внутреннее имя модели у провайдера, а не запрошенный публичный id. Текст ошибки провайдера тоже называл внутреннее имя.

Стало

Поле model в ответе и в каждом событии потока равно публичному id модели, который клиент передал в запросе, — как у остальных моделей каталога. Текст ошибки провайдера называет тот же публичный id. Успешный ответ остаётся успешным, форма конверта не меняется.

Влияние на интеграторов

Клиент, который сверяет model в ответе с запрошенным id, теперь получает совпадение и для таких моделей. Интеграции, которая разбирала внутреннее имя модели из поля model или из текста ошибки, нужно перейти на публичный id: внутреннее имя больше не отдаётся.

BC-0910-8: GET /v1/me отвечает 401 на удалённый ключ вместо 200

Поддержка старого формата до: не предусмотрена

Было

GET /v1/me отдавал KEY_NOT_FOUND телом {"success": false, "error": {"code": "KEY_NOT_FOUND", "message": "Key not found"}} под статусом 200 OK. Так отвечал вызов по ключу, удалённому во время обработки запроса. Отозванный ключ этой ошибки не получает — у него KEY_INACTIVE.

Стало

Тот же отказ приходит под статусом 401 с тем же телом. Форма тела не изменилась.

Что делать интеграторам

Клиенту, чья HTTP-библиотека сама бросает исключение на 4xx (axios с дефолтным validateStatus, ky, got, ofetch, Guzzle с http_errors), править код обязательно: до тела ответа он больше не доходит, вместо ветки по полю success срабатывает исключение. Обработайте 401 и выпустите новый ключ по нему.

Клиент, определявший успех по HTTP-статусу (res.ok, response.raise_for_status()), раньше принимал удалённый ключ за рабочий и разбирал тело отказа как ответ /v1/me. Теперь он получает 401 — проверьте, что ветка ошибки ведёт к выпуску нового ключа, а не к повтору того же запроса.

Клиент, который читает поле success из тела и на 4xx исключения не бросает (fetch без проверки статуса), работает без изменений.

BC-0910-9: отказ Битрикс24 по учётным данным ключа приходит как 401, а не 422

Поддержка старого формата до: не предусмотрена

Было

Когда Битрикс24 отклонял сами учётные данные, с которыми ключ обращается к порталу (отозванный или переставший действовать вебхук), вызов отвечал 422 BITRIX_ERROR, а код портала приходил дополнительным полем error.b24Code. На этом статусе отказ выглядел как ошибка данных запроса, поэтому клиенты повторяли вызов — на живом портале так набегало около 2 300 бесполезных повторов в сутки.

Стало

Тот же отказ приходит как 401 PORTAL_CREDENTIALS_REJECTED с подсказкой, что повтор не поможет, пока ключ не переподключён. Код приходит на любом маршруте, читающем данные портала, и в подошибке POST /v1/batch. Описание — Авторизация, ключи и права.

Что делать интеграторам

Разбирающим 422 BITRIX_ERROR с error.b24Code равным authorization_error или INVALID_CREDENTIALS — перевести эту ветку на 401 PORTAL_CREDENTIALS_REJECTED и вместо повтора запускать переподключение ключа (POST /api/keys/:id/reconnect). Остальные значения error.b24Code в 422 BITRIX_ERROR не изменились.

FIX-0910-10: операции перемещения и копирования на Диске появились в OpenAPI

Было

Пять живых операций Диска отсутствовали в /v1/openapi.json и в карточках справочника API. Четыре из них — перемещение и копирование файла и папки — были описаны на страницах документации, а получение содержимого корня хранилища не было описано нигде. Агент, который знакомится с платформой по машинной схеме, делал из этого вывод, что таких методов нет. Отдельно врала карточка скачивания файла: её заголовок и описание обещали возврат ссылки, тогда как метод отдаёт байты.

Стало

В OpenAPI и в карточках справочника описаны POST /v1/storages/{id}/children, POST /v1/files/{id}/moveto, POST /v1/files/{id}/copyto, POST /v1/folders/{id}/moveto и POST /v1/folders/{id}/copyto — со скоупом disk, телом запроса и полным набором кодов отказа. Операция GET /v1/files/{fileId}/download объявляет двоичный ответ и описана как поток байтов, а не как ссылка. Поведение методов не менялось, успешный ответ остаётся HTTP 200, прежние статусы и форматы ошибок сохранены.

BC-0910-11: пользовательский AI-провайдер больше не проходит по редиректу

Поддержка старого формата до: не предусмотрена

Было

Если базовый адрес пользовательского провайдера custom-openai-compat отвечал HTTP-редиректом (301, 302, 303, 307 или 308), платформа переходила по нему и выполняла запрос по новому адресу. Так работали проверка ключа, загрузка каталога моделей и вызовы моделей.

Стало

Платформа по редиректу не переходит и возвращает отказ.

Что делать интеграторам

Укажите в baseUrl конечный адрес API, на который ведёт редирект. Частый случай — https:// вместо http://. Уже сохранённый ключ обновите через PATCH /v1/ai/credentials/:id.

BC-0910-12: списочные ручки задач, телефонии и универсальных списков больше не игнорируют filter молча

Поддержка старого формата до: не предусмотрена

Было

Часть списочных ручек отвечала 200 и выборкой шире запрошенной, когда в запросе был параметр filter. Отличить применённый фильтр от отброшенного по ответу было нельзя.

Первая причина — две формы записи filter пишутся разборщиком строки запроса в одно и то же место, поэтому вторая замещает первую. Запрос ?filter[AUTHOR_ID]=1&filter= оставлял пустое значение, оно читалось как «фильтра нет», и в Битрикс24 уезжал вызов без фильтра: GET /v1/tasks/{taskId}/comments, GET /v1/calls/statistics, GET /v1/lists/{iblockId}/sections и GET /v1/lists/{iblockId}/elements. По той же причине терялось условие, записанное скобками глубже двух уровней (?filter[a][b][c]=1): до параметра filter оно не доходило вовсе.

Вторая причина — ручки, у которых фильтра нет вовсе, параметр не читали и не отклоняли: GET /v1/tasks/{taskId}/history, GET /v1/tasks/stages/{entityId}, GET /v1/tasks/{taskId}/checklist, GET /v1/lists и GET /v1/voximplant-lines.

Стало

Конверт filter, из которого хотя бы одно написанное вами условие не доехало до параметра, отклоняется с 400 и кодом INVALID_FILTER — до обращения к Битрикс24. Условие теряется, когда две формы смешаны в одном запросе (в любом порядке), когда два написания адресуют одно и то же условие (?filter[a]=1&filter[a][b]=2) и когда скобок больше двух уровней — разборщик строки запроса такое не собирает. Текст ошибки называет, что именно произошло. Собирайте фильтр в одной форме, каждое условие пишите один раз и не глубже двух уровней.

Ручки, у которых фильтра нет, отвечают 400 с кодом UNSUPPORTED_FILTER и называют, чем пользоваться вместо него: field для истории задачи, sort и offset для списка универсальных списков.

Пустое значение ?filter= по-прежнему означает «без фильтра», и ответ остаётся 200. Не затронуты на ручках, которые фильтр поддерживают: фильтр одной формы, у которого все скобочные условия дошли до параметра — целиком скобочный или один объект JSON, — именованные параметры field, sort, start, а также повторный ?filter=a&filter=b, у которого разборщик оставляет последнее значение — при условии, что оно НЕ пустое. Если последним стоит пустой ?filter=, он затирает предыдущее условие, и такой запрос отклоняется.

Две оговорки, чтобы список читался честно. На ручках, у которых фильтра нет вовсе, отклоняется любой значимый filter, включая одноформенный. И запрос, где скобочная форма стоит ПОСЛЕ пустого ?filter= (?filter=&filter[ID]=1), раньше отвечал 200 с корректно применённым фильтром, а теперь тоже отклоняется: имена параметров в строке запроса не несут значений, отличить пустую запись от непустой нельзя, поэтому отклоняются обе — как это давно сделано для списочных запросов сущностей.

Что делать интеграторам

  1. Соберите фильтр в ОДНОЙ форме: либо целиком скобочной (?filter[ID]=1&filter[STAGE]=NEW), либо одним объектом JSON (?filter={"ID":1,"STAGE":"NEW"}) — там, где ручка обе формы принимает. ⚠️ GET /v1/calls/statistics принимает ТОЛЬКО скобочную форму: объект JSON уходит в Битрикс24 строкой и фильтром не становится. Не отправляйте пустой ?filter= рядом со скобочными условиями — даже если раньше такой запрос работал.
  2. Уберите скобочные условия глубже двух уровней: разборщик строки запроса их не собирает, и до Битрикс24 они не доходили и раньше.
  3. Для ручек, у которых фильтра нет вовсе, перейдите на именованные параметры — их называет текст ошибки: field для истории задачи, sort и offset для списка универсальных списков.

Поддержка старого поведения не предусмотрена: половину потерянных условий восстановить нельзя, а отвечать 200 на запрос, фильтр которого не применён, — это и есть исправляемый дефект.

FIX-0910-13: два написания одного поля в фильтре больше не теряют условие молча

Было

У поля есть несколько принимаемых написаний: имя из схемы и родное имя Битрикс24 (amount и OPPORTUNITY у сделок), разный регистр. Если два условия в одном фильтре сводились к одному имени фильтра Битрикс24, запрос из-за ошибки отвечал 200 с потерянным условием: применялось только одно из двух, а какое именно — решал порядок ключей в запросе. Выборка выглядела отфильтрованной, хотя половина отбора не применялась. Полагаться на этот ответ было нельзя: он и не был обещан документацией. То же происходило с двумя написаниями одного оператора, с парными именами полей даты (updatedAt/updatedTime, createdAt/createdTime, где у сущности объявлено одно из двух, а второе принимается как псевдоним) и с парами синонимов, объявленных у сущности отдельными именами: у лидов это amount/opportunity, stageId/statusId, currency/currencyId.

Стало

Такой запрос отклоняется с 400 INVALID_DUPLICATE_FILTER_FIELD, и в message названы оба условия и общее имя, к которому они свелись. Запрос с одним написанием поля отвечает HTTP 200 как раньше — успешный ответ сохранён. Операторы, дающие РАЗНЫЕ имена, тоже не изменились: { "amount": { "$gte": 1000, "$lte": 5000 } } — это диапазон, а не дубль.

Решает результат, а не написание, и у двух пар границы РАЗНЫЕ. UF_-форма вместе с camelCase-написанием того же пользовательского поля складывается в одно имя только у сущностей со старым стилем именования И только для того написания, которое платформа переводит: чисто буквенное (ufCrmProjectCode) — на всех таких сущностях, а с цифровым хвостом (ufCrm_1698325419) — только у реквизитов. На остальных сущностях цифровая пара уезжает двумя именами, отказа не даёт, и второе условие по-прежнему молча отбрасывает Битрикс24. У сущностей с camelCase-именами и у элементов CRM это разные имена на проводе, и отказа тоже нет. $ne вместе с $nin на одном поле дают один префикс ! у ВСЕХ сущностей, кроме элементов CRM, — включая сущности с camelCase-именами (товары каталога, почтовые ящики, смарт-процессы); у элементов CRM $nin получает отдельный префикс !@, и пара проходит.

Правило действует на всех списках, поиске, агрегации и в обоих пакетных вызовах; в пакетном вызове отклоняется только сам вызов, соседние выполняются.

Затронутые эндпоинты: GET /v1/{entity} и POST /v1/{entity}/search сущностей со стандартным разбором фильтра, POST /v1/{entity}/aggregate и его legacy-двойник GET /v1/{entity}/aggregate, POST /v1/batch и подвызовы POST /v1/{entity}/batch, а также GET /v1/addresses с POST /v1/addresses/search. На этих дверях фильтр разбирает один и тот же код, поэтому отказ приходит одинаково.

Отдельные маршруты со своим форматом фильтра разбирают его собственным кодом и в это изменение не входят: там пара написаний по-прежнему отвечает 200 с потерянным условием. Среди прочих — GET /v1/requisite-links с POST /v1/requisite-links/search, статистика звонков, списки, комментарии задач и открытые линии; перечень не закрытый.

Почему это исправление, а не смена контракта: прежний 200 на таком запросе не был обещан документацией и не был воспроизводимым — он применял одно из двух условий, а какое именно, решал порядок ключей. Полагаться на такой ответ было нельзя, поэтому окна поддержки прежнего поведения нет: поддерживать нечего. Запрос с одним написанием поля не затронут.

NEW-0910-14: остатки вайбов по всем порталам одной выгрузкой

Появился GET /v1/platform/revenue/balances — одна строка на портал, снимок «на сейчас». В строке: balance (тот же остаток, что портал видит у себя), его состав paidRemaining + grantRemaining, накопленный недобор accruedShortfall, overdraftLimit, billingMode, frozen, а также portalId, portalDomain, portalStatus, portalKind и accountId. Суммы сходятся: balance = paidRemaining + grantRemaining − accruedShortfall.

Окна у запроса нет, есть limit и cursor; время снимка приходит полем capturedAt рядом с data. Счёт без траншей отдаётся строкой с нулями, а не пропускается, поэтому выгрузка описывает популяцию порталов целиком. Это единственная поверхность, где виден грантовый остаток: GET /v1/platform/revenue/topups идёт по купленным траншам, поэтому портал без покупок в нём отсутствует, а welcome-аллокация и начисленные бонусы не показаны.

Метод открывается отдельным доступом revenue:balances — ключ с revenue:read получает на нём 403 INSUFFICIENT_SCOPE. Прежние выгрузки не изменились, ключи с прежними доступами продолжают работать без правок.

NEW-0910-15: компании отдают связанные контакты через `include=contact`

Справочник компаний объявляет связь contact, и параметр include=contact теперь принимается на GET /v1/companies/{id}, GET /v1/companies и POST /v1/companies/search. В каждой записи появляется _included.contacts — массив полных карточек контактов, к каждой из которых добавлены поля привязки sort, isPrimary, roleId; у компании без привязанных контактов это пустой массив. Раньше такой запрос отвечал 400 INVALID_INCLUDE, потому что у компаний не было объявлено ни одной связи и список доступных значений include был пуст.

Вместе со связью открылись подмаршруты привязок: GET /v1/companies/{id}/contacts — список привязок, POST /v1/companies/{id}/contacts — добавить одну, PUT /v1/companies/{id}/contacts — заменить весь набор, DELETE /v1/companies/{id}/contacts/{contactId} — снять одну. Запросы без include отвечают как прежде.

FIX-0910-16: булев флаг пользовательского поля больше не теряется молча

Было

Свойства пользовательского поля multiple, mandatory, showFilter, showInList, editInList, isSearchable Битрикс24 хранит символами Y и N. Присланные булевыми, они применялись через раз — и это в пределах ОДНОГО тела запроса: showInList: true и editInList: true включались, а showFilter: true и isSearchable: true молча сохранялись выключенными. Ответ при этом приходил успешный: 201 на создании, {"updated": true} на изменении. Повторная отправка тех же флагов булевыми ничего не меняла: выключенные оставались выключенными.

Ни один из шести флагов не был объявлен в описании API: у создания перечислялись только fieldName, userTypeId и label, у изменения тело описывалось пустым объектом. Узнать рабочую форму значения было неоткуда.

Стало

Все шесть флагов принимаются и булевыми, и строками "Y" / "N" — платформа приводит обе формы перед вызовом портала. Каноническими формами остаются булев и "Y" / "N"; вдобавок платформа принимает снисходительные написания: "y", 1, "1" включают, а "n", 0, "0" выключают. Прежде все они доезжали до портала как есть — и включающие флаг не включали, а теперь включат; выключающие как выключали, так и выключают, для них не меняется ничего. Нераспознанные значения проходят как прежде, без изменений.

Обратите внимание, если ваша интеграция уже шлёт эти флаги булевыми. Прежде часть из них не срабатывала, и вы могли к этому привыкнуть — теперь сработают все. Замерены два перещёлкивания: showFilter: true и isSearchable: true прежде оставляли флаг выключенным, а теперь включат его. Включение isSearchable не косметика: значения поля уезжают в полнотекстовый поиск портала и начинают всплывать в общем поиске.

Следите и за mandatory: true — включённым он блокирует создание записей без этого поля, в том числе через другие интеграции портала. Срабатывал ли он от булевого прежде, мы не замеряли, поэтому исходите из того, что теперь сработает.

Если ваша интеграция шлёт showFilter: "E" — прежняя таблица страниц по фиксированным типам CRM называла это «маской» — поведение изменится: раньше такая запись оставляла фильтр ВЫКЛЮЧЕННЫМ, теперь она его включает. Среди ЗАМЕРЕННЫХ строковых значений это единственное перещёлкивание, и сделано оно нарочно: "E" — форма, в которой фильтр приходит на чтении, и возвращать её обратно должно быть безопасно.

Если ваша интеграция шлёт "I" или "S" по нашей прежней таблице — такая запись фильтр не включает ни прежде, ни теперь. Отправьте true, чтобы его включить.

Проверьте, что шлёте, до обновления.

Флаги объявлены в описании API у создания и у изменения. Отдельно описана особенность чтения showFilter: включённый фильтр возвращается как E — это форма хранения Битрикс24, а не ошибка. Значения "I" и "S" при записи не принимаются и фильтр выключают, поэтому включать его нужно значением true, "Y" или прочитанным "E". Справочник по фиксированным типам CRM прежде описывал эти буквы как режимы фильтра и приводил их в примерах — замер на четырёх типах поля этого не подтвердил, страницы исправлены.

Отдельно закрыт круговой рейс: прочитанное "E" теперь можно отправить обратно как есть — платформа понимает его как «включён». Прежде обычный цикл «прочитал поле → поменял одно свойство → отправил объект целиком» возвращал порталу прочитанное "E", и фильтр молча выключался под успешным ответом.

Правка касается фиксированных типов CRM — сделок, лидов, контактов, компаний, предложений и реквизитов. Пользовательские поля смарт-процессов идут другим контрактом Битрикс24 и этой правкой не затронуты.

FIX-0910-17: Ответы V1 возвращают X-Request-Id

Было

Документация просила приложить X-Request-Id к обращению в поддержку, но ответы API не содержали этот заголовок.

Стало

Каждый ответ /v1, сформированный backend, содержит серверный X-Request-Id. Значение из проблемного ответа можно приложить к обращению вместе со временем запроса.

Влияние на интеграторов

Существующие запросы менять не нужно. Клиент может сохранять новый заголовок для диагностики, но не обязан его обрабатывать.

2026-09-09

BC-0909-1: Расшифровка аудио сохраняет тарификацию по подписке при отключении Cowork/Code

Поддержка старого формата до: не предусмотрена

Было

При отключённом Cowork/Code запросы к POST /v1/audio/transcriptions уже выданным ключом vibe:cowork обходили квоту подписки, даже если подписка была активна и для доступной модели была назначена цена аудио. Исчерпание квоты подписки не препятствовало успешному ответу HTTP 200.

Стало

Отключение продукта не меняет тарификацию: при активной подписке и назначенной цене аудио за минуту или за вызов расход учитывается в её квоте, как у чата. При исчерпании окна запрос получает HTTP 402 cowork_quota_exhausted с заголовком Retry-After. Без активной подписки сохраняется прежний путь тарификации без списания квоты подписки.

Что изменить в интеграции

Обрабатывайте HTTP 402 cowork_quota_exhausted и при отключённом продукте: дождитесь сброса окна по Retry-After или увеличьте тариф подписки. Ключ и формат запроса менять не требуется.

NEW-0909-2: Идентификаторы методов каталога 1С поддерживают Unicode

GET /v1/onec/tools и POST /v1/onec/tools/{method}/call поддерживают идентификаторы методов из букв, комбинируемых знаков и чисел Unicode, а также ._-. Предел — 100 кодовых единиц UTF-16. Передавайте в URL точный идентификатор из каталога через encodeURIComponent(method): регистр и Unicode-представление сохраняются. Существующие латинские идентификаторы продолжают работать.

BC-0909-3: Универсальные списки: отсутствующий список отвечает «не найдено», а неверное смещение — ошибкой

Поддержка старого формата до: не предусмотрена

Было

Один и тот же несуществующий список отвечал по-разному в зависимости от того, какой адрес дёрнуть: перечень элементов, перечень полей, набор типов полей, отдельный элемент, отдельное поле и файлы элемента отвечали 422 с текстом от Битрикс24 — то есть ошибкой «что-то пошло не так», а не «такого списка нет». Логика «прочитать, а если нет — создать» на этом ломалась.

Постраничный обход тех же перечней принимал любое значение смещения. start=-5, start=abc, start=1.5 и start=1e2 не отклонялись: смещение молча не применялось либо применялось искажённым (1e2 превращалось в 1, и вместо сотой записи приходила вторая). Ответ при этом был 200, поэтому ошибка в вызывающем коде оставалась невидимой — при том что соседние параметры того же запроса, iblockTypeId и sort, отклоняются с внятным сообщением.

Стало

Отсутствующий список отвечает 404 с кодом LIST_NOT_FOUND на шести адресах, где Битрикс24 сообщает об этом машинным кодом. Смещение принимается только как неотрицательное целое в обычной записи; всё прочее отклоняется 400 с кодом INVALID_PARAMS, и в сообщении назван именно тот параметр, который не подошёл. Проверяются оба параметра, start и offset, даже когда спор о старшинстве между ними выигрывает первый.

Что делать интеграторам

Если ваш код ловил 422 от перечней списков как «списка нет» — переведите его на 404. Распознаётся машинный код Битрикс24, а не текст сообщения, поэтому ответ не зависит от языка портала. Сам код замерен на международной платформе: если ваш портал сообщает об отсутствии списка иначе, распознавание не сработает и ответ останется прежним. Если смещение собиралось из внешнего источника и могло прийти отрицательным или дробным, теперь вместо страницы придёт ошибка: почините источник, а не обход. Отдельно назовём один вход, который прежде понимался ВЕРНО: ?start=+5 — плюс в строке запроса декодируется пробелом, и прежний разбор читал такое как 5. Теперь это отказ. Если ваш сборщик кодирует плюсом, уберите его: ?start=5.

Отдельно про запись параметров квадратными скобками. ?start[]=7 и ?iblockTypeId[]=lists раньше проходили: платформа склеивала такой список в одно значение и работала дальше. Теперь это отказ — скобки означают структуру, а перечисленные параметры структуру не принимают. Если ваш сборщик запроса ставит скобки автоматически на всякий список, передавайте эти три параметра простым значением: ?start=7, ?offset=7, ?iblockTypeId=lists. Скобочные формы с именем внутри (?start[x]=1) прежде роняли запрос внутренней ошибкой — теперь на них приходит понятный отказ.

Ещё один параметр в том же ряду — набор возвращаемых полей: ?select[x]=1 прежде роняло запрос внутренней ошибкой, теперь набор просто не применяется и приходит полный состав полей; пустые элементы вида ?select[]=&select[]=NAME больше не уезжают в портал пустой строкой.

То же касается тела запроса при создании раздела и элемента: "iblockSectionId": [5] раньше принималось за пятёрку и клало запись под этого родителя, теперь значение отбрасывается и запись попадает в корень. Присылайте число или строку с числом.

Тип инфоблока в теле сузился так же, только отвечает иначе: "iblockTypeId": ["lists"] раньше проходил (список склеивался в одно значение), теперь приходит 400 с кодом INVALID_IBLOCK_TYPE — на восьми адресах, где этот параметр вообще читается из тела: создание и изменение списка, поля, раздела и элемента. Удаления его не принимают и не затронуты. Присылайте строку: "iblockTypeId": "lists".

Чего правка НЕ меняет

Чтение разделов по-прежнему отвечает жалобой на неверный тип инфоблока, а чтение самого списка — отказом в правах, если номер списка числовой. По символьному номеру этот же адрес отвечал «не найдено» и до правки: там Битрикс24 возвращает пустой результат, а не отказ. Битрикс24 сообщает об отсутствии списка на этих адресах теми же словами, что и о настоящей нехватке прав и о настоящем неверном типе, поэтому распознавать их как «не найдено» означало бы прятать реальные отказы.

BC-0909-4: фантомное поле contacts удалено у компаний, лидов и сделок

Поддержка старого формата до: не предусмотрена

Было

Схема полей и ответы компаний, лидов и сделок могли содержать contacts, хотя это поле нельзя было надёжно прочитать из Битрикс24. Явный select: ["contacts"] принимался и отвечал 200.

Стало

contacts не публикуется в схеме полей и не возвращается в записях. Явный select: ["contacts"] без * или UF_* отклоняется с 400 UNKNOWN_SELECT_FIELD. Запрос всех полей через * или UF_* по-прежнему выполняется, но contacts из ответа удаляется.

Что делать интеграторам

Удалите contacts из явных списков select. Для связей с контактами у компаний используйте contactIds, а у лидов и сделок — contactId и contactIds.

Затронутые эндпоинты: GET /v1/companies/fields, GET /v1/leads/fields, GET /v1/deals/fields, операции чтения и записи из разделов компаний, лидов и сделок, POST /v1/batch, POST /v1/{entity}/batch.

FIX-0909-5: на коробочном портале выписывается ключ, которому вебхук не нужен

Было

На коробочном портале у владельца без ключа разработчика выдача и перевыпуск ключа с правами только vibe:* (например vibe:infra + vibe:storage) отвечали 400 BOX_NO_DEVELOPER_KEY, хотя входящий вебхук такому ключу на портале не регистрируется вовсе. Затрагивало POST /v1/keys, POST /v1/keys/{id}/rotate и выдачу личного ключа приложения Коворка.

Стало

Набор прав без единого права Битрикс24 проверяется раньше коробочного гейта: ключ выдаётся, поле webhookUrl остаётся пустым, к порталу обращений нет. Отказ 400 BOX_NO_DEVELOPER_KEY сохраняется для наборов, где хотя бы одно право Битрикс24 есть, — такому ключу вебхук нужен, а снять его без ключа разработчика владельца нечем. Перевыпуск требует ещё одного условия: у прежней строки не должно остаться вебхука. Ключ без прав Битрикс24, за которым вебхук всё же числится, отвечает прежним 400 — снять этот вебхук без ключа разработчика нечем, и успешный ответ скрыл бы, что прежний доступ продолжает работать.

FIX-0909-6: замена ключа приложения больше не выдаёт секрет с истёкшим сроком

POST /v1/cowork/applications/{id}/key переносил срок жизни заменяемого ключа на новый как есть. Если срок уже прошёл, владелец получал секрет, не проходящий ни одного запроса, а прежний протухший ключ получал сутки жизни и на эти сутки снова начинал работать.

Было

Приложение с ключом, срок которого истёк три дня назад. Ответ 201, issued: "rotated", key.expiresAt — та же прошедшая дата, previousKey.graceUntil — сутки вперёд от момента замены.

Стало

Ответ по-прежнему 201, issued: "rotated". key.expiresAt отсчитывается заново по сроку, принятому на портале (его же отдаёт GET /v1/cowork/applications/defaults полем keyExpiresInDays), — новый ключ работает. previousKey.graceUntil не превышает собственного срока заменяемого ключа: у протухшего он остаётся в прошлом, и суточная отсрочка его не воскрешает. Действующий срок и его отсутствие переносятся как раньше — бессрочный ключ остаётся бессрочным, живой срок повторяется один в один.

Та же поправка к отсрочке действует у POST /v1/keys/{id}/rotate: перевыпуск ключа с истёкшим сроком больше не даёт прежнему ключу сутки работы.

FIX-0909-7: публичный числовой адрес в baseUrl и proxyUrl больше не отбивается как частный

Было

POST /v1/ai/credentials и PATCH /v1/ai/credentials/{id} с публичным числовым адресом в baseUrl или proxyUrl — например http://203.0.113.10:8080 — отвечали 400 с кодом BASE_URL_PRIVATE или PROXY_URL_PRIVATE. Сохранить такой ключ удавалось, только записав тот же адрес доменным именем.

Стало

Такой запрос отвечает успешно — как и с доменным именем. Частный числовой адрес по-прежнему получает 400 с теми же кодами: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 0.0.0.0/8, а для IPv6 — ::1, ::ffff:, fc00::/7 и fe80::/10. Только для числовых адресов к этому перечню добавились зарезервированные диапазоны, которыми числовой адрес раньше пройти не мог в принципе: 192.0.0.0/24, 198.18.0.0/15, 224.0.0.0/4, 240.0.0.0/4 (вместе с 255.255.255.255), а для IPv6 — fec0::/10, ff00::/8 и 100::/64. Для доменных имён перечень запрещённых диапазонов не менялся: имя, ведущее в любой из этих зарезервированных диапазонов, работает как работало, и запрос с доменным именем отвечает успешно, как прежде. Исключение — адреса, встраивающие IPv4 в IPv6-форму (::a.b.c.d, NAT64 64:ff9b::/96, 6to4 2002::/16): они отбиваются одинаково, и когда записаны числами, и когда имя ведёт на такой адрес.

FIX-0909-8: спека объявляет отказ 429 у ручек с собственным лимитом частоты

Было

Двадцать восемь операций несут собственный лимит частоты и отвечают 429 RATE_LIMITED, когда он исчерпан, а публичная спека GET /v1/openapi.json этот ответ у них не объявляла. Клиент, собранный по спеке, считал 429 неописанным статусом и не строил ветку повтора запроса вовсе. Затронуты POST /v1/batch, GET /v1/bots/:botId/events, GET /v1/chats/recent, POST /v1/chats/events/subscribe, GET /v1/connect/authorize, POST /v1/connect/token, POST /v1/connect/revoke, GET /v1/app/blueprints/:slug, GET /v1/me/sources, POST /v1/feedback/attachments, GET /v1/platform/coupons/campaigns, GET /v1/platform/coupons/campaigns/:slug, GET /v1/workday/records и пятнадцать ручек инфраструктуры — POST /v1/infra/servers, GET /v1/infra/servers/:id/ssh, POST /v1/infra/servers/:id/reboot, PATCH /v1/infra/servers/:id/sleep, PATCH /v1/infra/servers/:id/port, POST /v1/infra/servers/:id/wake-schedules, PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId, DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId, POST /v1/infra/servers/:id/sources, POST /v1/infra/servers/:id/exec, POST /v1/infra/servers/:id/upload, POST /v1/infra/servers/:id/icon, GET /v1/infra/servers/:id/logs, POST /v1/infra/servers/:id/deploy и POST /v1/infra/servers/:id/unstick. Отдельно у POST /v1/platform/coupons/issue отказ 429 был объявлен, но назван кодом RATE_LIMIT_EXCEEDED, которого платформа не отдаёт.

Стало

У каждой из этих операций отказ 429 объявлен в спеке, а у выпуска купонов назван настоящий код RATE_LIMITED. Описание отправляет за действующим значением в заголовок ответа x-ratelimit-limit, а за сроком повтора — в Retry-After. Число в спеку не вписано намеренно: заголовок — единственный источник, который не расходится с настройкой. У POST /v1/infra/servers, POST /v1/infra/servers/:id/upload и POST /v1/infra/servers/:id/deploy на этом же статусе назван второй код DEPLOY_BACKEND_BUSY — он приходит, когда заняты все места под встроенное тело запроса, отказывает конкретному телу, а не вызывающему, и несёт Retry-After: 30. Общие сведения о лимитах остаются на странице лимитов.

Влияние на интеграторов

Лимиты и поведение ручек не менялись, менять запросы не нужно. Клиенту, сгенерированному из спеки до этой правки, стоит пересобрать его — тогда обработка 429 появится там, где раньше отказ приходил как неописанный. Проверьте ветку разбора кода у выпуска купонов, если она сверялась со спекой: сервер отвечает RATE_LIMITED.

FIX-0909-9: истёкший пользовательский токен OAuth возвращает ошибку авторизации

Было

Если приложение не могло обновить истёкший пользовательский токен, отказ Битрикс24 expired_token возвращался из V1 API как 422 BITRIX_ERROR, то есть как ошибка данных запроса.

Стало

Одиночный вызов V1 API возвращает 401 TOKEN_EXPIRED с подсказкой повторно открыть приложение из меню Битрикс24. В пакетном вызове код TOKEN_EXPIRED приходит в ошибке отдельного вызова, а общий HTTP-ответ остаётся 200. Подробнее: ошибки API.

FIX-0909-10: отказ по частоте на выдаче токена стал машиночитаемым

Было

При срабатывании ограничителя частоты на входе в платформу POST /v1/connect/token отдавал HTML-страницу и не ставил заголовок Retry-After. Приложение не могло ни разобрать тело, ни узнать, через сколько повторять. Для входа по коду устройства это единственный отказ по частоте, до которого реально доходит живой опрос.

Стало

Тот же отказ приходит с телом в форме RFC 6749 — {"error":"slow_down","error_description":"..."} — и с заголовком Retry-After, который называет минимальную паузу в секундах. Форма совпадает с той, что эндпоинт уже объявляет для остальных отказов, поэтому готовая OAuth-библиотека разбирает её без доработок. Статус ответа остаётся 429, поэтому отказ по частоте по-прежнему отличим от состояний авторизации, которые приходят с кодом 400. Прежние вызовы работают без изменений.

Важно: ограничителей частоты на этом маршруте два, и тела у них разные. В форме RFC 6749, описанной выше, отвечает только ограничитель на входе в платформу. Ограничитель самого эндпоинта отвечает тем же статусом 429, но в общем конверте API — { "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } }, — поэтому ветка вида «на 429 разобрать тело и сравнить error со slow_down» распознает лишь половину отказов, а вторую примет за неизвестную ошибку. Различить ярусы можно по заголовку X-RateLimit-Limit: отказ на входе в платформу его не несёт, отказ самого эндпоинта несёт. Значение Retry-After — минимальная пауза, а не обещание, что следующий запрос примут, поэтому приложению нужно наращивать и собственную паузу. Оба отказа и оба тела описаны в разделе Partner Connect.

FIX-0909-11: reasoning модели для Cowork больше не попадает в финальный текст

Было

Когда Cowork не передавал настройку reasoning явно, модель могла вернуть внутренний рабочий текст как обычную часть финального ответа.

Стало

POST /v1/chat/completions для ключей с областью vibe:cowork применяет объявленную моделью настройку reasoning по умолчанию. Рассуждение остаётся в отдельном канале, а самостоятельный финальный текст сохраняется. Ответ остаётся HTTP 200.

Влияние на интеграторов

Изменения клиентов не требуются. Вызовы без области vibe:cowork сохраняют прежнее поведение.

BC-0909-12: коробочная покупка идёт на кассу своей страны

Поддержка старого формата до: не предусмотрена

Было

Клиента с коробочной лицензией Беларуси, Казахстана или Узбекистана уводило на российский сайт: другой коробочной кассы не существовало, и платформа намеренно считала такую лицензию российской. Купить там он всё равно не мог — касса отклоняла ключ чужого региона, — то есть путь вёл в тупик без объяснения.

Стало

Когда у страны открыта своя коробочная касса, покупка идёт на её сайт и в её валюте — цены, валюта и адрес оформления берутся из блока своего региона. Пока в каталоге страны нет коробочных позиций, пополнение для таких клиентов недоступно с понятным отказом, а не уводит на кассу, которая всё равно отклонит покупку. Появятся позиции — покупка включится сама, отдельного релиза для этого не требуется.

Дополнительно: платёж, пришедший в валюте, отличной от валюты заказа, теперь придерживается для разбора вместо тихого закрытия заказа.

GET /v1/cowork/subscription/preview для такой лицензии отдаёт валюту своей страны вместо RUB.

Что делать интеграторам

Читать валюту из ответа, а не подразумевать рубль: currency у пакетов и в корне ответа теперь может быть BYN, KZT или UZS, а пока пополнение для страны недоступно — null при topUpAvailable: false. Суммы приходят в минорных единицах своей валюты — пересчёт в рубли на стороне интегратора даст неверные числа.

Обработать отказ BOX_TOPUP_NOT_AVAILABLE на POST /api/billing/topup-init и признак недоступности в каталоге: он приходит, пока в каталоге страны нет коробочных позиций. Раньше в этом случае возвращалась ссылка на российскую кассу — покупка по ней всё равно не проходила, поэтому обрабатывать её как рабочую больше нельзя.

2026-09-08

FIX-0908-1: склады не подставляют чужой номер и открыто отказывают в неподдерживаемом фильтре

Было

Номер склада в адресе запроса читался нестрого: GET /v1/warehouses/12.5 не отклонялся, а тихо превращался в 12, и чтение, изменение или удаление уходило в другой, реально существующий склад под видом успеха. Так же вели себя 12abc, 1e2, 007, номер с пробелом и номер за пределом точности целых чисел; то же нестрогое чтение применялось к productId и limit. Параметр filter[] на списке складов, на остатках склада и на сводке остатков не читался вовсе: любое условие — и выдуманное, и настоящее имя поля — молча игнорировалось, а в ответ приходил полный список. В описании API семь операций складов и операция агрегации объявляли единственный вариант ответа — успех, а удаление склада было объявлено ответом 200, хотя отвечало пустым 204.

Стало

Номер склада, productId и limit принимаются только в канонической форме — одни цифры, без знака, ведущего нуля, дроби и экспоненты, в пределах точности целых чисел. Всё прочее отклоняется кодом 400 INVALID_PARAMS до обращения к Битрикс24, поэтому опечатка в номере больше не может попасть в чужой объект. Переданный filter[] на трёх перечисленных ручках отклоняется кодом 400 UNSUPPORTED_FILTER с перечнем полученных ключей — вместо полного списка, выглядевшего как отфильтрованный. Запросы с каноническим номером и без filter[] работают как прежде, и их успешный ответ 200 не изменился. В описании API у операций складов и у агрегации появились реальные коды отказа (400, 401, 403, 404, 422), а удаление склада объявлено как 204 — тем ответом, который оно и отдавало.

BC-0908-2: Значение не того типа в поле записи отклоняется вместо тихой порчи данных

Поддержка старого формата до: не предусмотрена

Было

Поле, объявленное в схеме сущности числом, принимало любую строку: {"amount":"сто тысяч"} на POST /v1/deals отвечал 201, а сумма сохранялась нулевой — Битрикс24 приводит нечисловую строку к 0. На обновлении это стирало уже записанную сумму: PATCH с непарсящимся значением отвечал 200 и обнулял поле. Так же вели себя все прочие числовые поля реестра (sort у справочников и товаров), булевы поля принимали любое слово, а строковое поле color у статусов заказа молча обрезалось до ширины колонки базы.

Стало

Такое значение отклоняется 400 с кодом INVALID_PARAMS до обращения к Битрикс24: числовое поле принимает JSON-число или числовую строку с точкой как десятичным разделителем; булево — true/false и распознаваемые строковые формы ("yes"/"no", "y"/"n", "1"/"0", "true"/"false"); строковое поле с объявленным ограничением длины — значение в пределах этой длины. Поля priority и status у задач принимают только значения из своего перечня.

Что делать интеграторам

Присылайте денежные суммы числом или строкой с точкой: 1234.56, не "1234,56" — запятая как десятичный разделитель отклоняется явной ошибкой, а не переводится в точку молча. Проверьте места, где значения собираются из внешних систем строками: раньше такой запрос отвечал успехом, а данные терялись, теперь он честно вернёт ошибку с указанием поля.

FIX-0908-3: Постраничное чтение последних диалогов не теряет и не дублирует записи

Было

GET /v1/chats/recent передавал размер страницы и смещение в Битрикс24 как есть, а там они ограничивают внутреннее соединение таблиц, а не число диалогов. Из-за этого страница отдавала меньше записей, чем запрошено, при обещании следующей страницы; последняя запись страницы приходила с пустыми полями чата и последнего сообщения; соседние окна перекрывались, так что обход смещение += размер выдавал одни диалоги дважды, а другие терял.

Стало

Страница читается с запасом и режется на стороне Вайбкод: окно содержит ровно запрошенное число полностью заполненных записей, недозаполненные строки не отдаются, а признак наличия следующей страницы считается от фактически отданного окна. Окно за концом списка приходит пустым и следующей страницы больше не обещает. Запрос без указания размера страницы отдаёт 50 записей — раньше размер выбирал Битрикс24, теперь он назван явно.

Оговорка про глубокие окна: защита от перекрытия работает, пока смещение плюс размер страницы не превышает 190 — окно вместе с запасом на недозаполненные строки должно помещаться в одну страницу Битрикс24, а это 200 записей. Дальше смещение уходит в Битрикс24, и на очень длинных списках соседние окна снова могут перекрыться — это ограничение самого метода, а не платформы.

Что делать интеграторам

Ничего: запросы прежние, ответ стал честным. Если ваш код обходил дубли вручную — эта защита больше не нужна.

BC-0908-4: Поля, которые Битрикс24 назначает сам, объявлены только для чтения

Поддержка старого формата до: не предусмотрена

Было

Пять полей были объявлены доступными для записи, принимались с ответом 200/201, но Битрикс24 их не сохранял — значение оставалось пустым, и отличить это от успешной записи по ответу было нельзя: признаки происхождения у воронок продаж, модуль-владелец у шаблонов документов, а также пометка проблемной оплаты и её причина у платежей при изменении.

Стало

Эти поля объявлены назначаемыми сервером: попытка записи отклоняется 400 с кодом READONLY_FIELD до обращения к Битрикс24, а описание поля прямо говорит, что значение проставляет платформа. Пометка проблемной оплаты и её причина по-прежнему принимаются при создании платежа — закрыто только их изменение, где значение и терялось. Признаки происхождения воронки раньше вовсе не были объявлены в схеме и подхватывались как записываемые; теперь они объявлены и отклоняются.

Что делать интеграторам

Уберите эти поля из тела запроса на изменение. Модуль шаблона платформа проставляет сама, признаки происхождения воронки не сохраняются вовсе, а пометку проблемной оплаты задавайте при создании платежа.

FIX-0908-5: поиск и сводные отчёты отвечают ошибкой на параметр неверного типа, а не тихой пустотой

Было

В теле POST /v1/{entity}/search поля limit и select принимали значение любого типа. Нечисловой limit (например, "abc") давал HTTP 200 с пустым списком записей и одновременно meta.hasMore: true — клиент, который листает страницы, пока платформа обещает продолжение, уходил в бесконечный цикл, не получая ни одной записи и ни одной ошибки. Значение select, не являющееся строкой или перечнем строк, превращалось в имя поля вида "999" или "[object Object]", уходило в Битрикс24 и возвращалось как HTTP 502 с кодом BITRIX_UNAVAILABLE — платформа сообщала о собственной недоступности там, где ошибка была в запросе клиента.

В теле POST /v1/{entity}/aggregate поле groupBy проверялось, только если пришло строкой или перечнем строк. Число, логическое значение или объект молча отбрасывались: ответ приходил с HTTP 200, без ключа groups и без предупреждения — клиент просил разрез отчёта, а получал общий итог и не мог этого заметить.

В meta.total списочных ответов на страницах за пределами коллекции попадало выдуманное число, растущее вместе со смещением: на портале с пятью хранилищами GET /v1/storages?offset=100 отвечал total: 100, а GET /v1/storages?offset=1000 — total: 1000.

Стало

limit неверного типа отклоняется кодом INVALID_LIMIT, select неверного типа — кодом INVALID_SELECT_TYPE, groupBy неверного типа — кодом INVALID_PARAMS; все три отказа приходят как HTTP 400 с указанием, какого типа значение пришло. Числовой limit работает как раньше, включая строковую запись числа ("50"), а null, пустая строка и пустой перечень по-прежнему означают «параметр не задан» и ошибкой не считаются.

Ключ meta.total не выдаётся, когда страница пуста, а смещение больше нуля: подтвердить счёт в этом случае нечем, а описание поля и без того предупреждает, что на ненулевом смещении ключа может не быть. Там, где страница непуста или смещение нулевое, meta.total приходит как прежде, и ответ по-прежнему HTTP 200.

FIX-0908-6: избранное, комментарии и записи времени задачи отвечают честно

Было

Добавление задачи в избранное и удаление из него отвечали 200 и success: true даже для задачи, которой нет: Битрикс24 подтверждает это действие для любого идентификатора, ничего при этом не сохраняя. Обещанный в описании API код 404 TASK_NOT_FOUND не приходил никогда.

Список комментариев задачи на запросе без фильтра и с сортировкой по идентификатору не учитывал offset — одна и та же страница приходила при любом значении, — а meta считалась по сырым сообщениям чата. На задаче, у которой в срезе только системные уведомления, ответ был data: [] вместе с total: 1 и hasMore: true, поэтому обход while (hasMore) offset += limit не завершался.

Обращение к несуществующему пункту чек-листа или записи учёта времени давало 422 BITRIX_ERROR с текстом внутреннего исключения Битрикс24 (TASKS_ERROR_EXCEPTION_#512; …; 512/TE/ITEM_NOT_FOUND_OR_NOT_ACCESSIBLE), причём на маршруте учёта времени этот текст говорил о чек-листе.

Параметры from и to у GET /v1/task-time не проверялись: ?from=notadate молча отдавал весь диапазон со статусом 200, как будто период не запрашивали.

Стало

Перед добавлением в избранное и удалением из него сервис проверяет, что задача существует и доступна ключу, и отвечает 404 TASK_NOT_FOUND, если нет. На существующей задаче ответ по-прежнему 200. Ограничение скорости и отказ авторизации задачей «не найдено» не подменяются.

Список комментариев учитывает offset на всех путях чтения, а meta.hasMore и meta.total считаются по комментариям, а не по сырым сообщениям чата: срез из одних системных уведомлений дочитывается дальше, и hasMore: false с total: 0 означает, что комментариев нет.

Отсутствующий пункт чек-листа и отсутствующая запись времени отвечают 404 NOT_FOUND — без внутреннего текста Битрикс24 и в терминах запрошенного ресурса. Код объявлен в описании API у всех методов, которые обращаются к одной записи.

Неразбираемое значение from или to отвечает 400 INVALID_PARAMS до обращения к Битрикс24; принимаются YYYY-MM-DD и ISO 8601. Пустое значение по-прежнему означает отсутствие фильтра. В описании API у GET /v1/tasks/{taskId}/comments объявлены реально работающие limit и offset.

BC-0908-7: Конвертация лида проставляет обратные связи и отвечает в общем формате чтения

Поддержка старого формата до: не предусмотрена

Было

POST /v1/leads/{id}/convert отвечал 200, но созданные сделка, контакт и компания не несли ссылки на лид, из которого возникли: запрос сделок с фильтром по лиду ничего не находил, и связать сделку с её источником было нельзя. Сам ответ приходил в сыром формате Битрикс24 — с ключами в верхнем регистре через подчёркивание и числами-строками, — единственный такой ответ среди маршрутов раздела: платформа читала созданные записи устаревшими методами, чьи имена полей не совпадали со схемой, и значения проходили мимо нормализации.

Стало

Каждая созданная запись получает ссылку на исходный лид, а сам лид — ссылки на созданные контакт и компанию (только на те, что действительно созданы). Ответ читается тем же методом, что и обычное чтение сделки, контакта и компании, поэтому его формат совпадает с GET /v1/deals/{id} и соседними — имена полей в общем стиле платформы, числа числами.

Что делать интеграторам

Если разбор ответа конвертации написан под сырой формат Битрикс24 — переведите его на обычный формат чтения, тот же, что у остальных маршрутов раздела. Обратные связи добавляются к данным и ничего не ломают: запрос сделок с фильтром по лиду теперь находит созданную сделку.

BC-0908-8: валюта оплаты, расходящаяся с валютой заказа, больше не подменяется молча

Поддержка старого формата до: не предусмотрена

Было

Создание оплаты принимало любую currency с ответом 201, а Битрикс24 сохранял оплату в валюте её заказа — присланная валюта отбрасывалась без единого замечания. Описание поля при этом обещало свободный выбор со ссылкой на GET /v1/currencies, так что расхождение всплывало только при сверке.

Стало

Если currency передана и не совпадает с валютой заказа orderId, запрос отклоняется 409 с кодом CURRENCY_MISMATCH до обращения к Битрикс24; сообщение называет обе валюты и заказ. Совпадающая валюта принимается по-прежнему, поэтому оплата, прочитанная и отправленная обратно целиком, работает как раньше. То же правило действует в batch-создании оплат. Если валюту заказа прочитать не удалось, запрос пропускается, а не отклоняется.

Что делать интеграторам

Не передавайте currency вовсе, чтобы унаследовать валюту заказа, либо передавайте ровно ту, что стоит у заказа: её показывает GET /v1/orders/:id.

NEW-0908-9: Политика авто-сна standalone-сервера теперь видна в API

POST /v1/infra/servers возвращает сохранённый data.sleepAfterMinutes для каждого успешного создания, reuse и idempotency replay. Это поле ответа, а не новый параметр создания. Успешный standalone-деплой возвращает тот же снимок политики в data.sleepAfterMinutes, а SSE — в sleepAfterMinutes события done. Для обычного standalone-сервера, не защищённого политикой авто-сна агента или бота, при числовом таймауте и отсутствии включённых повторяющихся окон пробуждения в конец warnings[] добавляется предупреждение с префиксом AUTO-SLEEP POLICY: и двумя вариантами: окно пробуждения для задачи, которая успевает завершиться до таймаута, либо осознанный режим «Всегда онлайн» на невытесняемом тарифе. Окно только будит сервер и не удерживает фоновый процесс включённым. Для POST /v1/infra/servers/:id/sleep-now теперь документирован существующий успешный исход slept: false, reason: "WAKE_IMMINENT", когда ближайшее окно пробуждения уже на подходе. Сам ответ HTTP 200 не изменился.

BC-0908-10: запись суммы у смарт-процесса без товарной части больше не отвечает ложным успехом

Поддержка старого формата до: не предусмотрена

Было

POST /v1/items/{entityTypeId} и PATCH /v1/items/{entityTypeId}/{id} принимали opportunity и isManualOpportunity у любого типа смарт-процесса и отвечали 201/200. Если у типа выключена товарная часть (isLinkWithProductsEnabled: false), Битрикс24 эти поля не сохраняет — сумма пропадала молча, а клиент видел успех и уходил.

Стало

Платформа сверяет запрошенное с перечитанной записью — она и раньше перечитывалась перед ответом, так что лишних обращений к Битрикс24 не появилось. Если запрос ЯВНО просил ручной режим (isManualOpportunity: true) и не получил его, приходит 422 AMOUNT_NOT_APPLIED: error.details.unappliedFields перечисляет неприменённые поля, а data несёт фактическое состояние уже созданной или изменённой записи. Сумма, присланная БЕЗ этого флага, не проверяется: по ответу её не отличить от законного пересчёта по товарным позициям, который работает штатно, — поэтому такие запросы отвечают как раньше. Проверка не затрагивает сделки, лиды, счета и коммерческие предложения — у них товарная часть есть всегда.

FIX-0908-11: хост из galaxyId теперь доступен для чтения

Было

Ответ создания galaxy-приложения уже содержал galaxyId, но GET /v1/infra/servers не показывал этот общий хост, а GET /v1/infra/servers/:id отвечал 404 NOT_FOUND тому же API-ключу.

Стало

Оба вызова чтения возвращают ограниченную проекцию хоста с access.via: "galaxy-reference". Она содержит только безопасные поля для чтения. Управление хостом и операции приложения по-прежнему требуют ресурса текущего ключа.

Влияние на интеграторов

Изменять запросы не нужно. Клиенту, который использует появившийся в ответе общий хост, следует проверять access.via: значение galaxy-reference означает доступ только для чтения.

NEW-0908-12: выдача ключа говорит, получил ли он доступ к данным Битрикс24

POST /v1/connect/token теперь отдаёт необязательное поле b24_credentials, а GET /v1/cowork/me — такое же b24Credentials. Оно отвечает на вопрос, может ли выданный ключ читать данные аккаунта Битрикс24: ready: true — права рабочие, ready: false — нет, и тогда приезжает reason из закрытого набора (WEBHOOK_NOT_CONFIGURED, WEBHOOK_MINT_FAILED, WEBHOOK_MINT_REFUSED_BY_PORTAL, B24_MARKET_SUBSCRIPTION_REQUIRED, B24_MARKET_TRIAL_USED, INT_TARIFF_REQUIRED, VIBE_SCOPES_ONLY), а для причин про оплату — ещё и upgradeUrl. Тот же набор причин уже несёт тело 401 TOKEN_MISSING, так что клиент разбирает его одной веткой кода.

Зачем это нужно. Ключ выдаётся и тогда, когда аккаунт отказал платформе подключить его к своим данным: сам ключ работает для вызовов ИИ, а каждый запрос к данным аккаунта отвечает 401 TOKEN_MISSING. Раньше клиент узнавал об этом, только напоровшись, и не мог отличить отсутствующее право от сетевого сбоя. Теперь состояние приезжает вместе с ключом и переспрашивается на GET /v1/cowork/me в любой момент.

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

FIX-0908-13: история рабочих дней возвращается, когда день ещё не закрыт

Было

GET /v1/workday/records отвечал 502 BITRIX_UNAVAILABLE, если в выборку попадала запись незакрытого рабочего дня. У такой записи ещё нет времени окончания, длительности и признака подтверждения, а ручка требовала эти поля заполненными и отбраковывала всю страницу целиком — включая закрытые дни, которые пришли в том же ответе. На практике это означало отказ у любого сотрудника, который сейчас на смене.

Стало

Запись незакрытого дня возвращается как есть, вместе с остальной страницей: ответ остаётся HTTP 200, поля окончания приходят такими, какими их отдал Битрикс24. Проверки сохранены там, где ответ иначе стал бы неверным незаметно: страница по-прежнему отбраковывается, если запись принадлежит другому сотруднику или у неё нет корректного времени начала — от него считается tzOffset. Клиентам ничего менять не нужно.

NEW-0908-14: агенты доступны для привязки к Открытым линиям

Добавлен GET /v1/agents: метод возвращает агентов владельца API-ключа и bitrixBotId, который можно передать в welcomeBotId конфигурации Открытой линии. Поле agentId для Hermes-агента не используется; queue по-прежнему содержит ID операторов-людей.

NEW-0908-15: include отделов в записях сотрудников

Параметр include=department добавляет в запись сотрудника массив _included.departments с карточками всех отделов из departmentId. Для сотрудника без отдела приходит пустой массив. Запросу нужны скоупы user и department.

Затронутые эндпоинты: GET /v1/users, GET /v1/users/:id, POST /v1/users/search

NEW-0908-16: названия и описания всех базовых полей

В ответе GET /v1/{entity}/fields все объявленные базовые поля теперь содержат локализованные название и описание: русские для русскоязычных порталов и английские для международных.

NEW-0908-17: защита от записи поверх более свежих исходников

POST /v1/infra/servers/{id}/sources принимает необязательный заголовок X-Parent-Version: v<N> — «я правил вот эту версию». Если к моменту записи последняя версия уже другая, запись отбивается кодом SOURCE_VERSION_CONFLICT (409), а error.details.latestVersionId называет актуальную версию (null — живых версий нет вовсе). Кривая форма заголовка — 400 INVALID_VERSION_ID. Без заголовка ручка работает как прежде.

«Актуальная» здесь — самая свежая ЖИВАЯ версия, та же, что стоит первой в списке версий и скачивается: названную в отказе версию всегда можно взять и повторить запись. Удалённые версии в счёт не идут, но их номера не переиспользуются. Дедупликация исключением не является: архив, побайтно совпавший с уже существующей версией, тоже получит 409, если верхушка ушла вперёд.

Гарантия действует в одну сторону: ваша запись не ляжет поверх более свежей версии. Обратной половины нет — со стороны браузера исходники сохраняет автосейв деплоя, которому родителя взять неоткуда, поэтому следующая выкладка из браузера всё ещё может лечь сверху. Отказ выносится там же, где создаётся версия, поэтому два запроса с одним родителем оба выиграть не могут, а архив отбитого не остаётся в хранилище.

Ответ POST /v1/cowork/deploy-key получил два поля о переносе на новый ключ: repointTruncated — переехало не всё, остаток уедет на следующем вызове, и applicationSlotBlocked — карточка приложения осталась на прежнем ключе, потому что слот свежего занят другой карточкой. Раньше частичный исход был виден только в журнале платформы.

Затронутые эндпоинты: POST /v1/infra/servers/{id}/sources, POST /v1/cowork/deploy-key

NEW-0908-18: замена ключа приложения одним запросом и маска ключа в карточке

Приложение можно добавить в Коворк/Код из каталога — созданное раньше, на другой машине или до появления вкладки «Код». Сырого ключа у такого приложения нет и восстановить его нельзя: платформа хранит только хеш. Публиковать было нечем.

Появилась ручка POST /v1/cowork/applications/{id}/key, которая приводит ключ приложения в рабочее состояние одним запросом. Слот личного ключа пуст — ключ выдаётся, занят — перевыпускается; что именно произошло, говорит поле issued (minted или rotated). Сырой ключ приходит один раз, как при создании приложения. Заголовок Idempotency-Key обязателен, повтор с тем же ключом отвечает rawApiKey: null и KEY_NOT_REPLAYABLE. Если запрос упал уже ПОСЛЕ выдачи (500 с orphanKeyId в теле), ключ идемпотентности считается израсходованным навсегда: повтор с ним получит IDEMPOTENCY_KEY_ALREADY_USED, а не второй ключ. Прочитайте карточку и повторяйте с НОВЫМ ключом идемпотентности. Отдельный случай — ключ выдан, а карточку прочитать не удалось: ответ всё равно 201 с секретом, application приезжает пустым, и об этом говорит код APPLICATION_CARD_UNAVAILABLE в warningCodes. Карточку возьмите GET /v1/applications/{id}: терять из-за неё единственную копию секрета незачем.

Прежний ключ не отзывается мгновенно: ему ставится срок в сутки, поэтому идущая публикация доработает. Поле previousKey.graceUntil называет момент, когда он перестанет аутентифицироваться. Тело запроса принимает syncServerEnv: true — тогда платформа заменит ключ в переменных окружения задеплоенного сервера и перезапустит приложение, а поле envSync скажет, чем это кончилось: заменено, строки в файле нет, сервер спит, перезапуск не удался и так далее. Без этого приложение продолжит работать со старым ключом и через сутки начнёт получать отказы.

Отказы приходят отдельными кодами, а не общим 400: NOT_APPLICATION_OWNER — приложение чужое; KEY_ROTATE_OAUTH_APP_KEY, KEY_ROTATE_SYSTEM_KEY, KEY_ROTATE_LIVE_OAUTH_GRANT, KEY_ROTATE_NOT_ACTIVE — ключ в слоте заменить нельзя, причина в коде; KEY_ROTATE_KEY_VANISHED — ключ исчез, пока летел запрос; KEY_LIMIT_REACHED — исчерпана квота ключей; APPLICATION_KEY_REPLACE_IN_PROGRESS — замена этого приложения уже идёт. Замена идёт по одной на приложение, с каким бы ключом идемпотентности ни пришёл второй запрос: иначе оба выдали бы по ключу, а слот достался бы тому, кто финишировал вторым. Брошенная упавшим запросом замена перестаёт мешать через две минуты.

На пустой слот выдача проходит общий гейт доступа к платформе, как и остальные двери выпуска ключей: у аккаунта без доступа она отвечает 402. Перевыпуск гейта не проходит намеренно — он переносит права прежней строки и новых не создаёт, и владелец с истекшим доступом обязан уметь закрыть свои дела.

Карточка приложения получила блок key: slot (auth или api — какой ключ показан), present, prefix, suffix, status, expiresAt, lastUsedAt, rotatable и rotateBlockedReason (сработает ли замена и почему нет), affectsShownKey (тронет ли она именно показанный ключ). Маска собирается клиентом из prefix и suffix. Поле rotatable говорит о том, что платформе УЖЕ известно, и гарантией не является ни одной стороной: false — «известен отказ, вот его причина» (rotateBlockedReason, в него входит и право доступа), true — «известных отказов нет». Кнопку по нему рисовать удобно — гасите её при false и показывайте причину, — но тупиком false делать не стоит: отказ мог уже отпасть, а карточка ещё не узнать (про запаздывание и про два гейта вне поля — ниже). Судите по ответу двери, а не только по полю. Блок приходит ВСЕГДА; тому, кому приложение просто открыли, поля приезжают пустыми (present: false), а rotatable — false с причиной NOT_MANAGER: это значит «не положено», а не «ключа нет». На ПУСТОМ слоте, где вызов выдал бы ключ, поле учитывает и гейты выдачи: ISSUANCE_BLOCKED (у аккаунта нет доступа к платформе), INFRA_DISABLED (инфраструктура выключена), KEY_QUOTA_REACHED (квота ключей исчерпана), READONLY_POLICY (аккаунт выдаёт только ключи для чтения). Про сам вызывающий ключ поле говорит теми же кодами, что и дверь, и они повторяются на КАЖДОЙ карточке ответа: INSUFFICIENT_SCOPE (у ключа нет скоупа vibe:cowork) и COWORK_HARNESS_KEY_FORBIDDEN (ключ выписан внешнему агенту). Точность у поля односторонняя намеренно: false на пустом слоте читается по уже известному платформе состоянию доступа — только что изменившийся доступ может ещё показываться как false, пока это состояние не обновится. ДВА гейта Коворк/Код остаются ВНЕ поля и могут отказать при true: платформенный рубильник (503 COWORK_FEATURE_DISABLED) и состояние вашего места Коворк/Код (403 COWORK_NOT_ACTIVATED, о котором и так говорит GET /v1/cowork/state). Ни то ни другое не относится к самой карточке, поэтому по карточкам не размножается — обрабатывайте отказ двери, а не читайте true как гарантию.

Затронутые эндпоинты: POST /v1/cowork/applications/{id}/key, GET /v1/applications, GET /v1/applications/{id}

NEW-0908-19: чтение списка, активного спринта и спринта по ID

API Вайбкод добавляет GET /v1/scrum/sprints для чтения доступных спринтов, GET /v1/scrum/sprints/active для чтения текущего спринта проекта и GET /v1/scrum/sprints/:id для чтения одного спринта по ID. Ответ содержит название, даты, статус и связь с проектом. Если активного спринта нет, соответствующая ручка возвращает data: null.

BC-0908-20: приостановленное место Коворк/Код больше не открывает Битрикс24 в обход подписки Маркетплейса

Поддержка старого формата до: не предусмотрена

Было

Ключ десктопа Коворк/Код и ключ агента открывали REST Битрикс24 на аккаунте без подписки Маркетплейса независимо от того, действует ли само место. Приостановленное место сохраняло эту возможность до тех пор, пока не гасился ключ, — около месяца.

Стало

Возможность привязана к состоянию места. Пропуск действует только у места в состоянии ACTIVE, включая место с назначенной отменой — до конца оплаченного срока. У приостановленного, отменённого и припаркованного места ключ обслуживается общим правилом аккаунта: там, где подписка Маркетплейса куплена, не меняется ничего, а без неё вызовы к Битрикс24 получают тот же отказ аккаунта, что и любое другое приложение.

Что делать интеграторам

Возобновить подписку Коворк/Код либо оформить подписку Маркетплейса на аккаунте. Текущее состояние места приходит в поле subscription.state ответа GET /v1/cowork/state.

NEW-0908-21: каталог инструментов 1С доступен через клиентский API

GET /v1/onec/tools возвращает последний проверенный каталог инструментов портала вместе с признаками loaded, stale, версиями и временем обновления. Каждый инструмент содержит method, title, description и inputSchema. Эндпоинт требует scope vibe:onec, включённый коннектор на портале и сегмент RU, но не требует связи текущего пользователя с пользователем 1С. Пока каталог не загружен, ответ явно содержит loaded: false, пустой массив tools и null в полях ревизии и времени.

POST /v1/onec/tools/{method}/call теперь принимает вложенные JSON-значения в filters: до 200 элементов в массиве, 50 ключей в объекте, 1024 символов в строке, глубины 6 и 2000 узлов на весь объект filters. Ключи __proto__, prototype и constructor запрещены на любом уровне. Поля user, portal и requestId по-прежнему назначает сервер. Вызов остаётся асинхронным: успешный запрос отвечает 202, а результат читается через GET /v1/onec/operations/{operationId}.

FIX-0908-22: реестр исходников больше не обещает дверь, которая ответит вызывающему ключу 403

Было

GET /v1/me/sources считал reachableViaApi по одной достижимости СТРОКИ — «жив ли сервер, не удалено ли приложение» — и не смотрел на КЛЮЧ, которым пришли. А двери, на которые ведут его указатели, скоуплены по ключу и отбивают ключ OAuth-приложения, если сервер принадлежит не ему.

Наблюдаемо это выглядело так. Выдача перечисляет владельцев снимков по ПОЛЬЗОВАТЕЛЮ, а не по ключу, поэтому, придя ключом своего приложения, вы получали свои серверы, заведённые ЛИЧНЫМИ ключами, с reachableViaApi: true и рабочим с виду listEndpoint — а GET /v1/infra/servers/{id}/sources по этому адресу отвечал тому же ключу 403 NOT_AUTHORIZED.

Та же ложь была на строке приложения: автор приложений A и B, пришедший ключом приложения B, видел строку A с рабочим указателем, по которому дверь отвечала 403 SOURCE_APP_ID_MISMATCH.

Стало

reachableViaApi отвечает на один вопрос: пустит ли ВЫЗЫВАЮЩИЙ КЛЮЧ детальный переход. Строка, чью дверь этот ключ не откроет, несёт reachableViaApi: false, а listEndpoint и latestDownloadEndpoint — null. Обе kind-ветки выдачи (server и legacy-app) считают это тем же предикатом, что и сама дверь.

Строка из выдачи НЕ пропадает: снято ложное обещание, а не видимость. Владелец по-прежнему видит, что снимки существуют, и по user.id понимает, чьи они.

Что делать интегратору

Проверяйте reachableViaApi перед тем, как идти по listEndpoint или latestDownloadEndpoint. Оба поля и раньше были объявлены как string | null и уже приходили пустыми у осиротевшего сервера и мягко удалённого приложения, а reachableViaApi: false уже был документированным значением для тех же двух случаев, — поэтому клиент, который соблюдал контракт, менять ничего не обязан. Получили false там, где ждали true, — значит вы пришли ключом приложения: повторите вызов личным ключом (своим или владельца сервера) либо ключом администратора портала. Для строки приложения то же самое: ключ приложения открывает только своё приложение.

Чего это НЕ меняет

Доступ. Ни одна дверь не стала пускать или не пускать кого-то нового: правило доступа осталось прежним, изменилось только то, что выдача перестала обещать доступ, которого нет. Сведение дверей одного сервера к единой модели владения — отдельное ломающее изменение, и оно едет отдельной записью; там же поедут подсказки отказов publish и deploy, потому что у них тот же адрес снимается ВМЕСТЕ с полем hint.requiredAction, а это уже удаление документированного поля.

2026-09-07

FIX-0907-1: погашение купона на своём портале закрыто новым кодом отказа

Было

POST /v1/cowork/coupon/redeem смотрел только на купон, кампанию и место. Кто именно гасит код и чей это портал, ручку не интересовало: сотрудник платформы, имеющий право выдавать купоны, мог выписать код и активировать его на портале, за которым стоит он сам, — тариф уезжал за счёт платформы, а в ответе было обычное HTTP 200.

Стало

Погашение отбивается кодом COUPON_SELF_PORTAL (HTTP 409), если за порталом стоит сотрудник платформы с правом выдавать купоны — сам гасящий либо любой участник портала, владелец API-ключа или владелец сервера на нём. Успешное погашение при этом не меняется: на портале без таких людей ответ остаётся HTTP 200 с тем же телом, и трогать интеграцию не нужно. Попытку в счётчике подбора кодов отказ не расходует — причина в составе портала, а не в том, что человек не угадал код. Код добавлен в таблицу ответов ручки в документации.

NEW-0907-2: самоописание отвечает, остановлена ли инфраструктура за неуплату

Ответы GET /v1/me и GET /v1/cowork/state несут новый блок infraState: frozen — остановлены ли за неуплату серверы, деплой и хранилище, reason — код причины (DEBT либо null), topupUrl — адрес пополнения кабинета либо null.

Блок отвечает на вопрос, который до сих пор было неоткуда прочитать: что именно остановлено у портала с долгом. Строки внутри блока машинные, текст для человека пишет клиент.

⚠️ Смысл поля зависит от того, включена ли на портале развязка «долг за инфраструктуру гасит только инфраструктуру» (FIX в этом же выпуске). Пока не включена, отрицательный баланс отбивает вызовы V1 широко, включая GET /v1/cowork/state; читается в этом режиме только GET /v1/me — он от заморозки освобождён. infraState.frozen там означает «остановлено практически всё». После включения поле означает ровно то, что написано: инфраструктура остановлена, а вызовы в пределах месячной квоты тарифа продолжают работать.

Блок добавлен, существующие поля не изменились.

FIX-0907-3: исправлена пагинация списков действий и роботов бизнес-процессов

Было

Запрос с offset=50 возвращал те же коды, что и запрос с offset=0. Ответ оставался успешным с HTTP 200.

Стало

offset пропускает указанное число кодов в полном списке. meta.total показывает размер полного списка, а meta.hasMore — остались ли коды после текущей страницы. То же поведение действует для list-подвызовов этих сущностей в глобальном пакетном запросе, включая оба канала total. Ответ по-прежнему возвращает HTTP 200, требования к OAuth-авторизации не изменились.

Влияние на интеграторов

Действия не требуются. Интеграции, использующие offset, теперь получают запрошенную страницу вместо повторной первой страницы; формат ответа и требования к авторизации не изменились.

Затронутые эндпоинты: GET /v1/bizproc-activities, GET /v1/bizproc-robots, POST /v1/batch.

FIX-0907-4: долг за инфраструктуру больше не выключает то, что оплачено отдельно

Было

Отрицательный баланс кошелька отбивал 402 ACCOUNT_FROZEN почти на всех вызовах V1 — освобождены были только самоописание, справочник и разговор с поддержкой, — включая и те, что кошелька не касаются: обращения к своему Битрикс24 через прокси, состояние подписки Коворка, список моделей. Долг за виртуалку выключал ИИ, выданный тарифом Битрикс24.

Стало

Заморозка счёта применяется к тому, что платится кошельком: серверы, хранилище, деплой, поиск и research за деньги платформы, расход сверх месячной квоты, выписка ключей развёртывания и создание приложения. Вызов по месячной квоте тарифа, оплаченный период подписки Коворка и REST-прокси к своему порталу проходят при любом состоянии кошелька.

⚠️ Поведение под фича-флагом wallet-debt-scoped-to-infra; при выключенном флаге ответы прежние.

FIX-0907-5: завышенный числовой CRM ID отклоняется до обращения к Битрикс24

Было

Числовые ID CRM проверялись только на форму: строка из цифр без ведущих нулей проходила гард целиком. Это затрагивало обычный :id записи в пути (/v1/deals/{id}, /v1/contacts/{id} и аналогичные сущности), а также положительные entityTypeId smart-processes и соседних CRM-операций. ID длиннее безопасного диапазона целых уезжал в Битрикс24, где мог округлиться до другого значения, а часть методов crm.item отвечала на него сырой ошибкой PHP вместо внятного отказа.

Стало

Такие ID отклоняются с ошибкой невалидных параметров до вызова Битрикс24. Проверка действует для обычных record ID в path и batch, а также для entityTypeId smart-processes, dynamic-параметров и связанных CRM-операций. Граница проходит по безопасному целому: 9007199254740991 по-прежнему принимается, 9007199254740992 и всё, что длиннее, отклоняется. ID 0 остаётся допустимым там, где он был разрешён раньше (например, для основной воронки сделок).

FIX-0907-6: конвертация лида учитывает выбранную воронку сделки

Было

POST /v1/leads/{id}/convert игнорировал параметр categoryId, поэтому новая сделка попадала в воронку по умолчанию.

Стало

Если передать categoryId, новая сделка сразу создаётся в указанной воронке. Значение 0 по-прежнему выбирает воронку по умолчанию.

Целочисленная неотрицательная строка остаётся допустимой для совместимости и приводится к числу. Остальные формы значения по-прежнему игнорируются и оставляют сделку в воронке по умолчанию; null и отсутствие параметра равнозначны.

NEW-0907-7: номер сотрудника Битрикс24 у автора приложения в ответах /v1/apps

Ответы семейства приложений теперь несут два новых поля: authorBitrixUserId — числовой идентификатор сотрудника Битрикс24, создавшего приложение, и authorBitrixUserIdSource — признак того, откуда этот идентификатор взят. authorBitrixUserId — тот же идентификатор, что приходит в поле id ответа GET /v1/users, поэтому он служит ключом связи между двумя ответами: раньше у приложения был только authorId — идентификатор пользователя платформы Вайбкод, — и сопоставить создателя с карточкой сотрудника было нечем.

Значения authorBitrixUserIdSource: member — идентификатор взят из подтверждённого участия автора в этом аккаунте Битрикс24, на такой идентификатор ссылку ставить можно; snapshot — идентификатор взят из значения, снятого при создании приложения, это оценка по возможности, и она может указывать на другого сотрудника, чем текущий автор приложения; null — идентификатор неизвестен, authorBitrixUserId тогда тоже null. Реестру, который не должен ошибаться, ссылку на карточку сотрудника стоит ставить только при member. На аккаунтах в коробке и на аккаунтах с микросервисными учётными данными значение snapshot не выдаётся вовсе: там происхождение снятого значения не связано с подтверждением личности. Подтверждённое участие на таких аккаунтах работает как обычно, поэтому идентификатор там либо member, либо пуст.

Имя и фамилию автора ответы приложений НЕ отдают: это решение принято сознательно и сужает исходную заявку, которая просила ещё и имя. Персональные данные не уезжают в ответ, который читает любой ключ аккаунта, включая ключ, вшитый в код развёрнутого приложения; имя запрашивается по authorBitrixUserId из GET /v1/users, где доступ к нему ограничен разрешением user.

Существующие запросы продолжают работать без изменений: поля добавлены, ни одно не убрано и не переименовано.

Затронутые эндпоинты: GET /v1/apps, GET /v1/apps/:id, POST /v1/apps, PATCH /v1/apps/:id, POST /v1/apps/:id/publish, POST /v1/apps/:id/unpublish, POST /v1/apps/:id/relink-oauth

BC-0907-8: Поле companies удалено из API контактов

Поддержка старого формата до: не предусмотрена

Было

Поле companies публиковалось в GET /v1/contacts/fields и принималось в select для операций чтения контактов. Платформа Битрикс24 не возвращала значение этого поля, поэтому успешный ответ мог не содержать ключ companies.

Стало

Поле companies больше не публикуется и всегда удаляется из ответов контактов. Попытка запросить его в select отклоняется с 400 UNKNOWN_SELECT_FIELD.

Что делать интеграторам

Не запрашивать companies. Для идентификаторов связанных компаний использовать поле companyIds.

Затронутые эндпоинты: GET /v1/contacts, GET /v1/contacts/:id, POST /v1/contacts/search, POST /v1/contacts/aggregate, POST /v1/contacts/batch, GET /v1/contacts/fields.

FIX-0907-9: составная загрузка заменяет крупный объект под прежним ключом

Было

POST /v1/storage/objects/multipart/create возвращал 409 STORAGE_KEY_EXISTS, если живой объект приложения уже занимал тот же key. Поэтому содержимое размером больше 10 МБ нельзя было заменить без смены ключа. Для личного ключа совет использовать multipart вёл к тому же отказу.

Стало

Для уже загруженного объекта приложения запрос возвращает 200 и открывает сессию замены с прежним objectId. Пока POST /v1/storage/objects/multipart/complete не опубликовал новые байты, чтение отдаёт старое содержимое; успешный ответ подтверждает новую версию. Подтверждённый POST /v1/storage/objects/multipart/abort до публикации сохраняет старую версию. Если complete вернул 502 STORAGE_BUCKET_ERROR, публикация могла состояться: по этому ответу нельзя определить текущую версию, сессия остаётся активной и блокирует удаление объекта до разрешения администратором платформы. Видимость объекта не меняется.

POST /v1/storage/objects/multipart/create для уже существующего живого объекта личного ключа возвращает 409 STORAGE_KEY_EXISTS и не открывает сессию загрузки. Сначала удалите объект, затем снова начните составную загрузку под тем же key. Это неатомарная операция: между удалением и успешным завершением новой загрузки объект недоступен.

Влияние на интеграторов

Файлы приложения больше 10 МБ можно обновлять составной загрузкой под прежним key. Операции multipart create, complete и abort могут вернуть повторяемый 409 STORAGE_KEY_CONFLICT, когда другой запрос одновременно изменяет тот же key или состояние сессии изменилось. Для create и complete повторяйте ту же операцию. 409 на abort во время финализации требует повторить complete с теми же parts; если complete вернул 502 STORAGE_BUCKET_ERROR, не делайте вывод о текущей версии и обратитесь к администратору платформы. Если abort вернул 502 STORAGE_BUCKET_ERROR, повторяйте abort, пока освобождение сессии не будет подтверждено. DELETE /v1/storage/objects/{key} во время активной multipart-сессии возвращает 409 STORAGE_MULTIPART_IN_PROGRESS: сначала завершите или отмените сессию, учитывая особый случай финализации выше.

FIX-0907-10: перевод сделки/лида на несуществующую стадию теперь отвечает ошибкой, а не тихим успехом

Было

POST /v1/deals/{id}/move, POST /v1/leads/{id}/move, PATCH /v1/deals/{id} и PATCH /v1/leads/{id} отвечали 200 success:true и возвращали полный объект сделки/лида даже тогда, когда переданная stageId (или categoryId у сделки) не существовала в справочнике — Битрикс24 тихо игнорировал значение, стадия оставалась прежней, а вызывающий не мог отличить реальный перевод от отклонённого без ручной сверки поля data.stageId.

Стало

Ответ по-прежнему 200, если переданная стадия или воронка существует и применяется — поведение для валидных значений не изменилось. Когда переданное значение не применилось (Битрикс24 принял вызов, но фактическая стадия осталась прежней), ответ — 422 STAGE_NOT_APPLIED с деталями в error.message: какое значение запросили и какое оказалось на самом деле после перечитывания записи.

FIX-0907-11: Подсказка привязки встройки говорит «trial», а не «demo»

Было

GET /v1/me отдавал в подсказке возможности apps.bindPlacements старый термин: «…403 B24_MARKET_SUBSCRIPTION_REQUIRED (or B24_MARKET_TRIAL_USED if the demo was already used)». Запись FIX-0904-7 уже перевела латинскую терминологию на слово trial, поэтому эта строка осталась последней, расходившейся с кодом отказа, который она сама и описывает. Тот же термин держался в двух статьях документации — про запуск пробного периода Маркетплейса и про имя тарифа в GET /v1/me.

Стало

Подсказка читается как «…if the trial was already used», документация переведена на тот же термин. Коды отказов, статусы и поля ответа не изменились, успешный ответ остался успешным. Строка не локализована и отдаётся одинаково на обеих установках.

Влияние на интеграторов

На логику — никакого. Клиенту, который сравнивает эту подсказку по подстроке, стоит перепроверить сравнение.

FIX-0907-12: спека объявляет все ответы переключения режима сервера

Было

Публичная спека GET /v1/openapi.json объявляла у PATCH /v1/infra/servers/:id/mode только ответы 200, 400, 403 и 404. Клиент, собранный по спеке, считал остальные исходы невозможными, хотя ручка их отдавала: отказ по исчерпанному балансу, отказ роли сервера, конфликт параллельного запроса и два отказа применения сетевой политики.

Стало

Спека дополнена ответами, которые ручка отдаёт: 401, 402 ACCOUNT_FROZEN и OPEN_MODE_REQUIRES_COMMERCIAL, 409 SERVER_NOT_RUNNING, AGENT_NOT_CONNECTED, MODE_SWITCH_SEALED_ROLE и CONFLICT, 502 IPTABLES_FAILED, PROVIDER_NOT_CONFIGURED, GATEWAY_UNREACHABLE и TUNNEL_NOT_FOUND, 503 SECURITY_GROUP_ATTACH_FAILED и отказ, код которого начинается с GATEWAY_TIMEOUT. Описание 400 дополнено кодами SAME_MODE, NO_SUBDOMAIN и MODE_SWITCH_STANDALONE_ONLY. Те же коды заведены в таблицу ошибок страницы: до этой правки она не называла ни одного из трёх отказов состояния — сервер не запущен, агент не на связи, у сервера нет субдомена. Из таблицы ошибок на странице ручки снят код PROVIDER_ERROR: переключение режима его не отдаёт. Статус 429 объявления не получил намеренно — своего лимитера у ручки нет, а общий пограничный лимит платформы описан на странице лимитов и в спеке по маршрутам не объявляется. Поведение ручки не изменилось: успешный ответ остаётся HTTP 200 с тем же телом, менять запросы не нужно, и правка касается только объявления и документации.

NEW-0907-13: `/v1/cowork/me` и `/v1/cowork/state` называют портал, к которому привязан ключ

Обе ручки самоописания Коворк/Код получили новое поле portal:

JSON
{
  "portal": { "id": "8f3c…", "domain": "acme.bitrix24.ru" },
  "tier": "PRO",
  "state": "ACTIVE"
}

Поле отвечает на вопрос, о каком портале говорят остальные поля тела. Сиденье Коворк/Код определено на паре «владелец ключа + портал», а ключ привязан к одному порталу навсегда, поэтому у человека с несколькими порталами приложение докладывает про тариф и квоту одного портала, а браузер — другого. Раньше домен приезжал ровно один раз, при обмене кода на ключ (POST /v1/connect/token), и переспросить его было негде.

portal.id присутствует всегда — он взят с самого ключа. portal.domain равен null только в вырожденном случае, когда строки портала уже нет. Остальные поля обоих ответов не изменились, и запросы, написанные до появления поля, работают без правок.

BC-0907-14: частичный отказ конвертации лида отвечает 422, а не 200

Поддержка старого формата до: не предусмотрена

Было

POST /v1/leads/:id/convert создаёт сделку, контакт и компанию отдельными записями одна за другой. Когда часть из них не создавалась, ответ всё равно приходил со статусом 200: поле success было false, а error, code и details лежали на верхнем уровне тела, причём error был строкой, а не объектом. Клиент, который ветвится по HTTP-статусу или читает error.code, принимал частично выполненную запись в CRM за успех.

Стало

Частичный отказ отвечает 422 с кодом CONVERSION_FAILED в обычном конверте ошибок: error — объект с полями code и message. Исход каждой запрошенной операции переехал в error.details — по ключу на сделку, контакт и компанию, с признаком success и либо идентификатором созданной записи, либо текстом отказа Битрикс24. Верхнеуровневых error-строки и code в ответе больше нет. Лид, который не удалось прочитать, отвечает 404 с кодом ENTITY_NOT_FOUND в том же конверте. Успешная конвертация по-прежнему отвечает 200 с полем data.

Что делать

Ветвиться по HTTP-статусу и по error.code, а не по полю success и не по верхнеуровневому code; исходы отдельных записей читать из error.details, а не из details. Уже созданные записи по-прежнему не откатываются, а статус лида не меняется, поэтому перед повторным вызовом сверяйте, что уже появилось в CRM: повтор создаёт второй комплект записей. Окна поддержки прежнего ответа со статусом 200 нет: он сообщал об успехе там, где часть записей в CRM не создалась, поэтому сохранять его означало бы и дальше выдавать отказ за успех.

FIX-0907-15: Проверка корневого типа тела на создании, обновлении, batch и поиске сущностей

Было

Тело запроса — JSON-литерал null — роняло POST /v1/{entity}, PATCH /v1/{entity}/{id} и POST /v1/{entity}/batch в 500 INTERNAL_ERROR; этот статус не был объявлен ни для одного из трёх маршрутов. JSON-массив или JSON-строка в теле POST /v1/{entity} создавали настоящую запись с пустыми или автосгенерированными полями и отвечали 201, как будто запрос был корректным. На PATCH /v1/{entity}/{id} JSON-строка проходила проверку пустого тела и отвечала 200, фактически ничего не изменив. На POST /v1/{entity}/search тело-массив отвечало 400 INVALID_FILTER_SHAPE с сообщением, неверно называющим присланный тип данных («получена function» вместо «массив»); тело-скаляр (число, строка, boolean) проходило как «фильтр не указан» и отвечало 200 с неотфильтрованным списком записей.

Стало

Корректное тело (JSON-объект; для /search — включая пустой {}, это законный запрос «без фильтра») обрабатывается без изменений. Ответ по-прежнему 201 при создании и по-прежнему 200 при обновлении, batch и поиске — ничего из этого не поменялось. Тело, чей корневой JSON-тип не является объектом (null, массив, строка, число, boolean), теперь на любом из четырёх маршрутов отвечает 400, а сама сущность не создаётся и не изменяется: EMPTY_CREATE_BODY на создании, EMPTY_UPDATE_BODY на обновлении, INVALID_BATCH_ACTION на пакетном маршруте сущности, INVALID_REQUEST на поиске. Сообщение об ошибке поиска теперь называет фактически присланный тип данных, а не внутренний артефакт реализации.

FIX-0907-16: отсутствующая запись отвечает 404 на складах, каталогах, статусах заказа, позициях корзины и смарт-процессах

Было

Один и тот же сценарий — «записи с таким id нет» — отвечал в пределах одного раздела разными кодами. GET /v1/order-statuses/{id} и GET /v1/basket-items/{id} отдавали 422 BITRIX_ERROR, хотя соседние заказы, счета, платежи и предложения на том же шаге отдавали 404. Так же вели себя GET /v1/catalogs/{id}, GET /v1/warehouses/{id} и GET карточек свойств товара, при том что товары и цены каталога уже отвечали 404. Проверка «удалилось ли» по коду ответа на этих путях молча не срабатывала.

Отдельно на портале с нерусским языком интерфейса PATCH и DELETE для /v1/smart-processes/{id} на несуществующий entityTypeId отвечали 400 INTERNAL_ERROR — код, который для DELETE даже не объявлен в спецификации. На русском портале то же действие честно отвечало 404 SMART_PROCESS_NOT_FOUND, а GET был верен на любом языке.

Стало

Отсутствующая запись отвечает 404 на всех перечисленных путях. GET, PATCH и DELETE для складов, каталогов, статусов заказа и позиций корзины отдают 404 ENTITY_NOT_FOUND; текст портала в поле message не меняется, меняется только классификация ответа. Тот же 404 приходит и на страницах свойств товара и их значений.

PATCH, DELETE и POST /v1/smart-processes/batch отвечают 404 SMART_PROCESS_NOT_FOUND независимо от языка портала — так же, как это давно делал GET. Клиент может опираться на один код существования записи во всём API Вайбкод, а не заводить исключения по разделам и по языку.

2026-09-06

FIX-0906-1: бесплатный тариф Битрикс24 при выписке ключа отвечает терминально, а не «повторите позже»

Было

Облачный аккаунт на бесплатном тарифе Битрикс24 получал на выписку ключа 502 CONNECTOR_REST_UNAVAILABLE с details.retryable: true, если по данным аккаунта его доступ к Маркетплейсу действовал. Ответ обещал, что поможет повтор. Битрикс24 на бесплатном тарифе закрывает REST целиком, поэтому повтор не помогал никогда.

Аккаунт без подписки Маркетплейса на том же тарифе получал 403 B24_MARKET_SUBSCRIPTION_REQUIRED, а с уже израсходованным пробным периодом — 403 B24_MARKET_TRIAL_USED. Оба ответа продавали средство, которое бесплатный тариф не снимает.

Признак activation.marketTrial.available в GET /v1/cowork/state отдавал true такому аккаунту, и POST /v1/cowork/activate-market-trial расходовал его одноразовый пробный период Маркетплейса впустую.

Стало

Тот же случай отвечает терминальным отказом, который называет средство — платный тариф Битрикс24: B24_PAID_TARIFF_REQUIRED (402) с полем userMessage. Код не новый, изменился набор состояний, при которых он приходит: раньше только «доступ оплачен, но не действует», теперь — любое состояние доступа на прочитанном бесплатном тарифе. Повтор без смены тарифа вернёт то же самое. В этом случае коды 403 B24_MARKET_SUBSCRIPTION_REQUIRED и B24_MARKET_TRIAL_USED уже не приходят — их клетки перехватывает тарифный отказ. При других отказах выписки они по-прежнему возможны, в том числе на бесплатном тарифе.

Всё сказанное про подписку относится к аккаунтам, где она продаётся, — Россия и Беларусь. В Казахстане и Узбекистане подписки как продукта нет: там та же стена отдаёт свой терминальный отказ — 403 INT_TARIFF_REQUIRED, — а подписочные коды к таким аккаунтам не приходили и раньше.

activation.marketTrial.available для такого аккаунта теперь false с причиной not_supported, а POST /v1/cowork/activate-market-trial и POST /v1/portals/{id}/activate-market-trial отвечают отказом, не расходуя пробный период. Аккаунт на тарифе «Демо» Битрикс24 не затронут: этот тариф REST не закрывает, и ответ у него прежний. Аккаунт, тариф которого прочитать не удалось, ведёт себя как раньше.

BC-0906-2: PATCH /v1/infra/servers/:id/mode отклоняет открытие для серверов с запечатанной ролью

Поддержка старого формата до: не предусмотрена

Было

Для сервера, которому по его роли (участник пула, служебная машина) полагается закрытая сетевая политика, PATCH /v1/infra/servers/:id/mode с mode: "OPEN" отвечал 200: сервер действительно переходил в открытый режим, а выданный SSH-пароль работал.

Стало

Такой запрос отклоняется с 409 MODE_SWITCH_SEALED_ROLE; режим сервера не меняется, пароль не выдаётся. Отказ применяется там, где сетевая политика сервера поддержана дополнительным слоем для этого сервера; там, где такой поддержки нет, поведение не изменилось.

Что делать интеграторам

Клиенты, которые ожидали 200 для серверов с запечатанной ролью, обязаны обрабатывать 409 MODE_SWITCH_SEALED_ROLE как терминальный отказ: открытый режим для такого сервера недоступен, повторять запрос бессмысленно. Для остальных серверов (не запечатанной роли) 200 и выдача пароля не изменились.

NEW-0906-3: PATCH /v1/infra/servers/:id/mode может отдать отказ переключения сетевой политики

PATCH /v1/infra/servers/:id/mode закрывает сетевую политику сервера ещё одним слоем — снаружи гостевой машины, поверх уже существующей защиты внутри неё. Если переключить сетевую политику сервера не удалось, ручка отвечает 503 SECURITY_GROUP_ATTACH_FAILED; режим сервера при этом не меняется, и повтор запроса имеет смысл. Отказ возможен только там, где этот дополнительный слой поддержан для конкретного сервера; там, где поддержки нет, поведение ручки не изменилось.

FIX-0906-4: PATCH /v1/infra/servers/:id/mode закрывает фаервол сервера, если связь с ним оборвалась посреди переключения

Было

PATCH /v1/infra/servers/:id/mode выполняет переключение режима командами на самом сервере. Если связь с сервером терялась посреди этой последовательности — таймаут, обрыв туннеля, недоступный агент, — ручка отвечала ошибкой, но сервер мог остаться с уже открытым фаерволом при неизменённом режиме BLACKHOLE: команда открытия успевала исполниться, а закрыть её обратно было некому. Ошибка при этом сообщала, что переключение отменено.

Стало

Такой обрыв теперь запускает закрытие фаервола обратно до того, как ошибка уедет клиенту: раньше на этом пути не делалось ничего, и открытый сервер при режиме BLACKHOLE оставался так до следующего вмешательства.

Закрытие идёт по тому же каналу связи, который только что оборвался, поэтому гарантией оно не является: если сервер недоступен полностью, оно тоже не доедет, и расхождение сохранится. Ответ ручки об этом не сообщает — исход попытки виден в журнале платформы.

Один случай остаётся исключением и описан здесь намеренно: если сервер уже успел закрыться, а сорвалась запись режима, фаервол НЕ открывается обратно — снимать выполненное закрытие из-за сбоя записи было бы опаснее. Сервер тогда закрыт, а режим по нему отдаётся как OPEN.

Влияние на интеграторов

Коды ответов не изменились, и повтор запроса по-прежнему имеет смысл. В исключении, описанном выше, повтор с mode: "OPEN" отклоняется как 400 SAME_MODE — режим по строке уже OPEN; доступ возвращается переключением в BLACKHOLE и обратно в OPEN.

BC-0906-5: интеграционный ключ платформы умирает вместе с полномочиями выпустившего

Поддержка старого формата до: 31.12.2026

Было

Ключ /v1/platform/* жил, пока его не отозвали руками. Снятие с сотрудника платформенной ступени, его блокировка и приём заявки на стирание аккаунта ключ не трогали: канал выгрузки выручки и купонные партии продолжали работать от имени человека, которого сама платформа уже не пускает.

Стало

Ключ закрывается автоматически в тот же момент, когда его создатель теряет платформенные полномочия — снятие ступени, блокировка, приём заявки на стирание. Запросы по такому ключу получают 401. Остальные платформенные администраторы получают письмо с перечнем закрытых ключей.

Если канал нужен дальше, выпустите ключ заново от действующего сотрудника: POST /api/platform/integration-keys. Проверять состояние канала заранее нечем — о закрытии сообщает письмо, поэтому интеграции стоит держать обработку 401 как сигнал «нужен новый ключ», а не как транзиентную ошибку.

2026-09-05

FIX-0905-1: чат по ключу Cowork/Code работает при выключенном продукте

Было

POST /v1/chat/completions возвращал HTTP 503 с кодом cowork_feature_disabled в нижнем регистре в теле ответа для уже выданного ключа с правом vibe:cowork, когда Cowork/Code был выключен на уровне платформы.

Стало

Вызов модели продолжает работать через подписку Cowork/Code и по-прежнему проверяет активность подписки, доступность модели и квоту. Выключатель продолжает закрывать интерфейсы и операции продукта Cowork/Code. Интеграторам ничего менять не нужно.

FIX-0905-2: отказ на выписке ключа отличает непрочитанный тариф от бесплатного

Было

Аккаунт на международном сегменте, тариф которого Вайбкод прочитать не смог, получал на выписке ключа 402 с кодом INT_VIBE_PLUS_REQUIRED (или INT_TARIFF_REQUIRED) и советом подключить платный тариф. Совет мог быть и неверным — аккаунт уже мог платить, — потому что решение принималось по пустому коду тарифа, а пустой он и у прочитанного бесплатного аккаунта, и у аккаунта, чью лицензию запросить не удалось.

Стало

Эти два состояния разведены. Если кода тарифа нет И последняя проба лицензии закончилась неудачей, ответ теперь 403 с кодом PORTAL_TARIFF_UNREADABLE: тариф не прочитан, покупка его не изменит. Поле details.requiredTariffs пустое — ни один тариф этот отказ не снимает, — а details.upgradeUrl и alternatives[0].url ведут в поддержку. Прежние коды остаются за своим состоянием: аккаунт с прочитанным бесплатным тарифом по-прежнему получает 402 и прежний текст.

2026-09-04

NEW-0904-1: асинхронные вызовы инструментов 1С и статус операции

Появились POST /v1/onec/tools/{method}/call и GET /v1/onec/operations/{operationId}. Вызов инструмента создаёт долгоживущую операцию и отвечает 202 с operationId, status и expiresAt, а заголовок Location ведёт на адрес статуса. Статус проходит значения pending, running, succeeded, failed, cancelled, expired; у успешной операции есть result, у неуспешной — безопасный error. Результат хранится 24 часа, затем адрес статуса отвечает 410 ONEC_OPERATION_GONE. Операция доступна только исходному порталу, пользователю и API-ключу. На порталах без включённого коннектора 1С оба адреса отвечают 404.

В этом выпуске поверхность объявлена, но недоступна: механизма связи пользователя с пользователем 1С ещё нет, поэтому любой вызов инструмента отвечает 403 ONEC_USER_NOT_MAPPED, операция не создаётся, и описанный цикл 202 → опрос статуса пройти нельзя. Он заработает с выпуском связывания пользователей; до него интегрировать вызов инструмента не нужно.

NEW-0904-2: признак «Поддерживает BitrixMobile» при регистрации приложения

POST /v1/apps принимает необязательное поле mobile: boolean, по умолчанию false. При true платформа сообщает Битрикс24 признак «Поддерживает BitrixMobile» в момент регистрации приложения на аккаунте, и приложение становится видно в мобильном клиенте. Поле mobile появилось и в объекте приложения — в ответах создания, данных приложения, списка и перепривязки. У приложений, созданных раньше, значение false. Признак задаётся только при создании — PATCH /v1/apps/:id поле mobile не принимает. Если аккаунт регистрирует приложение способом, который признак не передаёт, приложение создаётся с mobile: false, а в warnings ответа создания приходит строка, начинающаяся с mobile:. Прежние запросы без mobile работают как раньше. Подробнее — Создать приложение.

BC-0904-3: код отказа для коробок переименован в SELFHOSTED_NOT_AVAILABLE

Поддержка старого формата до: не предусмотрена

Было

Отказ коробочному Битрикс24 на международной установке приходил кодом INT_BOX_PARTNER_REQUIRED. Текст отказа объяснял, что доступ даёт партнёрская лицензия, а по ключу партнёрская пометка не подтверждена, и предлагал переподключить модуль на портале. Кнопка вела в документацию по подключению коробки: details.upgradeUrl и alternatives[0].url содержали адрес страницы /docs/connect-self-hosted-bitrix24.

Стало

Тот же отказ приходит кодом SELFHOSTED_NOT_AVAILABLE. Клиент, разбирающий ответ по коду, обязан заменить строку — старый код больше не отдаётся ни на одной поверхности.

Остальное в ответе не изменилось: статус по-прежнему HTTP 402, поле details.requiredTariffs по-прежнему пустое (отказ не снимается покупкой тарифа), набор alternatives тот же и в том же порядке.

Изменились текст и адрес кнопки. Текст больше не называет причину отказа и не просит ничего делать на портале: коробка пока не поддерживается, доступ открывается постепенно, серверы и данные портала сохраняются. details.upgradeUrl и alternatives[0].url теперь содержат mailto: адреса поддержки — единственный адрес, по которому вопрос решается.

FIX-0904-4: подсказки о trial-развёртывании учитывают пригодность galaxy-хоста

Было

GET /v1/me обещал one-shot развёртывание при наличии любого запущенного или спящего galaxy-хоста. Если хост не мог принять приложение, POST /v1/infra/servers отказывал, а подсказка предлагала two-step создание, которое затем упиралось в лимит trial-портала.

Стало

GET /v1/me показывает one-shot путь только когда известный хост выглядит пригодным для размещения, и явно отмечает прогноз как рекомендательный. Авторитетным остаётся POST /v1/infra/servers; если непригодный хост занимает trial-лимит и контроль ограничений включён, ответ больше не советует заведомо недоступное two-step создание.

Влияние на интеграторов

Перед one-shot созданием проверяйте deployment.galaxyApp, а после отказа следуйте error.hint. Для двухшагового пути дополнительно проверяйте capabilities.servers.create.available и выбирайте план из capabilities.servers.create.limits.allowedPlans того же ответа.

FIX-0904-5: карточка каталога не публикуется без привязки к порталу

Было

Публикация могла завершиться со значением SYNCED в b24CatalogSync.status, хотя карточка каталога оставалась без привязки к отправителю. В таком состоянии уведомление о новой версии приложения не приходило в чат, а причина не отображалась.

Стало

При вызове POST /v1/infra/servers/:id/b24-catalog/publish такая карточка больше не создаётся. Для спаренного портала публикация завершается только после подтверждения привязки карточки к отправителю. Пока подтверждение не получено, b24CatalogSync.status остаётся не SYNCED, pendingOp остаётся ADD, а lastError называет причину. Ответ по-прежнему HTTP 200, и успешная публикация возвращает то же самое, что и раньше.

Влияние на интеграторов

Менять запросы не требуется. Если интеграция отслеживает асинхронный статус, сочетание pendingOp=ADD со статусом, отличным от SYNCED, означает, что карточка ещё не опубликована. Следует дождаться изменения статуса или показать пользователю значение lastError.

FIX-0904-6: expiresAt предподписанных ссылок равен сроку их подписи

Было

POST /v1/storage/objects/multipart/create считал parts[].expiresAt от запрошенного срока — 24 часа для каждой части, а GET /v1/apps/{id}/sources/{versionId}/download и GET /v1/infra/servers/{id}/sources/{versionId}/download — от фиксированных 30 минут; ссылка из перенаправления GET /v1/storage/objects/{key} документировалась как действующая 10 минут. Отключённый сейчас POST /v1/storage/objects отвечает 503 STORAGE_PRESIGNED_UPLOAD_DISABLED и считал expiresAt так же. Подпись ссылки могла истечь раньше, и хранилище отвечало 403, пока expiresAt ещё оставался в будущем.

Стало

expiresAt в этих ответах берётся из подписи самой ссылки и равен её сроку. Он может быть короче 24 часов сессии составной загрузки и короче 30 минут для ссылок на скачивание; ссылка из перенаправления тоже может истечь раньше 10 минут. Для отключённого POST /v1/storage/objects ничего не меняется: он по-прежнему отвечает 503, а после включения его expiresAt тоже будет браться из подписи. Ответ остаётся HTTP 200, формат полей не меняется.

Влияние на интеграторов

Планируйте отправку и скачивание байтов по expiresAt из ответа и начинайте как можно раньше, а не по сроку сессии uploadId и не по документированным 30 минутам. Клиенты, которые уже ориентировались на expiresAt, ничего не меняют.

FIX-0904-7: латинская локаль называет пробный доступ Битрикс24 словом trial

Было

В латинских локалях пробный доступ Битрикс24 назывался demo, хотя сам Битрикс24 на международной установке называет его trial. Расхождение доходило и до контракта: GET /v1/me отдавал в data.tariff.name значение Demo period, а отказ BY_PAID_ONLY — текст Paid or demo Bitrix24 plan required (Belarus). Внутри одного сценария активации соседние сообщения расходились между собой: одно говорило trial, другое demo про тот же самый доступ.

Стало

Значение тарифа и текст отказа в латинских локалях сведены к слову trial. GET /v1/me отдаёт для этого тарифа Trial period, отказ BY_PAID_ONLY — Paid or trial Bitrix24 plan required (Belarus). Клиент, который сравнивает data.tariff.name или текст отказа со строкой, должен пересмотреть сравнение: сами коды, статусы и поля ответа не изменились, и успешный ответ остаётся успешным. Русская локаль по-прежнему говорит «демо».

Охват этой записи — контракт: значение тарифа и текст отказа. Подсказка возможности apps.bindPlacements того же эндпоинта и статьи документации на момент публикации ещё говорили demo, их перевод вышел позже — запись FIX-0907-11.

FIX-0904-8: батч-подзапрос currencies больше не отдаёт meta.total 0 при непустом data

Было

POST /v1/batch со списочным подзапросом currencies публиковал data.meta.<id>.total: 0 и hasMore: false, хотя в data.results.<id> лежали записи. Метод Битрикс24 crm.currency.list отдаёт конвертный total: 0 рядом с непустым результатом, и на батч-двери этот литеральный ноль принимался за размер коллекции. Клиент, который листает по hasMore, при limit меньше справочника читал первую порцию и считал набор исчерпанным — хвост терялся.

Стало

На считанном подзапросе ноль из конверта больше не побеждает посчитанный размер набора: data.meta.<id>.total и data.totals.<id> показывают размер до клиентского среза, а hasMore считается как offset + returned < total и остаётся true, пока хвост есть. Правило совпало с одиночными эндпоинтами, где эта особенность метода нормализовалась и раньше. Непосчитанный подзапрос (params.withTotal: false, отрицательный params.start) по-прежнему не получает total, а честный пустой справочник по-прежнему отдаёт total: 0. Ответ остаётся HTTP 200.

NEW-0904-9: партнёрское приложение отзывает свой ключ само

Добавлена ручка POST /v1/connect/revoke по RFC 7009: приложение передаёт client_id, client_secret (публичные клиенты — без него) и token и гасит ровно тот ключ, который предъявило. Место в лимите ключей пользователя на портале освобождается сразу, запросы с ключом начинают отвечать 401 KEY_INACTIVE.

Ответ 200 приходит и когда гасить было нечего — на неизвестный токен, на чужой ключ и на уже отозванный, поэтому вызов идемпотентен и по нему нельзя выяснять перебором, живы ли чужие ключи. Отсутствие client_id или token даёт 400 invalid_request, неизвестный клиент или неверный секрет — 401 invalid_client. Ключ, выданный до деактивации клиента, отозвать тоже можно.

Ручка объявлена в документе обнаружения /.well-known/oauth-authorization-server полями revocation_endpoint и revocation_endpoint_auth_methods_supported. До неё отозвать выданный приложению ключ могли только пользователь в разделе «Подключённые приложения», владелец приложения полным удалением клиента и администратор платформы; все три способа работают как раньше.

BC-0904-10: смещение в справочниках статусов и воронок работает на любой глубине

Поддержка старого формата до: не предусмотрена

Было

GET /v1/statuses возвращал одни и те же записи при offset=0, 50, 100 и 300. Метод Битрикс24, на котором работает справочник, отдаёт весь набор одним ответом и навигацию не применяет, а обёртка резала этот ответ с начала — клиент получал первую страницу под видом пятидесятой. При смещении, не кратном 50, окно повторялось с периодом 50: offset=130 отдавал те же записи, что offset=30.

Обход по смещению завершался, но собирал дубли и не доходил до конца справочника: из 267 записей через список было достижимо около 50, а meta.total при этом называл честные 267. Ошибки не было ни на одном шаге — все ответы приходили с кодом 200. То же поведение было у GET /v1/deal-categories и у списочных подзапросов в POST /v1/batch.

При этом списочный подзапрос батча БЕЗ явного limit вёл себя иначе, чем тот же список за одиночным запросом: одиночный GET /v1/statuses отдавал 50 записей по умолчанию, а подзапрос батча — весь справочник целиком, все 267 записей одним ответом.

Стало

Окно [offset, offset + limit) вычисляется на стороне платформы Вайбкод по полному набору, в том порядке, который вернуло ядро. offset=50&limit=5 отдаёт 51-ю…55-ю записи, limit больше 50 не обрезает хвост, meta.total равен размеру набора с учётом фильтра, а meta.hasMore становится false на последней странице. Обход по смещению завершается и покрывает каждую запись ровно один раз. Ответ по-прежнему приходит с кодом 200.

Списочный подзапрос батча теперь подчиняется тому же правилу по умолчанию, что и одиночный запрос: без явного limit он отдаёт первые 50 записей и hasMore: true, а не весь справочник.

Сортировка и фильтрация по-прежнему выполняются на стороне Битрикс24, поэтому параметр sort работает как раньше. То же поведение применяется на POST /v1/statuses/search и в списочных подзапросах POST /v1/batch.

Влияние на интеграторов

Циклы, которые раньше читали одни и те же записи и не доходили до конца справочника, начинают отдавать полную выборку — для них менять код не нужно.

Менять код нужно в одном случае: подзапрос POST /v1/batch со списком статусов или воронок, у которого limit не задан явно. Раньше он отдавал весь справочник, теперь — первые 50 записей. Ошибки при этом не будет, ответ придёт с кодом 200 и с hasMore: true, поэтому недостающие записи потеряются молча, если их не запросить.

Что делать: либо задать limit явно, либо читать справочник постранично, увеличивая offset на размер страницы, пока meta.hasMore не станет false. Второй вариант предпочтителен — он не зависит от размера справочника и работает на любом портале.

BC-0904-11: История рабочего дня возвращает смещение часового пояса

Поддержка старого формата до: не предусмотрена

Было

GET /v1/workday/records требовал только timeman. В записи не было обязательного tzOffset, поэтому клиент не мог надёжно получить местное время сотрудника.

Стало

Ручка требует timeman и один из скоупов user_brief, user_basic или user. Каждая запись содержит обязательный tzOffset — смещение в секундах восточнее UTC, вычисленное для момента startTime по историческим правилам текущей IANA-зоны TIME_ZONE из профиля сотрудника. Значение не доказывает, что эта зона была назначена сотруднику при создании записи. Строки startTime и endTime не изменяются. Если текущую зону профиля или смещение по ней нельзя достоверно определить, непустая страница возвращает 502 BITRIX_UNAVAILABLE; смену зоны после создания записи API обнаружить не может и 502 в этом случае не обещает.

Что делать интеграторам

Перевыпустите существующие ключи с timeman и одним user-family скоупом, затем обрабатывайте обязательный tzOffset в ответе GET /v1/workday/records.

NEW-0904-12: асинхронные вызовы учётной системы под связанным пользователем

POST /v1/onec/tools/{method}/call ставит операцию в очередь под активной связью текущего пользователя и требует scope vibe:onec. Тело задаёт только метод, колонки, фильтры и пагинацию: идентификатор и имя пользователя сервер берёт из настроек портала, без клиентского override и без fallback-учётной записи. Ответ 202 содержит operationId, а состояние и результат доступны через GET /v1/onec/operations/{operationId} тому же порталу, пользователю и ключу.

BC-0904-13: Чтение данных по сущностям получило лимит запросов на портал

Поддержка старого формата до: не предусмотрена

Было

Чтение данных по сущностям — список (GET /v1/deals и аналогичные для всех сущностей), поиск (POST /v1/deals/search), агрегаты (POST /v1/deals/aggregate, включая устаревший GET …/aggregate), описание полей (GET /v1/deals/fields), связанные записи (GET /v1/deals/{id}/contacts, …/activities), товарные позиции (GET /v1/deals/{id}/products) — и батч по сущности (POST /v1/deals/batch и аналогичные) принимали запросы без ограничения частоты — при том, что общий POST /v1/batch лимит уже нёс. Один клиент, читающий список чаще примерно десяти раз в секунду, замедлял списки и поиск для всех порталов той же площадки вплоть до обрыва по времени. Запрос HEAD к списку обслуживался как полноценный GET: данные читались целиком, а отдавались только заголовки.

Стало

На каждое такое чтение действует лимит 300 запросов в минуту на портал; все API-ключи одного портала делят один лимит, у каждой сущности и у каждой операции он считается отдельно. При превышении API Вайбкод отвечает 429 RATE_LIMITED с заголовком Retry-After; срока в теле ответа нет — берите его из заголовка. Текущее значение лимита приходит в заголовке x-ratelimit-limit. Батч по сущности ограничен строже — 30 запросов в минуту на портал, как и общий POST /v1/batch: один такой запрос разворачивается в сотни обращений к Битрикс24. Метод HEAD на этих адресах чтением больше не обслуживается — вместо него используйте GET с limit=1. Успешные ответы, коды ошибок и формат данных не изменились.

Что делать интеграторам

Обрабатывать 429 на всех чтениях по сущностям и на батче так же, как на /v1/search и /v1/batch: дождаться срока из Retry-After и повторить. Если лимит срабатывает регулярно — читать реже и крупнее (limit до 5000 за один вызов), кэшировать результаты у себя и не запускать одинаковые чтения параллельно. Если вы проверяли доступность списка методом HEAD — используйте GET с limit=1. Батч разумнее укрупнять, а не учащать: в один запрос помещается до 500 элементов.

NEW-0904-14: счета поддерживают include=deal

GET /v1/invoices/:id, GET /v1/invoices и POST /v1/invoices/search теперь принимают include=deal. Связанная сделка из parentId2 возвращается в _included.deal. Если связи нет, значение равно null.

2026-09-03

NEW-0903-1: `GET /v1/models` отдаёт декларацию управления рассуждением

У каждой модели в GET /v1/models и GET /v1/models/{model} появилось поле reasoning: null, если декларация не задана, иначе объект { map, default, budgetTokens } — map переводит ступень платформы (none|low|medium|high|max) в родной режим модели, default называет ступень, которую модель применяет без параметра рассуждения, budgetTokens — поддержка бюджета токенов рассуждения. Поле аддитивное: форма capabilities не менялась, прежние запросы работают как раньше. Подробнее — /docs/ai/models/list.

NEW-0903-2: управление рассуждением модели в чат-комплишенах

POST /v1/chat/completions принимает reasoning_effort, reasoning и chat_template_kwargs, нормализует их в пять ступеней платформы Вайбкод (none, low, medium, high, max) и применяет ближайшую поддерживаемую моделью ступень — округляя вниз и никогда не выключая рассуждение без явной просьбы. Ответ несёт поле reasoning (requested, applied, native), предупреждения REASONING_EFFORT_ADJUSTED, REASONING_NOT_SUPPORTED, REASONING_CANNOT_BE_DISABLED, REASONING_BUDGET_NOT_SUPPORTED, TEMPERATURE_OVERRIDDEN_BY_REASONING в warnings и заголовки X-Reasoning-Applied, X-Reasoning-Native, X-Reasoning-Warnings — в потоковом режиме заголовки единственный канал. Без параметра в запросе тело, уходящее в модель, остаётся прежним, а поведение модели не меняется. Токены рассуждения тарифицируются как выходные по той же ставке. Подробнее — /docs/ai/chat/completions.

FIX-0903-3: некорректное значение параметров рассуждения отклоняется

Было

Неизвестные поля reasoning_effort, reasoning, chat_template_kwargs молча отбрасывались, и запрос с опечаткой в значении выполнялся как будто параметра не было — ответ был HTTP 200.

Стало

Значение вне словаря (none, minimal, low, medium, high, xhigh, max), неположительный reasoning.max_tokens или chat_template_kwargs не-объект отклоняются кодом 400 invalid_request в OpenAI-конверте { "error": { "message", "type", "code" } }; явный null в любом из трёх полей принимается как «не задано»; для корректного запроса ответ остаётся HTTP 200.

FIX-0903-4: GET /v1/me перестал объявлять region обязательным

Было

В ответе GET /v1/me описание создания сервера без source помечало region обязательным и включало его в список обязательных полей. Из-за этого клиенты, использующие self-discovery, могли требовать регион, хотя контракт создания сервера уже разрешал его не передавать.

Стало

GET /v1/me помечает region необязательным и объясняет, что при его отсутствии платформа использует регион по умолчанию выбранного провайдера. Изменилось только self-discovery: GET /v1/me остаётся HTTP 200, а существующее поведение POST /v1/infra/servers без region и его ответ HTTP 201 не изменились.

Влияние на интеграторов

Клиенты, которые строят запрос по данным GET /v1/me, могут перестать считать region обязательным. Рабочие запросы с явно переданным регионом менять не нужно.

FIX-0903-5: прямые BOX-вызовы учитывают доверие к сертификату

Было

На BOX-портале с доверенным самоподписанным сертификатом оконный поиск, скачивание файлов и отдельные операции с ключами могли завершаться ошибкой TLS, хотя обычные API-вызовы работали.

Стало

Все эти вызовы используют настройку доверия только для точного адреса BOX-портала. Облачные и OAuth-вызовы, а также переходы скачивания на внешний адрес сохраняют строгую проверку сертификата.

BC-0903-6: ключ приложения без сессии сотрудника читает по имени только общие файлы

Поддержка старого формата до: не предусмотрена

Было

Запрос GET /v1/storage/objects/{key} ключом приложения без заголовка Authorization возвращал файл сотрудника, если под этим логическим именем существовал ровно один объект. Это работало лишь потому, что имя было одно на приложение, и рассчитывать на такое поведение было нельзя: как только объектов под именем становилось больше одного, выбор становился произвольным.

Стало

Такой запрос читает только общий файл приложения — с релиза, переходного режима нет. Если под этим именем есть лишь персональные файлы сотрудников, ответ — 404 STORAGE_OBJECT_NOT_FOUND. То же сужение действует на HEAD и на удаление по имени. Листинг GET /v1/storage/objects состав ответа не меняет.

Что делать интеграторам

Если вы читали или удаляли файл сотрудника ключом приложения без его сессии, передайте сессию сотрудника в заголовке Authorization: Bearer — тогда доступен его файл. Маршрута GET /v1/storage/objects/{objectId}, который эта запись советовала раньше, в API нет: файл читается только по ключу. Публичный файл сотрудника открывается и по ссылке GET /v1/public-storage/{portalId}/{objectId}, если на портале включён анонимный доступ.

FIX-0903-7: сотрудники одного приложения сохраняют файлы под одним именем

Было

Логическое имя файла было одно на приложение. Если сотрудник портала сохранял avatar.png, второй сотрудник того же приложения получал 409 STORAGE_KEY_OWNED_ELSEWHERE на прямой загрузке и 409 STORAGE_KEY_EXISTS на составной — хотя файлы легли бы по разным адресам, потому что в адрес персонального файла входит идентификатор сотрудника. Отказ не истекал: имя оставалось занятым, пока первый сотрудник не удалит файл.

Стало

Имя занимается отдельно для каждого сотрудника. Второй сотрудник получает 200 и свой объект с собственным идентификатором; повторная загрузка тем же сотрудником по-прежнему заменяет его собственный файл. Общий файл приложения и персональный файл сотрудника делят одно пространство имён, поэтому запрос, заставший имя уже занятым объектом другого типа владения, по-прежнему получает 409 STORAGE_KEY_OWNED_ELSEWHERE.

FIX-0903-8: составная загрузка различает, кто занял логическое имя

Было

POST /v1/storage/objects/multipart/create на занятое логическое имя отвечал 409 STORAGE_KEY_EXISTS независимо от того, кто это имя держит, — и когда объект принадлежал вам, и когда речь шла об объекте другого типа владения: общий файл приложения против персонального файла сотрудника. Различить эти случаи по ответу было нельзя, хотя действия для их разрешения разные.

Стало

Когда имя держит объект другого типа владения, составная загрузка отвечает 409 STORAGE_KEY_OWNED_ELSEWHERE — так же, как давно отвечает прямая загрузка. Остальные случаи занятого имени по-прежнему отвечают 409 STORAGE_KEY_EXISTS, статус ответа во всех случаях прежний.

Влияние на интеграторов

Действий не требуется: отказ остаётся отказом с тем же статусом 409, меняется только код в теле. Если ваш клиент ветвится на конкретном коде составной загрузки, добавьте ветку для STORAGE_KEY_OWNED_ELSEWHERE — она означает, что имя занято объектом другого типа владения, и освободить его сменой сотрудника нельзя.

BC-0903-9: история стадий смарт-процессов по числовому entityTypeId, параметры запроса стали строгими

Поддержка старого формата до: не предусмотрена

Было

GET /v1/stage-history принимал только четыре именованных типа — deal, lead, invoice и new-invoice. У смарт-процесса имени нет, поэтому запросить его историю стадий было нельзя: ?entityType=128 отвечал 400 INVALID_ENTITY_TYPE, не обращаясь к Битрикс24.

Соседние параметры (ownerId, typeId, categoryId, limit, offset, createdAfter, createdBefore, stageId, stageSemanticId, statusId, statusSemanticId) при этом принимались в формах, которые отвечали УСПЕХОМ:

?entityType=deal&ownerId[]=5 возвращал 200 и историю владельца 5, как если бы значение пришло скаляром. Повтор ?entityType=deal&ownerId=5&ownerId=6 возвращал 200, молча взяв последнее значение. Повтор с одинаковыми значениями ?entityType=deal&limit=50&limit=50 возвращал 200 и корректную выборку.

Стало

entityType принимает числовой entityTypeId смарт-процесса — ?entityType=128. Идентификатор возвращает GET /v1/smart-processes. Смарт-процессы stage-based, как сделки: доступны stageId, stageSemanticId и categoryId, а ответ несёт те же поля.

Числовой идентификатор, у которого есть именованный ключ (1, 2, 31), отклоняется с подсказкой на этот ключ. У контакта (3), компании (4), предложения (7) и старого счёта (5) истории стадий нет — они тоже отклоняются. Идентификатор, которого нет на портале, возвращает 400 INVALID_ENTITY_TYPE, а не ошибку Битрикс24.

Каждый параметр из списка выше принимается ровно ОДИН раз и только одним строковым значением. Все три формы из блока «Было» теперь отвечают 400 INVALID_PARAMS — и скобочные (?ownerId[]=5, ?ownerId[x]=1), и плоский повтор (?ownerId=5&ownerId=6), в том числе когда повторённые значения совпадают, и смесь написаний (?ownerId[]=6&ownerId=5) — последняя отвечала 200, оставляя от запроса только 5. Скобочная форма ?ownerId[x]=1 до этого отвечала 500.

Числовые параметры (ownerId, typeId, categoryId, limit, offset) вдобавок принимаются только целым числом, записанным полностью: без знака, экспоненты, ведущих нулей и хвоста. ?ownerId=1e3 возвращал 200 и историю владельца 1 вместо 1000, ?typeId=2abc — историю типа 2, а ?ownerId=abc вообще терял ограничение по владельцу и отдавал выборку ШИРЕ запрошенной. Теперь все три — 400 INVALID_PARAMS.

Ужесточены и формы самого entityType: entityType[]= и entityType[x]= возвращали 500 вместо 400; повторённый entityType=a&entityType=b молча брал последнее значение, а теперь отклоняется; entityType=constructor и entityType=__proto__ отвечали 200 с пустым результатом вместо отказа. Текст ошибки INVALID_ENTITY_TYPE теперь называет и числовую форму, а не только четыре имени.

Тот же класс закрыт в POST /v1/duplicates/find и POST /v1/triggers/fire: тело с нестроковым type или entityType — числом, объектом или массивом — возвращало 500 вместо документированного 400. В triggers/fire то же сделано для entityId и triggerId: объект в этих полях раньше либо ронял запрос в 500, либо уезжал в Битрикс24 бессмысленным значением и отвечал 200, ничего не запустив. Массив из НЕСКОЛЬКИХ значений в entityId уходил в Битрикс24 склеенным в одну строку, ни одной записи не адресовал и триггер не запускал, — теперь это 400. Массив из ОДНОГО значения работал ([5] читался как 5, и триггер срабатывал) и работать не перестал. Числа в обоих полях принимаются как и раньше и уходят в Битрикс24 без изменений — числовой triggerId, который работал, работать не перестал. Нестроковый entityType в поиске дубликатов теперь отклоняется, а не игнорируется. Оба маршрута отвечают 400 и на запрос вовсе без тела и без заголовка Content-Type — раньше такой запрос давал 500.

Что делать интеграторам

Передавайте каждый параметр строки запроса один раз и скалярным значением: ?ownerId=5 вместо ?ownerId[]=5 и вместо повтора ?ownerId=5&ownerId=6. Если фильтр собирается в цикле, проверьте, что ключ не добавляется в строку запроса дважды — раньше лишний повтор проходил незаметно, теперь он даёт 400 INVALID_PARAMS с именем параметра в тексте ошибки.

Прежняя форма не остаётся принятой на переходный период: окна поддержки нет, отказ действует сразу после обновления — поэтому правку в клиенте надо выкатывать вместе с обновлением, а не после него.

Клиенты, которые уже передают параметры скалярами по одному разу, менять ничего не должны.

FIX-0903-10: один запрос больше не ставит метод Битрикс24 на паузу своими подвызовами

Было

Если один запрос к пакетным и циклическим методам API Вайбкод несколько раз подряд получал таймаут от одного метода Битрикс24, он мог сам включить для этого метода 15-минутную паузу. Оставшиеся подвызовы возвращали TIMEOUT_QUARANTINE, а пауза затрагивала другие запросы того же портала.

Стало

Все подвызовы одного запроса учитываются как одна попытка для каждой пары «портал × метод». Отдельные запросы по-прежнему могут включить защитную паузу после установленного числа последовательных таймаутов. Форматы успешных и ошибочных ответов не изменились.

Влияние на интеграторов

Менять клиентский код не требуется. Исправлены POST /v1/batch, POST /v1/{entity}/batch, GET /v1/tasks/:taskId/comments, POST /v1/tasks/:taskId/comments, POST /v1/tasks/:taskId/comments/batch, GET /v1/task-time и GET /v1/timeline-logs.

BC-0903-11: на обновлении отклоняется фотография, из которой не собрать файл

Поддержка старого формата до: не предусмотрена

Было

При обновлении сотрудника значение personalPhoto, из которого Битрикс24 не может собрать файл, доезжало до портала как команда снять текущую фотографию. Так вели себя литерал false, логические true и false, число 0, строка, в которой меньше двух символов алфавита base64 — например !!! или ===, — inline-пара [имя файла, base64], содержимое которой одно из перечисленного, и любая другая структура, у которой второго значения нет, оно null или нечитаемо, включая вложенную {fileData: [...]}, явный null и структуру на месте содержимого. Вызов отвечал успехом уже после удаления, а прежний отказ покрывал только пустую строку и строку из пробельных символов.

Стало

Такие значения отклоняются с INVALID_PARAMS до вызова Битрикс24 на всех трёх поверхностях обновления, как и пустая строка. Значение с двумя и более символами base64 по-прежнему уходит на портал: из него получается файл, и решение о пригодности принимает сам Битрикс24. На CREATE в POST /v1/users новый отказ не действует.

Что делать интеграторам

Не подставляйте в personalPhoto заглушки вроде false, 0, true или пустых строк, чтобы «ничего не менять» — просто не передавайте поле. Чтобы снять фотографию, вызовите DELETE /v1/users/:id/personal-photo.

NEW-0903-12: появилась команда удаления фотографии сотрудника

У фотографии сотрудника появилась отдельная команда удаления — DELETE /v1/users/:id/personal-photo. Она снимает фотографию профиля и отвечает 200 с полями id, personalPhoto: null и removed: true. Вызов идемпотентен: у сотрудника без фотографии он тоже успешен. Операция необратима, Битрикс24 удаляет сам файл, поэтому повторная загрузка того же изображения даст новый URL. Нужен скоуп user, ключ только для чтения команду не выполняет, а решение о правах принимает Битрикс24 — при отказе приходит 403 UPDATE_FAILED и фотография остаётся на месте.

Записью в поле personalPhoto фотографию снять нельзя ни на одной поверхности обновления, и null для этого тоже не подходит. На PATCH /v1/users/:id и в пакете по одной сущности null принимается и игнорируется, а на общем POST /v1/batch отклоняется с INVALID_PARAMS, потому что кодировщик подзапроса превратил бы его в пустое значение строки запроса — ту же команду снять фотографию. Это различие поверхностей теперь описано в документации намеренно, поведение вызовов не менялось. Чтобы заменить фотографию, отдельное удаление не нужно: отправьте пару [имя файла, base64] в personalPhoto на POST /v1/users или PATCH /v1/users/:id.

BC-0903-13: создание действий и роботов бизнес-процессов возвращает строковый код

Поддержка старого формата до: не предусмотрена

Было

POST /v1/bizproc-activities и POST /v1/bizproc-robots отвечали HTTP 201 с булевым data.id: true. Это значение нельзя было использовать как code в PATCH или DELETE.

Стало

Те же запросы по-прежнему отвечают HTTP 201. При обычном ответе Битрикс24 true поле data.id содержит строковый code, переданный при создании; если Битрикс24 явно вернул CODE, используется это значение. Идентификатор подходит для последующих PATCH и DELETE.

Что делать интеграторам

Измените тип data.id в обработчиках этих двух ответов с boolean на string и используйте полученное значение как code для обновления или удаления.

BC-0903-14: повторная загрузка личным ключом отвечает отказом там, где состояние объекта его требует

Поддержка старого формата до: не предусмотрена

Было

Повтор на занятый key с личным ключом разработчика или в серверном контексте всегда отвечал HTTP 200 и создавал вторую строку, поэтому проверки состояния объекта до него просто не доходили.

Стало

Повтор проходит тот же разбор состояний, что и ключ приложения, и три ситуации отвечают отказом. Другое значение visibility при повторе — 400 STORAGE_VISIBILITY_MISMATCH: поле не передавайте либо передайте сохранённое значение, оно указано в сообщении. Составная загрузка (путь C) на занятый адрес — 409 STORAGE_KEY_EXISTS до создания сессии. Тело без Content-Length на занятый адрес — 409 STORAGE_REPLACE_REQUIRES_LENGTH. Дополнительно становятся достижимыми 409 STORAGE_UPLOAD_PENDING и 409 STORAGE_MULTIPART_IN_PROGRESS — раньше для этих ключей они не срабатывали никогда.

FIX-0903-15: повторная загрузка личным ключом заменяет файл, а не создаёт второй объект

Было

Личный ключ разработчика и серверный контекст на повторной загрузке того же key создавали ВТОРОЙ объект по тому же физическому адресу. Байты первого объекта перезаписывались вторым, размер считался дважды, а чтение и удаление по логическому имени могли попасть в любую из строк. Гейт опасных типов для PUBLIC-объектов при этом считался по заявленной видимости, поэтому text/html проходил под живой публичный объект.

Стало

Повтор заменяет содержимое существующего объекта: сохраняются object.id, createdAt, key, физический адрес и visibility, обновляются sizeBytes, sha256, contentType и contentUpdatedAt. Ответ остаётся HTTP 200. Подходящий мягко удалённый объект оживляется так же, как у ключа приложения. Гейт опасных типов считается по видимости найденной строки — до записи байт, поэтому text/html под живой PUBLIC-объект больше не проходит.

FIX-0903-16: выкладка Node.js 20 не зависит от репозитория пакетов при готовом образе

Было

Шаг подготовки Node.js 20 всегда обращался к внешнему репозиторию системных пакетов, даже если все базовые пакеты уже были установлены. При недоступном репозитории шаг долго оставался без обновлений, а затем завершался ошибкой.

Стало

Готовый образ с базовыми пакетами не обращается к репозиторию. Если пакет всё же требуется, сетевое ожидание ограничено; потоковый ответ во время долгой подготовки периодически передаёт прошедшее время. Формат итогового ответа и статус успеха не изменились.

BC-0903-17: сортировка валют отвергает поле, по которому Битрикс24 не сортирует

Поддержка старого формата до: не предусмотрена

Было

GET /v1/currencies и POST /v1/currencies/search принимали в sort и order любое имя поля. Метод Битрикс24 crm.currency.list молча подменяет неизвестный ключ сортировкой по умолчанию, поэтому ответ приходил 200 со списком в произвольном для клиента порядке и без признака ошибки. То же касалось ключей sort и order у подвызова entity=currencies в POST /v1/batch.

Стало

Принимаются три поля: sort, id и fullName. Любое другое имя — как несуществующее bogus, так и объявленное amount — возвращает 400 UNKNOWN_SORT_FIELD до вызова Битрикс24, и сообщение перечисляет допустимые имена. Документированный ?order[sort]=asc не меняется.

Что делать интеграторам

Три класса изменений, каждый требует своей проверки.

Первое: запрос с неизвестным именем поля вместо 200 получает 400. Если код полагался на то, что такой запрос «просто работает», он теперь увидит ошибку — это и есть исправление, но обработать её нужно.

Второе: поля amount, amountCnt, base, formatString, decimals, decPoint, thousandsSep, lid, dateUpdate и lang тоже отвечают 400. Сортировки по ним никогда не было: ядро подменяло её порядком по умолчанию, то есть успешный ответ был неправдой.

Третье, самое тихое: ?sort=id и ?sort=fullName теперь действительно меняют порядок строк, оставаясь 200. Раньше оба молча сортировались по умолчанию. Постраничный обход по этим полям через offset вернёт другие страницы, чем до обновления, и признака в статусе ответа нет — перепроверьте такие обходы.

Затронутые эндпоинты: GET /v1/currencies, POST /v1/currencies/search и подвызов entity=currencies в POST /v1/batch — оба ключа, sort и order. Для остальных сущностей ключ order в батче по-прежнему уходит в Битрикс24 как есть.

BC-0903-18: include принимает только разрешаемые связи

Поддержка старого формата до: не предусмотрена

Было

Связи site в GET /v1/pages/:id, requisite в GET /v1/companies/:id и GET /v1/contacts/:id, а также quote в GET /v1/deals/:id объявлялись доступными. Запрос с таким include возвращал HTTP 200, но не добавлял связь в _included. То же происходило со связями deal, contact и company у GET /v1/quotes/:id, contact и company у GET /v1/invoices/:id, а также section у GET /v1/products/:id.

Стало

Связи предложений, счетов и товаров возвращаются в _included как объект или null, если поле-ссылка пустое. site у страниц, requisite у компаний и контактов, а также quote у сделок больше не объявляются. Запрос этих имён возвращает 400 INVALID_INCLUDE.

Что делать интеграторам

Для страницы прочитайте siteId и запросите GET /v1/sites/:id. Для компании используйте GET /v1/requisites с фильтрами entityTypeId=4 и entityId=<companyId>, для контакта — с entityTypeId=3 и entityId=<contactId>. Для сделки прочитайте quoteId и запросите GET /v1/quotes/:id.

BC-0903-19: массив input в POST /v1/embeddings ограничен 64 строками

Поддержка старого формата до: не предусмотрена

Было

POST /v1/embeddings принимал массив input любой длины — ограничения на число строк в одном запросе не было.

Стало

Массив input принимает не более 64 строк. Более длинный массив отклоняется с 400 invalid_request до списания средств и до обращения к модели. Разбейте больший запрос на части по 64 строки.

NEW-0903-20: новая модель распознавания речи BitrixGPT 5.6 Transcribe

POST /v1/audio/transcriptions принимает вторую платформенную модель — bitrix/bitrixgpt-5.6-transcribe (BitrixGPT 5.6 Transcribe): 25 европейских языков, включая русский и украинский. Whisper Large v3 Turbo остаётся моделью по умолчанию — вызовы без поля model не меняются. У новой модели поля language, prompt, hotwords, temperature и vad_filter принимаются, но на распознавание не влияют; их имена возвращаются в новом информационном заголовке ответа X-Ignored-Params. Форматы text, srt и vtt для новой модели строит платформа Вайбкод из сегментов, и они тарифицируются по длительности аудио, как json. В capabilities модели появился ключ transcription_openai_only. Английские тексты ошибок ai_provider_timeout и ai_provider_unavailable теперь не называют движок распознавания; коды и статусы прежние.

FIX-0903-21: демо линейки Vibe+ открывает доступ на .com

Было

Портал, которому выдали демо Vibe+, продолжал получать отказ INT_VIBE_PLUS_REQUIRED: выдача демо не меняет код тарифа портала, а вердикт доступа читал только его. Слот capabilities.servers.create в /v1/me оставался available: false, и повторное действие упиралось в тот же отказ.

Стало

Активное демо открывает доступ в режиме пробного периода — с теми же ограничениями, что у демо Маркетплейса. Купленный тариф Vibe+ поверх активного демо по-прежнему даёт полный доступ, а истёкшее демо доступа не даёт.

NEW-0903-22: два новых кода отказа у промокода: ограничение по тарифу Битрикс24

Кампания промокодов теперь может ограничивать круг получателей тарифом Битрикс24. Ограничение задаёт список разрешённых тарифов; пустой список означает, что ограничения нет, — все ранее выпущенные кампании и промокоды работают как прежде, и запрос без знания новых кодов ничего не теряет.

Когда список задан, оба публичных метода промокода отвечают новыми кодами: POST /v1/cowork/coupon/preview возвращает их в поле причины при valid=false, POST /v1/cowork/coupon/redeem — статусом 409. Кодов два, и путать их нельзя.

COUPON_TARIFF_NOT_ELIGIBLE — тариф портала прочитан и в список не входит. Отказ окончательный: на этом портале код не начнёт работать сам, повторять запрос бессмысленно.

COUPON_TARIFF_UNKNOWN — ограничение действует, но тариф портала платформа ещё не определила (так бывает на только что подключённом портале). Промокод цел, попытка не расходует лимит попыток погашения, и повтор позже — верное действие.

В обоих случаях промокод остаётся непогашенным, а прежние коды отказа не изменились.

⚠️ У preview статус ответа прежний — 200 с valid=false, — но в поле reason теперь приходят и эти два значения. Клиент, который разбирает reason по известному ему набору, обязан завести им ветки: COUPON_TARIFF_UNKNOWN в ветке по умолчанию «промокод недействителен» покажет окончательный отказ там, где верное действие — повторить позже. Расход лимита попыток у проверки такой же, как у погашения: COUPON_TARIFF_UNKNOWN его не тратит, COUPON_TARIFF_NOT_ELIGIBLE тратит.

2026-09-02

BC-0902-1: Документы сообщают состояние преобразования в PDF

Поддержка старого формата до: не предусмотрена

Было

Поля isTransformationError, transformationErrorCode, transformationErrorMessage, transformationCancelReason и pullTag не были объявлены в контракте документов. Если Битрикс24 возвращал их через GET /v1/documents/:id, API Вайбкод передавал значения без нормализации. Те же поля в телах POST /v1/documents и PATCH /v1/documents/:id не отклонялись локальной проверкой полей только для чтения.

Стало

Пять полей объявлены как допускающие null поля ответа только для чтения. isTransformationError возвращается как boolean | null, остальные четыре поля — как string | null. Пустые строки в строковых полях становятся null. Запрос записи с любым из этих полей возвращает 400 READONLY_FIELD.

Что делать интеграторам

Не передавать эти пять полей в телах создания и изменения документа, в том числе при повторной отправке ранее полученного объекта. При чтении учитывать null и использовать pullTag для подписки на обновления состояния преобразования.

BC-0902-2: Адрес переключателя режима в отказе по режиму доступа больше не фиксирован

Поддержка старого формата до: не предусмотрена

Было

Отказ по режиму доступа WRITE_BLOCKED_READONLY_KEY всегда возвращал details.switchUrl равным "/keys", а машинная схема закрепляла это значение перечислением enum: ["/keys"]. Раздел ключей показывает только личные ключи, поэтому для двух других видов путь был тупиком: ни ключ авторизации приложения, ни менеджмент-ключ там не показываются по построению — ни владельцу, ни администратору Битрикс24. Держателю сообщали адрес страницы, на которой переключателя для его ключа нет.

Стало

details.switchUrl указывает на страницу, где режим переключается именно у этого ключа: личный ключ — раздел ключей "/keys", ключ авторизации приложения — кабинет приложений "/applications", менеджмент-ключ — раздел менеджмент-ключей "/management-keys". Текст сообщения называет тот же адрес, что и поле, — они собираются вместе и разойтись не могут. Перечисление значений снято со схемы: это путь в интерфейсе, а не константа протокола, и закрепление литерала делало контракт невыполнимым при переезде страницы. Поле по-прежнему приходит всегда и остаётся обязательным — изменилось значение, а не наличие.

Что делать интеграторам

Читать switchUrl из ответа, а не подставлять "/keys". Клиентам, которые генерируют типы из схемы, литеральный тип '/keys' больше не выдаётся — обновите сгенерированные типы, иначе проверка значения не пройдёт.

FIX-0902-3: поток, который не начался, завершается повторяемой ошибкой у моделей всех поставщиков

Было

У моделей bitrix/* установка потока уже делала одну попытку с общим бюджетом, а у моделей остальных поставщиков платформа молча отправляла запрос второй раз и ждала ещё один полный бюджет. Клиент в это время не получал ничего — до двух полных бюджетов тишины, — а затем видел общий отказ провайдера, по которому нельзя отличить зависшую установку потока от любой другой неопознанной ошибки.

data: { "error": { "code": "ai_provider_unavailable", "type": "server_error", "retryable": true, "retryAfter": 6 } }
data: [DONE]

Стало

Установка потока делает одну попытку с общим бюджетом у моделей ЛЮБОГО поставщика. Если заголовки не пришли за отведённое время, ожидание не удваивается, а поток завершается тем же событием stream_idle_timeout, что и замолчавший в середине ответа, — по нему видно, что молчал именно поток.

data: { "error": { "code": "stream_idle_timeout", "type": "server_error", "retryable": true, "retryAfter": 7 } }
data: [DONE]

Ожидание больше не удваивается невидимо, а причина отказа названа точно — теперь на всём наборе моделей, а не только на bitrix/*.

NEW-0902-4: список ресурсов бронирования в V1

Появился GET /v1/booking-resources — он возвращает ресурсы бронирования, доступные ключу, и закрывает разрыв в сценарии первого бронирования: POST /v1/bookings требует непустой resourceIds, а узнать валидные идентификаторы средствами одного только V1 было нельзя.

Нужен скоуп booking. Ключ с режимом «только чтение» подходит — метод читающий. Фильтры typeId и searchQuery необязательны и комбинируются, любой другой параметр отклоняется с 400 INVALID_PARAMS.

Каталог собирается целиком, поэтому параметров постраничного вывода нет: meta.total равен длине data, meta.hasMore всегда false. Каждая запись несёт ровно четыре поля — id, name, typeId и isMain. Поле description не отдаётся: это свободный текст портала, для выбора ресурса он не нужен.

Полнота ответа гарантируется для каталога, не менявшегося во время запроса, — снимка коллекции Битрикс24 не предоставляет. Портал больше чем с 500 ресурсами получает 502 BITRIX_RESULT_TOO_LARGE вместо усечённого списка; сузить выборку помогают те же два фильтра.

NEW-0902-5: выгрузка «Поступления» помечает отозванные заказы и отдаёт сумму к признанию

В пореестровой выгрузке GET /v1/platform/revenue/money-in/payments у каждой строки появилось поле revokedAt — дата отзыва пакета в формате UTC ISO-8601, либо null у живого заказа. Строка отозванного заказа из выгрузки не исчезает и суммы её не меняются: деньги в дату paymentDate действительно поступили, и касса по-прежнему сходится с банковской выпиской.

В агрегате GET /v1/platform/revenue/money-in рядом с прежними суммами приехали два вложенных яруса. Ярус revoked — отозванная часть тех же величин (ordersCount, grossCashRub, vatRub, netRubExclVat, vibesCredited), ярус recognized — те же величины за вычетом отозванного, то есть сумма к признанию. Поля верхнего уровня значений не меняют и остаются кассой за период, поэтому существующие интеграции продолжают работать без правок.

Отзыв приходит отдельным событием и может случиться уже после закрытия периода, поэтому при повторной выгрузке у ранее выгруженной строки поле revokedAt может оказаться заполненным. Набор строк при этом прежний, ключ orderId стабилен.

NEW-0902-6: device-flow принимает идентификатор устройства

POST /v1/connect/device/authorize принимает необязательный параметр device_id — стабильный идентификатор установки клиента (до 128 символов, буквы, цифры, _.:-). Он действует для приложения Cowork/Code: при повторном подключении с того же устройства прежний ключ отзывается, а не остаётся в списке рядом с новым. Сторонние клиенты параметр передавать могут, но на выдачу их ключей он пока не влияет.

Параметр необязателен, и поведение без него прежнее — каждое подключение выдаёт отдельный ключ, прежние остаются активными. Несколько разных устройств у одного человека работают как и раньше: идентификаторы у них различаются, и ключи друг друга не вытесняют. Значение, не подходящее по формату, игнорируется — подключение проходит, теряется только замена прежнего ключа.

NEW-0902-7: новый код отказа INT_BOX_PARTNER_REQUIRED для коробок на международной установке

На международной установке доступ коробочного портала к платформе может требовать подтверждённой партнёрской (NFR) лицензии. Коробка без подтверждения получает HTTP 402 с новым кодом INT_BOX_PARTNER_REQUIRED — на создании сервера, агента, управляемого бота и на выписке ключа. Тело отказа устроено как у соседних кодов этого семейства (userMessage, alternatives, hint), но поле details.requiredTariffs пустое: переход на другой тариф Битрикс24 этот отказ не снимает, признак читается при регистрации модуля коннектора. Тот же код приезжает в поле reason слота capabilities.servers.create ответа GET /v1/me, а адрес документации — в его alternatives[].url. Требование выключено по умолчанию и включается администратором платформы; пока оно выключено, ни один портал этого кода не увидит и ответы остаются прежними.

NEW-0902-8: стабильный идентификатор портала в самоописании ключа

GET /v1/me возвращает новое поле portalId — устойчивый идентификатор портала Битрикс24, к которому привязан ключ. Оно приходит тем же запросом, рядом с уже существующими portal и owner, и есть у ключей vibe_api_ и vibe_app_.

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

В успешном ответе поле всегда непустое: ключ без привязанного портала до этого ответа не доходит, он получает 401 NO_PORTAL. Менеджмент-ключи vibe_live_ нового поля не получают — они не привязаны к одному порталу и по-прежнему отдают перечень порталов в portals.

NEW-0902-9: создание сервера отвечает 409 `REISSUE_IN_PROGRESS`, пока перевыпускается ключ авторизации приложения

У POST /v1/infra/servers появился новый код отказа REISSUE_IN_PROGRESS со статусом 409. Платформа Вайбкод отвечает им, когда владелец карточки приложения в этот момент перевыпускает её ключ авторизации: сервер не создаётся, потому что он оказался бы на ключе, который отзывается в ту же секунду.

Отказ временный — повторите запрос после того, как перевыпуск завершится. Успешный ответ и все прочие коды отказа прежние, запросы, которые работали раньше, работают без изменений.

FIX-0902-10: методы дашборда Открытых линий появились в OpenAPI

Было

Шесть публичных методов статистики Открытых линий были описаны на страницах документации, но отсутствовали в /v1/openapi.json и сгенерированных карточках API.

Стало

Все шесть методов доступны в OpenAPI и карточках API со скоупом imopenlines, параметрами и форматами ответов. Runtime-поведение не менялось: успешный ответ остаётся HTTP 200, прежние статусы и форматы ошибок сохранены.

NEW-0902-11: код обращения в неопознанном отказе выписки ключа

Когда Битрикс24 отказывает в выписке ключа причиной, которую платформа не распознаёт, ответ теперь дополнительно несёт error.details.incidentCode — код обращения из шести символов (латинские буквы и цифры). Тот же код платформа записывает в журнал портала рядом с разбором отказа, поэтому поддержке достаточно его назвать, чтобы найти запись.

Поле опциональное и добавлено к уже существующему телу ответа: HTTP-статус, error.code и error.details.reason прежние, клиентам делать ничего не нужно.

NEW-0902-12: раздел «Перформанс ревью» доступен через /v1/performan/review

Появились шесть точек с новым правом доступа performan. Чтение: GET /v1/performan/review/campaigns, GET /v1/performan/review/self-reviews, GET /v1/performan/review/peer-reviews, GET /v1/performan/review/manager/questions, GET /v1/performan/review/manager/reviews. Всем, кроме кампаний, обязателен параметр campaignId; у менеджерских карточек есть необязательный фильтр revieweeUserId. Ответ — { "success": true, "data": [...], "meta": { "nextCursor": ... } }. Постраничность курсорная: limit (1..200, по умолчанию 50) и afterCursorId, который берётся из meta.nextCursor.id предыдущей страницы. Значение limit вне диапазона отвечает 400. Параметра offset и поля с общим количеством записей у этих методов нет.

Каждый список ограничен вызывающим пользователем: его кампании, его карточки и те менеджерские связи, где он оценивающий. Пустой ответ означает «у этого пользователя ничего нет», а не «на портале ничего нет».

Запись: POST /v1/performan/review/manager/answers с телом { "relationId": 4, "answers": [...], "isAutosave": true, "expectedStateHash": "..." }. Поле isAutosave по умолчанию true — сохраняется черновик; передайте false, чтобы завершить оценку: карточка перейдёт в статус completed, и отменить это нельзя. Значение expectedStateHash приходит только в ответе на запись, ни один метод чтения его не возвращает, поэтому первый вызов идёт без него. Если состояние изменилось с момента чтения, ответ — 409 с кодом PERFORMAN_STATE_CONFLICT: перечитайте карточку и повторите запрос со свежим значением. Чужая связь и несуществующая связь одинаково отвечают 403. Ключ только для чтения получает на запись 403 WRITE_BLOCKED_READONLY_KEY.

Раздел доступен только тем порталам, где модуль «Перформанс ревью» установлен и поверхность для них включена: на остальных все шесть адресов отвечают 404, и их нет в спецификации GET /v1/openapi.json такого портала. Скоуп performan по той же причине не входит в набор по умолчанию.

Ключ Коворка получает скоуп реактивно: если он ещё не выдан, первый запрос к любому из адресов отвечает 409 PERFORMAN_SCOPE_JUST_GRANTED и выдаёт право на месте — повторите тот же запрос, он пройдёт. Ключ в режиме «только чтение» так право не получает: реактивная выдача — это запись, поэтому такому ключу отвечает 403, а скоуп добавляет владелец ключа в кабинете.

Ответить на вопрос с выбором варианта через API нельзя: идентификаторы вариантов не отдаёт ни один метод чтения, а список вопросов возвращает вопрос без вариантов.

У GET /v1/openapi.json появился лимит частоты на один адрес вызывающего. Точное значение потолка отдаётся в заголовке x-ratelimit-limit ответа — читайте его оттуда, а не задавайте числом в коде. Превышение отвечает 429 RATE_LIMITED. Лимит щедрый и на обычное чтение не рассчитан: спека отдаётся с ETag, поэтому дискавери-клиент и генератор SDK повторно получают 304 вместо тела.

Тело спеки зависит от ключа в запросе: с ключом портала, которому поверхность performan включена, она несёт performan-пути и скоуп performan в каталоге, без ключа — не несёт. Поэтому ответ помечен Vary: Authorization, X-Api-Key (ключ читается из любого из двух заголовков), а с таким ключом отдаётся под Cache-Control: private вместо public. Не переиспользуйте один сохранённый файл спеки между разными ключами.

FIX-0902-13: тарифный отказ Битрикс24 при выписке ключа и установке приложения называет причину

Было

Когда Битрикс24 отказывал в выписке кодом FEATURE_NOT_AVAILABLE_ON_CURRENT_PLAN (тариф портала не включает Вайбкод), ответ был безликим на ЛЮБОЙ ручке выписки — и там, где выпускается ключ (POST /v1/keys → 502 CONNECTOR_KEY_ISSUE_FAILED), и там, где устанавливается приложение (POST /v1/apps → 502 CONNECTOR_APP_INSTALL_FAILED). Текста для человека в теле не было вовсе, а причина оставалась только во внутреннем поле, поэтому клиент читал отказ как временный сбой платформы и повторял запрос. Повтор не помогает никогда: гейт снимается только сменой тарифа Битрикс24.

HTTP 502
{ "success": false, "error": { "code": "CONNECTOR_KEY_ISSUE_FAILED", "message": "connector refused to issue the key" } }

Стало

Отказ разбирается по региону портала. Там, где доступ продаётся, приходит 402 с кодом тарифного пейвола и текстом для человека: на международном сегменте — INT_VIBE_PLUS_REQUIRED с details.requiredTariffs: ["vibe+"] и details.upgradeUrl на страницу подключения тарифа в самом портале, в тарифных регионах СНГ на бесплатном тарифе — BY_PAID_ONLY, KZ_PAID_ONLY или UZ_PAID_ONLY. Там, где предлагать нечего (коробочный портал, портал уже на платном тарифе, нераспознанный регион), ответ остаётся 502, но получает собственный код CONNECTOR_PLAN_REQUIRED и непустой userMessage вместо пустого тела.

HTTP 402
{ "success": false, "error": { "code": "INT_VIBE_PLUS_REQUIRED", "message": "This action requires a Vibe+ plan on your Bitrix24 account.", "details": { "requiredTariffs": ["vibe+"], "upgradeUrl": "https://example.bitrix24.com/online/?feature_promoter=limit_why_pay_tariff_vibe" } } }

Влияние на интеграторов

Успешные ответы не меняются. Клиент, который различает отказы по коду, получает теперь терминальный класс вместо транзитного и может прекратить повторы: ни один из новых кодов повтором не снимается. Клиент, который смотрел только на статус, продолжает работать — операция и раньше, и теперь завершается ошибкой.

FIX-0902-14: отказ на странице согласия возвращает пользователя в приложение партнёра

Было

Пользователь подтверждал доступ, упирался в отказ выдачи ключа — нет подписки, неподдерживаемый регион, исчерпан лимит ключей — и оставался на странице согласия. На redirect_uri не уходило ничего: партнёр не узнавал ни о попытке, ни о её причине.

Стало

Отказ несёт готовый адрес возврата: на странице появляется кнопка «Вернуться в приложение», а redirect_uri получает error=access_denied (temporarily_unavailable при недоступности Битрикс24), машинный код причины в error_description и исходный state. Если у зарегистрированного адреса возврата был свой параметр code, при отказе он снимается — отказ не приходит вперемешку с признаками успеха. Так же ведут себя отказы до подтверждения — протухшая ссылка согласия и битый CSRF-токен, в том числе на кнопке «Отклонить».

2026-09-01

FIX-0901-1: отказ по правам на доске спринта приходит как 403, а не как ошибка платформы

Было

Ключ, у пользователя которого нет доступа к скрам-проекту, получал на колонки доски спринта ответ 422 BITRIX_ERROR — тот же класс, которым платформа Вайбкод сообщает о бизнес-ошибке Битрикс24. Отличить «нет прав» от «что-то временно не так на портале» по коду было нельзя, и запрос повторяли, хотя состояние стабильное и повтор ничего не меняет.

HTTP 422
{ "success": false, "error": { "code": "BITRIX_ERROR", "message": "Access denied", "b24Code": "0" } }

Стало

GET /v1/scrum/sprints/{sprintId}/stages и POST /v1/scrum/sprints/{sprintId}/stages отвечают на отказ по правам кодом 403 BITRIX_ACCESS_DENIED — тем же, что описан в общем справочнике ошибок. Это сигнал «не повторять, а выдать пользователю доступ к скрам-проекту».

HTTP 403
{ "success": false, "error": { "code": "BITRIX_ACCESS_DENIED", "message": "Access denied" } }

Успешные ответы не меняются: список колонок по-прежнему приходит с HTTP 200, созданная колонка — с HTTP 201. Не меняется и ответ на несуществующий спринт: он остаётся 404 ENTITY_NOT_FOUND.

BC-0901-2: пустая фотография сотрудника отклоняется при обновлении

Поддержка старого формата до: не предусмотрена

Было

При UPDATE сотрудника пустая строка или строка только из пробельных символов в personalPhoto доходила до Битрикс24 как команда снять текущую фотографию. Одиночный PATCH /v1/users/:id возвращал 200 success:true, хотя фотография удалялась.

Стало

Такие значения отклоняются до вызова Битрикс24 с INVALID_PARAMS. Одиночный PATCH /v1/users/:id возвращает 400 success:false; пакетные маршруты сохраняют свои конверты ошибок. На глобальном POST /v1/batch так же отклоняется personalPhoto: null: кодировщик подзапроса превращает null в пустое значение строки запроса, то есть в ту же команду снять фотографию. На PATCH /v1/users/:id и в пакете по сущности null нового отказа не получает. Чтобы оставить фотографию без изменений, не передавайте personalPhoto. На CREATE в POST /v1/users пустая строка не получает новый отказ; рабочая inline-пара и отдельная проверка приглашения сотрудника не изменились.

FIX-0901-3: описания лимита частоты называют долю реплики, а не только общий кап

Было

Описания POST /v1/feedback в путеводителе GET /v1/guide, в блоке feedback.limits ответа GET /v1/me и в схеме GET /v1/openapi.json называли лимит числом — «5 запросов в минуту». Запросы обслуживает несколько реплик бэкенда, каждая держит свою долю лимита, поэтому клиенту приходило меньше обещанного, и заголовок X-RateLimit-Limit расходился с описанием. То же расхождение было на страницах справки по отправке обращения и загрузке вложения, а в схеме — у пяти операций раздела Коворк.

Стало

Описания называют число и рядом поясняют, что предел делится между репликами, а действующее значение приходит в заголовке X-RateLimit-Limit. У операций Коворк в схеме описание отказа 429 приведено к той же форме, что уже стоит на страницах справки. Одна общая строка 429 на странице обращений разделена на две — у создания обращения и у загрузки вложения счётчики разные.

Влияние на интеграторов

Значения лимитов не менялись, поведение прежнее. Клиент, который закладывал число из описания, получал отказ раньше ожидаемого — теперь источник действующего значения назван явно: читайте X-RateLimit-Limit из ответа.

BC-0901-4: обновление комментария задачи отклоняет поля только для чтения

Поддержка старого формата до: не предусмотрена

Было

На старой карточке PATCH /v1/tasks/:taskId/comments/:id, которому клиент возвращал объект комментария из GET с изменённым message, мог ответить HTTP 200 и передать в Битрикс24 поля только для чтения. Элемент POST /v1/tasks/:taskId/comments/batch мог аналогично завершиться с success:true.

Стало

Single PATCH с полем только для чтения возвращает HTTP 400 READONLY_FIELD до обращения к Битрикс24. Для успешного обновления комментария старой карточки запросом только с {message} статус ответа остаётся HTTP 200; на новой карточке сохраняется 410 GONE.

В custom batch верхний ответ остаётся HTTP 200 с success:true, а запрещённый элемент получает success:false и error:READONLY_FIELD, не отправляется в Битрикс24 и не мешает выполнению валидных соседних элементов.

Что делать интеграторам

Для single PATCH формируйте новое тело только с {message} и не отправляйте обратно поля ответа id, taskId, authorId, createdAt или их aliases. В custom batch оставьте lowercase id селектором комментария и передавайте рядом только message: удалите дополнительный ID и остальные поля только для чтения. Клиентам, которые уже отправляют только документированные writable-поля, ничего менять не нужно.

FIX-0901-5: ошибка 401 TOKEN_MISSING называет причину на всех маршрутах V1

Было

Причину, по которой у личного ключа нет вебхука Битрикс24, ответ называл только на маршрутах сущностей (/v1/deals, /v1/tasks и т. п.) и в POST /v1/batch. Остальные семейства — чаты, списки, почта, скрам, заметки, открытые линии, боты, пользовательские поля и прочие — отвечали плоским текстом «API key has no tokens configured.» без поля error.details. Один и тот же сломанный ключ на /v1/deals получал объяснение, а на /v1/chats — текст, из которого не следовало ничего.

Стало

Ответ 401 TOKEN_MISSING устроен одинаково на всех маршрутах V1: личный ключ получает причину в error.details.reason и текст с нужным действием, ключ приложения — указание на недостающий OAuth-шаг. Ответ остаётся HTTP 401, набор кодов не менялся.

Влияние на интеграторов

Разбирайте error.details.reason — контрактом является он, а не error.message. Текст сообщения на этих маршрутах изменился, и код, сравнивающий его со строкой «API key has no tokens configured.», перестанет попадать; такой разбор был ненадёжен и раньше. Причины перечислены в ответе GET /v1/me в блоке b24Credentials.

FIX-0901-6: прямую загрузку можно повторить после удаления объекта

Было

После успешного DELETE /v1/storage/objects/{key} новая прямая загрузка POST /v1/storage/objects/upload с тем же ключом отвечала 409 STORAGE_KEY_DELETED до физической уборки объекта через 30 дней.

Стало

Для объекта приложения в состоянии COMPLETED без multipart-сессии прямая загрузка отвечает HTTP 200, сохраняет те же id и visibility и записывает новое содержимое. Удалённая публичная ссылка с этим id снова становится доступна с новыми байтами. Это не восстановление прежнего содержимого.

Путь B остаётся отключённым и отвечает 503 STORAGE_PRESIGNED_UPLOAD_DISABLED без URL. Путь C, удалённые объекты PENDING и объекты с multipart-сессией по-прежнему отвечают 409 STORAGE_KEY_DELETED. Сам DELETE и последующее чтение без новой подходящей прямой загрузки по-прежнему дают 410 STORAGE_OBJECT_DELETED в течение периода хранения.

Влияние на интеграторов

Менять запрос не нужно. После удаления подходящего объекта отправьте новые байты обычной прямой загрузкой с тем же ключом.

FIX-0901-7: временный отказ склада образов больше не помечает galaxy-приложение сломанным

Было

POST /v1/infra/servers с полем source, упёршийся в 502 GALAXY_BASE_IMAGE_UNAVAILABLE или в нехватку места на хосте, переводил слот в status=error — при том что тот же отказ на POST /v1/infra/servers/:id/deploy статус не трогал, а текст ошибки обещал, что слот, контейнер и том /data целы. Повтор деплоя error не снимал: GET продолжал отдавать сломанный слот, и приложение переставало удерживать свой хост от засыпания.

Стало

Оба временных отказа оставляют слот в status=provisioning; причина по-прежнему видна в provisionError ответа GET /v1/infra/servers/:id. Повтор того же деплоя на слоте, сломанном такой причиной, снимает error в начале попытки. Если слот бросить, платформа через ~20 минут всё равно завершит его ошибкой, но сохранит настоящую причину вместо общей DEPLOY_INCOMPLETE. Подсказки в error.hint, чеклист в /v1/me и документация больше не называют срок «2–3 минуты»: окно недоступности склада бывает и часовым.

BC-0901-8: Обычный чат-бот блокирует удаление ключа

Поддержка старого формата до: не предусмотрена

Было

Удаление API-ключа могло удалить привязанного обычного чат-бота вместе с токеном, нужным для штатного снятия регистрации на портале.

Стало

Удаление ключа с обычным чат-ботом, включая отключённую строку Bot Platform, возвращает 409 KEY_HAS_LINKED_AGENT. Возьмите числовой bitrixBotId из details.bots ответа 409, затем снимите регистрацию через DELETE /v1/bots/:botId или перенесите бота на другой ключ.

FIX-0901-9: загрузка файлов и AI-запросы получили отказ 429 при перегрузке памяти

Было

POST /v1/files/upload (до 70 МБ), POST /v1/note/documents/{id}/files (до 40 МБ), POST /v1/chat/completions (до 30 МБ) и POST /v1/audio/transcriptions (аудио до 25 МБ) принимали любое число одновременных крупных тел. При нескольких параллельных загрузках предельного размера запросы могли обрываться без ответа — в том числе чужие, идущие в этот момент.

Ещё одно наблюдаемое отличие: запрос без ключа или с неверным ключом на этих маршрутах мог получить 400 (неразобранный JSON), 413 (размер) или 415 (тип данных) — то есть ответ о теле раньше ответа о ключе.

Стало

Одновременные тела крупнее 1 МиБ ограничены общим объёмом. Запрос сверх лимита получает 429 с кодом LARGE_BODY_BACKEND_BUSY и заголовком Retry-After: 5 — до обращения к Bitrix24, то есть без побочных эффектов, и одинаково независимо от того, объявлен ли Content-Length:

JSON
{
  "success": false,
  "error": {
    "code": "LARGE_BODY_BACKEND_BUSY",
    "message": "Too many large request bodies are being processed. Retry in a few seconds.",
    "retryAfter": 5
  }
}

На OpenAI-совместимых POST /v1/chat/completions, POST /v1/ai/chat/completions и POST /v1/audio/transcriptions (+ POST /v1/ai/audio/transcriptions) конверт — как у остальных ответов этой поверхности, то есть без success и с кодом в нижнем регистре:

JSON
{
  "error": {
    "message": "Too many large request bodies are being processed. Retry in a few seconds.",
    "type": "rate_limit_exceeded",
    "code": "large_body_backend_busy",
    "retryAfter": 5
  }
}

Запрос без ключа или с неверным ключом теперь получает 401 на всех этих маршрутах — проверка ключа идёт до чтения тела. Успешные запросы и тела до 1 МиБ не затронуты; повтор через 5 секунд проходит штатно.

⚠️ На транскрипциях лимит считается по объявленному Content-Length. Загрузка без него (Transfer-Encoding: chunked) в общий объём не попадает и ограничена только собственным потолком в 25 МБ на файл.

FIX-0901-10: создание Hermes-агента больше не падает из-за размера стартового пакета

Было

Создание Hermes-агента завершалось ошибкой сразу после шага выдачи сервера. Виртуальная машина не создавалась вовсе, агент оставался в состоянии ошибки с кодом инцидента, а поле сервера в карточке оставалось пустым. Повторные попытки давали тот же результат. Причина — стартовый пакет для облака вырос и перестал помещаться в предел, который облако отводит на такие пакеты.

Стало

Служебные комментарии больше не уезжают в облако вместе с установочным скриптом, поэтому стартовый пакет снова помещается в предел с большим запасом, и виртуальная машина создаётся. Сам установочный скрипт и все прочие способы его доставки не изменились. Дополнительно перед обращением к облаку добавлена проверка размера: если пакет когда-нибудь снова вырастет сверх предела, запрос в облако не отправляется вовсе, а Вайбкод возвращает привычную ошибку провайдера с кодом инцидента.

NEW-0901-11: редактор бизнес-процессов доступен через API

Появился раздел /v1/workflow-designer/* — работа с шаблонами нового редактора бизнес-процессов на языке самого редактора: блоки, связи, каталог типов блоков, проверка графа, черновики и публикация. Это не то же самое, что /v1/bizproc-templates: тот раздел читает и пишет строки шаблонов, где граф лежит непрозрачным блоком.

Требуется scope bizprocdesigner — отдельный от bizproc. Добавьте его в ключ и перевыпустите ключ.

Запись идёт двумя шагами. POST /v1/workflow-designer/templates/{id}/draft сохраняет черновик: работающий бизнес-процесс продолжает выполняться по опубликованной версии, и черновик можно показать человеку, переписать или бросить. POST /v1/workflow-designer/templates/{id}/publish делает черновик рабочей версией — необратимо, и вместе с публикацией удаляются все черновики шаблона, включая чужие. Публикация требует draftFingerprint из GET /v1/workflow-designer/templates/{id}: так публикуется ровно тот граф, который вы прочитали и показали человеку, а переписанный кем-то черновик отбивается вместо тихой подмены.

Начинать работу стоит с GET /v1/workflow-designer/capabilities: он отвечает, доступен ли редактор этому порталу и этому ключу, а если нет — называет причину (DESIGNER_NOT_SUPPORTED — портал ещё не обновлён до версии, отдающей редактор через API, либо модуля нет, DESIGNER_UNAVAILABLE — редактор выключен или не входит в тариф, SCOPE_DENIED — у ключа нет scope, DESIGNER_REQUIRES_WRITE_KEY — ключ только на чтение, а операция пишет, DESIGNER_PROBE_FAILED — проверка не завершилась и состояние редактора неизвестно). Различить эти состояния по отказу самих методов нельзя, поэтому проверка вынесена в отдельный вызов.

Ключ только на чтение раздел обслуживает: граф, каталог блоков, поля документа, инструкцию портала и validate читает и он. DESIGNER_REQUIRES_WRITE_KEY приходит только на трёх пишущих операциях — создание шаблона, сохранение черновика и публикация.

Затронутые эндпоинты: GET /v1/workflow-designer/capabilities, POST /v1/workflow-designer/templates, GET /v1/workflow-designer/templates/{id}, GET /v1/workflow-designer/templates/{id}/brief, GET /v1/workflow-designer/templates/{id}/blocks, GET /v1/workflow-designer/templates/{id}/blocks/{blockId}, GET /v1/workflow-designer/templates/{id}/document-fields, POST /v1/workflow-designer/templates/{id}/validate, POST /v1/workflow-designer/templates/{id}/draft, POST /v1/workflow-designer/templates/{id}/publish

BC-0901-12: удаление сервера удаляет и привязанного к нему агента

Поддержка старого формата до: не предусмотрена

Было

DELETE /v1/infra/servers/:id удалял только сервер. Агент, который на нём работал, оставался в платформе Вайбкод как живой: его состояние по-прежнему читалось «работает», бот оставался зарегистрированным на портале Битрикс24, ключ агента — действующим, а ссылка агента вела на сервер, которого уже нет.

Стало

Вместе с сервером удаляется и привязанный к нему агент. Запись агента помечается удалённой, бот снимается с портала Битрикс24, ключ агента отзывается, карточка приложения снимается с каталога Битрикс24. Успешное удаление отвечает прежним HTTP 200 с телом {"success": true}.

Появился один новый исход. Если в момент вызова того же агента параллельно менял другой запрос, ответ — 409 AGENT_DELETE_CONFLICT в обычной форме отказа {"success": false, "error": {"code": "...", "message": "..."}}. Тогда не удалён и сам сервер: он продолжает работать и тарифицироваться, а запрос нужно повторить.

Что делать интеграторам

Сценарий, который рассчитывал пережить удаление сервера и переиспользовать агента, надо перестроить: агент создаётся заново после создания нового сервера. Код AGENT_DELETE_CONFLICT обрабатывается как повторяемый — сервер цел, повторите тот же запрос. Удаление сервера без агента и удаление сервера вида GALAXY_APP работают как раньше.

2026-08-31

FIX-0831-1: сервер, у которого не поднялся агент, больше не числится рабочим

Было

Если установка агента при старте машины обрывалась, виртуалка всё равно доходила до running, и сервер так и оставался в этом состоянии. В карточке и в списке серверов приходил status: "running" с пустыми provisionError и provisionErrorCode — по ответу этих эндпоинтов отличить рабочий сервер от неработающего было нельзя, а тарификация продолжалась.

JSON
{ "status": "running", "blackholeStatus": "NONE", "provisionError": null, "provisionErrorCode": null }

Стало

Такой сервер переводится в error с объяснением и новым кодом provisionErrorCode: "AGENT_NEVER_CONNECTED", и тарификация закрывается.

JSON
{ "status": "error", "blackholeStatus": "NONE", "provisionError": "Сервер загрузился, но агент так и не подключился — …", "provisionErrorCode": "AGENT_NEVER_CONNECTED" }

Влияние на интеграторов

Менять ничего не нужно: error — существующий статус, новых полей не появилось. Что стоит учесть:

  • Цикл ожидания готовности сервера теперь завершается: running при blackholeStatus: "NONE" больше не длится вечно. Если ждёте пары running + CONNECTED, добавьте выход по error.
  • Восстановление — POST /v1/infra/servers/:id/repair: он ставит агента заново по SSH. Пересоздавать сервер стоит, только если починка не помогла.
  • POST /v1/infra/servers/:id/start на таком сервере отвечает 422 с кодом AGENT_NEVER_CONNECTED. Статус ответа не изменился: по такой машине /start и раньше отдавал 422 (SERVER_WRONG_STATE, потому что она числилась running) — меняется код, не статус. Запуск тут не чинит ничего, а объяснение стирает — поэтому он и отклоняется. Клиент, который перезапускает упавшие серверы по расписанию, должен читать код и звать починку.
  • Три операции жизненного цикла на такой машине переходят из успешного ответа в отказ, потому что требуют running: POST /v1/infra/servers/:id/stop и POST /v1/infra/servers/:id/reboot отвечают 422 с кодом SERVER_WRONG_STATE, а POST /v1/infra/servers/:id/sleep-now — 400 с кодом NOT_RUNNING. Выключать такую машину и не нужно: тарификация закрывается вместе с вердиктом, так что плановая остановка ради экономии на ней ничего не экономит. Рабочие действия — repair (починить) и DELETE /v1/infra/servers/:id (удалить); их же перечисляет availableActions в ответе.
  • Вердикт выносится только что созданным серверам, уже работающие он не трогает. Перед ним платформа сверяется со шлюзом, поэтому живой туннель с потерянным уведомлением чинится, а не помечается ошибкой. Если агент подключится позже, сервер сам вернётся в running, а provisionError и provisionErrorCode очистятся.

FIX-0831-2: отказ выписки на бесплатном тарифе называет причину вместо «повторите через минуту»

Было

Портал с оплаченной подпиской на Маркетплейс, но на бесплатном тарифе Битрикс24, получал на выписку ключа и на установку приложения 502 CONNECTOR_REST_UNAVAILABLE с текстом «Битрикс24 сейчас не выдал ключ. Повторите попытку через минуту». Повтор не помогал никогда: на бесплатном тарифе Битрикс24 не считает подписку действующей и отказывает в REST целиком, а ответ об этом не сообщал.

Стало

Та же ситуация отвечает 402 B24_PAID_TARIFF_REQUIRED. Поле userMessage называет оба условия доступа и говорит, какое из них не выполнено: подписка у портала есть, нужен платный тариф Битрикс24. Успешные ответы не изменились и по-прежнему отдают HTTP 200, прочие причины отказа по-прежнему отвечают 502 CONNECTOR_REST_UNAVAILABLE.

FIX-0831-3: поиск и исследование через провайдера Linkup

Было

Любой вызов POST /v1/search или POST /v1/research с provider: "linkup" отклонялся провайдером: платформа не передавала обязательное поле outputType. По этой же причине не проходила проверка собственного ключа Linkup — подключение BYOK-ключа возвращало INVALID_CREDENTIAL с текстом HTTP 400 даже для действующего ключа.

Стало

Вызовы к Linkup выполняются, подключение BYOK-ключа отражает реальное состояние ключа: действующий ключ сохраняется, недействующий отклоняется.

В /v1/search поле answer заполняется при include_answer: true и равно null при include_answer: false; в этом случае текст страницы приходит в results[].content. Поле results[].published_date у Linkup всегда null — провайдер не отдаёт дату публикации, и матрица возможностей в GET /v1/search/providers теперь сообщает об этом честно.

FIX-0831-4: бесшовный вход в кабинет по подписанной форме портала

Было

Переход из каталога приложений Bitrix24 в кабинет открывал адрес платформы в новой вкладке. Пользователь попадал на экран входа, а после входа — на выбор портала, даже если он только что нажал кнопку внутри своего портала.

Стало

У лицензионно-подписанного канала появилась вторая поверхность перехода — cabinet. Модуль портала отправляет подписанную форму с BX_VIBE_SURFACE=cabinet на новый эндпоинт POST /microservice/open-cabinet (принимается и вариант с завершающим слешем), и пользователь оказывается в кабинете авторизованным, без экрана входа и без выбора портала.

Поля поверхности: BX_VIBE_AUD — адрес платформы, которому адресована форма (обязателен, сверяется как есть); BX_USER_EMAIL — почта пользователя портала; BX_VIBE_NETWORK_STATE — признак состояния сети, только для коробки; BX_VIBE_TARGET — необязательный относительный путь внутри кабинета, по умолчанию /dashboard. Набор и порядок подписываемых полей, требования к каждому и коды отказов — в контракте сторон.

Кука сессии ставится не в ответ на межсайтовый запрос: успешный ответ — страница на домене платформы с автоотправляемой формой, которая односайтовым запросом обменивает одноразовый код на куку в POST /api/cabinet/handoff и переводит пользователя на целевой путь. Код действует 120 секунд и расходуется однократно.

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

Поверхность за выключателем и по умолчанию выключена: пока он выключен, эндпоинт отвечает 404. Поверхность app (POST /microservice/open-app) не изменилась.

FIX-0831-5: открытие приложения из каталога Битрикс24 всегда идёт от учётной записи

Было

Сотрудник портала без учётной записи Вайбкод открывал приложение «на подпись портала»: платформа отдавала одноразовый init-код с пустым идентификатором пользователя Вайбкод, и приложение получало личность, за которой не стояло ни одной записи платформы. На коробке такой сотрудник открывал приложение как гость с именем и отделами, прочитанными из Битрикс24; в облаке платформа перед редиректом спрашивала портал user.get, живой ли это сотрудник.

Стало

У каждого открытия есть учётная запись, и __init всегда несёт непустого пользователя. В облаке запись создаётся по подписанным BX_NETWORK_USER_ID и BX_USER_EMAIL. На коробке личность подтверждает сам сотрудник: платформа показывает серверный экран с кодом на почту из подписанной формы, а по верному коду тем же запросом привязывает участника портала к профилю Bitrix24.Network и открывает приложение. Исходящего user.get перед редиректом больше нет.

Код на почту отправляет и проверяет сам Bitrix24.Network — двумя доверенными операциями. Первая принимает адрес: Сеть находит или заводит по нему профиль и шлёт код штатным подтверждением почты. Вторая принимает тот же адрес и код и возвращает профиль по подтверждённой почте. Платформа писем не отправляет и кода не хранит. Срок жизни кода и число попыток задаёт Сеть; свои потолки на частоту отправок платформа держит поверх.

BX_USER_EMAIL переехал из полей поверхности cabinet в общую часть подписываемого набора: обе платформы отправляют его на обеих поверхностях, сразу после BX_NETWORK_USER_ID (или после BITRIX_USER_ID, если сетевого идентификатора нет) и до BX_VIBE_SURFACE. Поле необязательно — форма без него обслуживается как раньше, поэтому модуль старой версии продолжает работать без правок. Успешный ответ остаётся 302 на адрес приложения с __init.

Ветви создания учётной записи — за выключателем на стороне платформы, по умолчанию выключенным: пока он выключен, открытие без учётной записи отвечает страницей отказа со ссылкой на обычный вход, и частично созданных профилей не остаётся.

Страница «Авторизовать приложение» переделана: одна кнопка вместо кнопки и тихой ссылки, а после авторизации страница сама переводит в приложение — закрывать вкладку и открывать приложение из каталога заново не нужно. Для этого iframe должен разрешать allow-popups. Экран подтверждения почты новой вкладки не открывает: поле кода раскрывается кнопкой на той же странице, и пока платформа проверяет код и подключает приложение, на экране виден лоадер. Сменить адрес на этом экране больше нельзя — подтверждается только почта из подписанной формы, а альтернатива ей одна: самостоятельный вход на платформе по ссылке с того же экрана. Полный набор полей, экраны подтверждения почты и точки продолжения — в контракте сторон.

FIX-0831-6: Пакетное создание линий телефонии отдаёт адресуемый номер в results[].id

Было

POST /v1/telephony-lines/batch с action: "create" отвечал 200, но в results[].id приходил внутренний числовой идентификатор строки Битрикс24. Подставить его в PATCH или DELETE /v1/telephony-lines/:number было нельзя — линия ищется по номеру, и запрос отвечал 422. Одиночный POST /v1/telephony-lines при этом уже отдавал правильный номер, то есть две двери на одно и то же создание расходились в ответе.

Стало

results[].id — тот же номер, что передан в поле number этого элемента, и то же значение, что отдаёт одиночный POST /v1/telephony-lines. Значение возвращается без изменений, включая номера с + и другими символами. Ответ по-прежнему 200, состав остальных полей не менялся.

FIX-0831-7: повторный вход в Коворк на бесплатном месте больше не отбивается

Было

Если человек отменял бесплатную подписку Коворка, повторный вход десктопа отбивался 403 COWORK_SUB_CANCELLED: отменённое место не оживало, а ключ выдаётся только активному. Из приложения выхода не было — браузерного пути к возврату бесплатного места в этом сценарии не существует, и человек оставался без входа насовсем.

Стало

Место с нулевой ценой возвращается в активное состояние прямо на входе, и ключ выдаётся. Платное место по-прежнему отвечает 403 COWORK_SUB_CANCELLED / COWORK_SUB_PAUSED — его возобновление это оплата, и оно делается осознанно в кабинете.

FIX-0831-8: `TOKEN_MISSING` для личного ключа называет причину отказа в вебхуке

Было

Если у личного ключа (vibe_api_*) не было вебхука Битрикс24 из-за того, что портал отказал владельцу ключа в праве на входящие вебхуки, либо прошлая попытка выпустить вебхук завершилась ошибкой, ответ 401 TOKEN_MISSING всё равно называл общую причину (например, WEBHOOK_NOT_CONFIGURED) и отправлял читать GET /v1/me, не объясняя, что вебхука не будет и кто должен это исправить.

Стало

В этих двух сценариях error.details.reason возвращает WEBHOOK_MINT_REFUSED_BY_PORTAL (нужны действия администратора портала) или WEBHOOK_MINT_FAILED (платформа повторяет попытку сама), а error.message называет причину и адресата действия явно. Для WEBHOOK_MINT_REFUSED_BY_PORTAL совет переподключить ключ (POST /api/keys/:id/reconnect) больше не приходит — платформа выпустит вебхук автоматически, как только администратор портала откроет право на входящие вебхуки, и повторный запрос переподключения не поможет. Код ответа, HTTP-статус и структура error не изменились.

Влияние на интеграторов

Обрабатывайте WEBHOOK_MINT_REFUSED_BY_PORTAL и WEBHOOK_MINT_FAILED как отдельные значения details.reason наравне с уже документированными — не пересоздавайте ключ и не дёргайте reconnect в цикле по этим причинам, ждите действия администратора портала или автоматический повтор платформы.

Отдельно про текст сообщения. error.message теперь ветвится по причине и по тому, доступно ли этому ключу переподключение, — и это касается НЕ только двух новых причин. У ключа без скоупов Битрикс24 (VIBE_SCOPES_ONLY) текст стал своим, а ключам, которым платформа переподключение отобьёт (коворк-ключи, ключи приложений, ключи с привязанным сервером или живым агентом), вместо совета переподключиться приходит совет создать новый ключ. Значения details.reason в этих сценариях прежние. Если ваш код разбирает error.message поиском по строке — переключитесь на details.reason: текст сообщения контрактом не является и меняется.

FIX-0831-9: пополнение вайбов коробочному порталу открыто на международной установке

Было

Коробочному порталу на международной установке пополнение всегда отвечало отказом BOX_TOPUP_NOT_AVAILABLE. Превью подписки GET /v1/cowork/subscription/preview возвращало такому аккаунту topUpAvailable: false, а каталог пакетов приезжал пустым.

Стало

Коробочный портал на международной установке пополняется наравне с облачным — получает каталог пакетов, ссылку на оплату и topUpAvailable: true в превью подписки. Отказ BOX_TOPUP_NOT_AVAILABLE остаётся только там, где пополнение коробочным порталам закрыто для региона портала. Формат ответа не меняется, успешный ответ остаётся HTTP 200.

2026-08-30

BC-0830-1: Предподписанная single-PUT загрузка временно отключена

Поддержка старого формата до: не предусмотрена

Было

Путь B выдавал предподписанную PUT-ссылку для single-PUT загрузки и публиковал её через /complete.

Стало

Путь B (создание URL и подтверждение) fail-closed отвечает 503 STORAGE_PRESIGNED_UPLOAD_DISABLED. Для новых загрузок используйте прямую загрузку (путь A) или multipart (путь C); legacy PENDING-брони не публикуются и не освобождаются GC автоматически.

BC-0830-2: переименование приложения больше не отчитывается успехом без подтверждения Битрикс24

Поддержка старого формата до: не предусмотрена

Было

При переименовании через ключ разработчика PATCH /v1/apps/{id} снимал прежнюю привязку пункта меню и вешал новую. Если Битрикс24 отклонял новую привязку после успешного снятия, ответ был 502 BITRIX_PARTIAL_REBIND, а пункт оставался снятым.

На облачном OAuth-пути тот же метод в другом случае вёл себя иначе: если Битрикс24 не подтвердил ни снятие прежней привязки, ни новую, ответ был 200 OK с предупреждением, и новое имя записывалось. Пункт меню при этом либо исчезал с портала, либо оставался с прежней подписью, а повторный запрос уже не перепривязывал его — платформа считала имя актуальным.

Стало

При переименовании через ключ разработчика платформа один раз возвращает прежнюю подпись. Успешно восстановленные места перечислены в новом поле error.restored и ручного вмешательства не требуют. Поле error.unbound перечисляет места, которые нужно проверить на портале: оставшиеся снятыми, а также те, по которым Битрикс24 не подтвердил снятие. Новые поля error.bitrixCodes и error.bitrixStatuses передают санитизированную диагностику каждого отклонённого вызова.

Облачный OAuth-путь в случае, когда Битрикс24 не подтвердил ни снятия, ни новой привязки, теперь отвечает 502 BITRIX_PARTIAL_REBIND и НЕ записывает новое имя.

Что делать интеграторам

Считайте 502 BITRIX_PARTIAL_REBIND признаком того, что имя не сохранено, и повторяйте запрос — повтор снова запускает перепривязку. Прежний 200 OK в этом сценарии успех не означал. Восстанавливать вручную через POST /v1/placements/bind нужно только места из error.unbound.

FIX-0830-3: у моделей `bitrix/*` поток, который не начался, тоже завершается повторяемой ошибкой

Было

Если апстрим модели bitrix/* не отдавал заголовки ответа, платформа молча отправляла запрос второй раз и ждала ещё один полный бюджет. Клиент в это время не получал ничего — до двух полных бюджетов тишины, — а затем видел общий отказ провайдера, по которому нельзя отличить зависшую установку потока от любой другой неопознанной ошибки.

data: { "error": { "code": "ai_provider_unavailable", "type": "server_error", "retryable": true, "retryAfter": 6 } }
data: [DONE]

Стало

Установка потока у этих моделей делает одну попытку с общим бюджетом. Если заголовки не пришли за отведённое время, ожидание не удваивается, а поток завершается тем же событием stream_idle_timeout, что и замолчавший в середине ответа, — по нему видно, что молчал именно поток.

data: { "error": { "code": "stream_idle_timeout", "type": "server_error", "retryable": true, "retryAfter": 7 } }
data: [DONE]

Ожидание больше не удваивается невидимо, а причина отказа названа точно. Моделей других поставщиков изменение пока не касается: там установка потока по-прежнему идёт двумя попытками и заканчивается общим ai_provider_unavailable.

2026-08-29

FIX-0829-1: признак полей для импорта в guide

Было

В GET /v1/guide подробный контракт data.entities[].fieldsDetailed не сообщал, можно ли задать поле при импорте.

Стало

В data.entities[].fieldsDetailed для таких полей приходит importable: true. Ответ остаётся HTTP 200, компактная карта data.entities[].fields не меняется.

NEW-0829-2: ссылка на публичное приложение разворачивается в карточку

Ссылка на приложение в режиме PUBLIC приходила в мессенджер и в чат Битрикс24 голым адресом субдомена. Сборщик превью получал ту же страницу, что и браузер, и показать ему было нечего.

Ссылка на приложение в режиме PUBLIC разворачивается в карточку с названием, описанием и картинкой. Данные берутся из карточки приложения в каталоге; нет названия в каталоге — подставляется имя сервера, нет иконки — картинка платформы. Собирает карточку сам шлюз: приложение запроса не получает, спящий сервер не просыпается. На любой другой политике доступа карточки нет — сборщик превью получает нейтральную страницу без названия, описания и картинки. Уже показанную карточку отозвать нельзя: принимающая сторона кеширует её у себя, поэтому переключение политики на приватную закрывает только новые запросы. Подробнее — Авторизация в приложении на Black Hole.

BC-0829-3: фильтр лидов учитывает семантику стадии

Поддержка старого формата до: не предусмотрена

Было

GET /v1/leads, POST /v1/leads/search и POST /v1/leads/aggregate с фильтром stageSemanticId возвращали UNKNOWN_FILTER_FIELD. Поле stageSemanticId, переданное при создании, обновлении, импорте, в пакетном запросе сущности или общем пакетном запросе, игнорировалось.

Стало

Фильтр принимает P (в работе), S (успех) и F (провал). Сортировка по stageSemanticId также доступна. Руководство, OpenAPI и интерактивный справочник описывают поле как строку только для чтения. groupBy: stageSemanticId по-прежнему не поддерживается. Создание и обновление возвращают READONLY_FIELD, импорт — IMPORT_ITEM_VALIDATION, пакетный запрос сущности — BATCH_ITEM_VALIDATION, общий пакетный запрос — READONLY_FIELD для соответствующего вызова.

Что делать интеграторам

Используйте stageSemanticId только для чтения, фильтрации и сортировки. Удалите поле из тел запросов на создание, обновление и импорт, а также из пакетных запросов. Для группировки выберите другое поле.

FIX-0829-4: списочные вызовы почтовых ящиков и узлов оргструктуры работают в пакетных API

Было

Подвызов {"entity": "mail-mailboxes", "action": "list"} в POST /v1/batch отвечал ошибкой ERROR_METHOD_NOT_FOUND, а тот же список в POST /v1/mail/mailboxes/batch — CALL_FAILED. Так же вёл себя humanresources-nodes. Отдельным запросом GET /v1/mail-mailboxes тот же список отдавал данные.

Стало

Оба пакетных API возвращают записи. Такой подвызов выполняется отдельным запросом к порталу при любом значении limit и расходует свою квоту rate-limit; соседние вызовы других сущностей по-прежнему уезжают одним пакетом. Количество записей у этих двух сущностей приходит всегда, даже с withTotal: false. Действие get у них в пакетном API по-прежнему не отвечает данными — читайте запись её собственным маршрутом. Подробности — в разделе «Известные особенности» страницы «Пакетные операции».

Отдельно изменился текст сообщения у отказавшего подвызова — во всех пакетных вызовах, не только у этих двух сущностей. Раньше туда попадало внутреннее диагностическое сообщение платформы; теперь публикуется только текст, пришедший от Битрикс24, а во всех прочих случаях — фиксированное Internal error. При этом переполнение очереди портала, ожидание в очереди и таймаут портала перестали попадать в общий код и приходят под собственными кодами QUEUE_OVERFLOW, QUEUE_TIMEOUT, BITRIX_TIMEOUT, ERROR_LOOP_DETECTED, RATE_LIMITED, OPERATION_TIME_LIMIT и TOKEN_REFRESH_FAILED, у всех кроме последнего — с полем retryAfter. Их стало можно отличить от внутреннего сбоя. Ответ по-прежнему 200, коды остальных подошибок не менялись.

То же правило действует и когда отказывает пакетный запрос целиком, а не отдельный вызов: текст берётся из ответа Битрикс24, иначе он фиксированный. Статус ответа, код и поле bitrixError при этом не меняются — bitrixError приходит, когда Битрикс24 действительно ответил, даже если текста в его ответе не было.

Одиночные эндпоинты тоже перестали отдавать внутренний текст в одном случае: при отказе TOKEN_REFRESH_FAILED сообщение теперь фиксированное — раньше в него мог попасть кусок ответа портала. Код и статус не изменились.

2026-08-28

FIX-0828-1: остановка сервера больше не тарифицируется как рабочий час

Было

После остановки сервера через POST /v1/infra/servers/{id}/stop переход машины в сон не попадал в журнал состояний. Если почасовое списание за этот час выполнялось с задержкой, оно опиралось на последнюю известную запись — «работает» — и списывало полный рабочий час по рабочей ставке, включая время, когда машина уже была остановлена. Ответ ручки при этом был верным, расхождение проявлялось только в счёте.

Стало

Переход записывается сразу, и час тарифицируется по факту: рабочее время — до остановки, спящее — после. Формат ответа и коды не изменились, HTTP 200 сохраняется.

FIX-0828-2: поздняя загрузка по действующей ссылке сохраняет объект

Было

Если загрузка по подписанной ссылке начиналась позднее часа после её выдачи, служебная бронь могла быть удалена раньше окончания срока ссылки. Последующее завершение отвечало 404, а загруженные байты оставались без объекта в API.

Стало

Пустая бронь сохраняется до максимального срока подписанной ссылки. Ответ успешного завершения не изменился; поздняя загрузка по ещё действующей ссылке остаётся связанной с объектом API.

FIX-0828-3: правила листания в GET /v1/guide приходят одним блоком paginationCanon

Было

Правила листания и подсчёта записей повторялись внутри каждой сущности ответа. Поля operations.search.paginationStability и описание параметра operations.search.params.withTotal несли один и тот же текст у всех сущностей.

Стало

Тот же текст приходит один раз, блоком paginationCanon на верхнем уровне ответа. Формулировки перенесены дословно, ни одно правило не переписано и не сокращено. Одноимённые поля внутри сущностей сохранены и содержат указатель на соответствующий пункт paginationCanon.

Исключение — operations.search.paginationStability.counting. Он по-прежнему приходит у каждой сущности со своим текстом, потому что способ узнать точное количество зависит от того, доступна ли этой сущности операция агрегации.

Влияние на интеграторов

Вызовы работают как прежде, ни одно поле ответа не удалено. Если ваш код читал текст правил из полей сущности, возьмите его из блока paginationCanon. Ответ на полном наборе скоупов стал меньше примерно на 37 процентов.

FIX-0828-4: filter и sort комментариев задач принимают camelCase-имена полей

Было

GET /v1/tasks/:taskId/comments принимал в filter и sort только сырые имена полей Битрикс24. Имена authorId и createdAt, в которых те же поля приходят в ответе, давали 400 UNKNOWN_FILTER_FIELD или 400 INVALID_SORT_FIELD.

Стало

Параметры filter и sort принимают camelCase-имена наравне с сырыми: id, authorId, authorName, createdAt, а sort дополнительно authorEmail. Ограничения по типу карточки не изменились: фильтр по authorName и сортировка по authorName или authorEmail по-прежнему принимаются только на старой карточке, на новой отвечают 400. Если filter называет одно поле дважды в разных написаниях имени или знака равенства, приходит 400 INVALID_FILTER. Неизвестные имена по-прежнему возвращают 400.

Влияние на интеграторов

Для фильтрации и сортировки можно использовать authorId и createdAt — те же имена, в которых эти поля приходят в ответе. Менять существующие запросы с сырыми именами не требуется.

BC-0828-5: Исход deploy можно подтвердить после обрыва связи

Поддержка старого формата до: не предусмотрена

Было

Если связь со шлюзом обрывалась во время deploy, клиент не получал исход и идентификатор операции. Повторный deploy мог встретить EXEC_BUSY, но не отвечал на главный вопрос: завершилась ли первая выкладка.

Стало

POST /v1/infra/servers/:id/deploy сохраняет наблюдаемый исход standalone- и galaxy-выкладки, а GET /v1/infra/servers/:id/operations возвращает последние операции текущего API-ключа даже когда терминальный ответ с operationId был потерян. При GATEWAY_UNREACHABLE, GATEWAY_CONNECTION_TERMINATED, GATEWAY_STREAM_ERROR*, GATEWAY_TIMEOUT*, TUNNEL_NOT_FOUND и post-drop обрыве Galaxy исход сохраняется как unknown, а error.hint требует сначала сверить операцию, сервер и логи. Для standalone это одинаково работает и когда транспорт бросил исключение, и когда шлюз прислал timeout/error кадром внутри exec или upload/download; такой неподтверждённый исход несёт error.retryable: false и не запускает внутренний повтор. При подтверждённом EXEC_BUSY standalone-сервер можно восстановить через /unstick только когда выполняющейся операции уже нет; для общего Galaxy-хоста tenant recovery не поддерживается — нужно подождать и при устойчивом отказе обратиться в поддержку.

Что делать интеграторам

При error.retryable: false не отправляйте тот же deploy повторно: сначала прочитайте GET /v1/infra/servers/:id/operations, состояние сервера и логи. Повтор допустим только после устойчивого исхода failed, когда подсказка ответа явно разрешает тот же запрос. Не удаляйте и не пересоздавайте слот при исходе unknown.

FIX-0828-6: создание комментария задачи возвращает правильный числовой ID

Было

На новой карточке задачи POST /v1/tasks/:taskId/comments и POST /v1/tasks/:taskId/comments/batch с action: create могли вернуть id: null для найденного сообщения, если его числовые идентификаторы пришли строками. При нескольких совпадениях строковое сравнение могло выбрать неверный ID.

Стало

Оба эндпоинта возвращают числовой id найденного сообщения и сравнивают совпавшие ID как числа. Ответ одиночного POST по-прежнему остаётся HTTP 201. Ответ batch остаётся HTTP 200, а успешно созданный элемент — success: true.

Если поиск не находит сообщение или не может определить его однозначно, id: null остаётся допустимым успешным результатом: комментарий уже создан, и повторный запрос может создать дубликат.

Влияние на интеграторов

Менять клиент не требуется. Продолжайте поддерживать number | null и не считайте null признаком неудачного создания.

BC-0828-7: незавершённые публичные загрузки больше не доступны анонимно

Поддержка старого формата до: не предусмотрена

Было

Для объекта PUBLIC в состоянии PENDING запрос GET /v1/public-storage/{portalId}/{objectId} отвечал 302, а HEAD /v1/public-storage/{portalId}/{objectId} — 200. Файл становился доступен без аутентификации до завершения загрузки.

Стало

Оба запроса отвечают 404, пока загрузка остаётся в состоянии PENDING. Анонимный доступ открывается только для объекта PUBLIC с завершённой загрузкой.

Что делать интеграторам

После загрузки файла вызовите POST /v1/storage/objects/complete и публикуйте анонимную ссылку только после успешного ответа.

FIX-0828-8: MCP отдаёт содержимое файла Диска вместо ошибки сети

Было

Действие download инструмента manage_file разбирало ответ GET /v1/files/:fileId/download как служебный JSON, хотя этот запрос отдаёт байты файла. На любом реальном файле агент получал success: false с кодом NETWORK_ERROR и текстом Unexpected token … — то есть первые байты уже скачанного файла выдавались за сбой связи. Рабочего ответа у действия не было.

Стало

Действие возвращает content с содержимым файла в base64, а также contentType, size и filename — симметрично тому, как upload принимает content. Файлы больше 10 МиБ отклоняются кодом FILE_TOO_LARGE, и текст ошибки называет способ получить такой файл: запрос к тому же адресу с тем же ключом API. Ответы с ошибкой остаются обычным конвертом платформы: отсутствующий файл — это ENTITY_NOT_FOUND, а не NETWORK_ERROR. Универсальный call_api, который умеет читать только JSON-конверты, теперь заранее отклоняет известные не-JSON маршруты кодом NON_JSON_RESPONSE_UNSUPPORTED, не выполняя заведомо неверный запрос. Так же заранее он отклоняет кодом REDIRECT_ROUTE_UNSUPPORTED браузерные точки входа потоков OAuth и подключения: они отвечают перенаправлением, и запрос к ним уходил вместе с ключом API, а затем следовал за перенаправлением на адрес, который выбирает не платформа. Путь приводится к каноническому виду до проверки, поэтому его написание через .. больше не обходит отказ, а полный адрес вместо пути отклоняется кодом INVALID_PATH. У действия download идентификатор файла теперь обязан быть целым положительным числом: дробное значение приводило к содержимому другого файла, выданному как успешный ответ. Сам REST-запрос по-прежнему отдаёт бинарные данные, поэтому обычные клиенты работают как раньше.

FIX-0828-9: конкурентные прямые загрузки одного объекта согласованы

Было

Одновременные вызовы POST /v1/storage/objects/upload для одного физического адреса приложения могли перемешать содержимое файла и его метаданные.

Стало

Прямые загрузки пути A с ключом приложения выполняются по очереди. После получения очереди ответ по-прежнему остаётся HTTP 200. Если безопасное право записи не подтверждено в пределах 60 секунд или лимита конкурентности, запрос получает 409 STORAGE_KEY_CONFLICT до записи в объектное хранилище. Повторите весь запрос.

Гарантия относится только к пути A с ключом приложения. Она не расширяет поведение личных ключей (appId = null) и путей B/C.

Влияние на интеграторов

Формат успешного ответа не изменился. При STORAGE_KEY_CONFLICT повторяйте исходный запрос целиком.

FIX-0828-10: спецификация ответов создания и обновления отражает идентификатор

Было

Спецификация OpenAPI и примеры ответов для операций создания и обновления обещали полную сущность там, где API возвращает только data.id.

Стало

OpenAPI и документация описывают фактический ответ с data.id. Статусы HTTP 201 для создания и HTTP 200 для обновления, а также поведение API при выполнении запросов не изменились.

Затронутые эндпоинты: POST /v1/bizproc-activities, PATCH /v1/bizproc-activities/:code, POST /v1/bizproc-robots, PATCH /v1/bizproc-robots/:code, POST /v1/bizproc-templates, PATCH /v1/bizproc-templates/:id, POST /v1/calendar-sections, PATCH /v1/calendar-sections/:id, POST /v1/telephony-lines, PATCH /v1/telephony-lines/:number.

Влияние на интеграторов

Существующие запросы менять не нужно. Повторно сгенерированные клиенты теперь видят фактическую форму успешного ответа.

FIX-0828-11: выполнение команд в galaxy-приложении больше не отклоняется до отправки

Было

POST /v1/infra/servers/{id}/exec для galaxy-приложения отвечал 502 с кодом EXEC_FAILED и сообщением о неподдерживаемом безопасном выполнении в контейнере. Отказ приходил на любом приложении и не зависел ни от команды, ни от состояния контейнера: до контейнера запрос не доходил вовсе.

Стало

Команда выполняется в контейнере приложения, и успешный ответ остаётся HTTP 200 с полями exitCode, stdout, stderr и duration — той же формы, что на остальных серверах. Код CONTAINER_NOT_READY для остановленного или отсутствующего контейнера платформа возвращает там, где может подтвердить это состояние; где не может — причина приезжает в stderr результата, а сам ответ по-прежнему HTTP 200.

FIX-0828-12: Устойчивее установка Node.js 20 в шаблонах рантайма

Node.js 20 в шаблонах node20* теперь устанавливается устойчивее при проблемах с внешней сетью.

Было

Шаблон без ограничения выполнял установку через NodeSource. При сетевой ошибке мог быть установлен Node.js 18, после чего деплой завершался только общей ошибкой проверки версии.

Стало

Установка NodeSource выполняется с ограниченными сетевыми попытками и проверкой целостности. При неуспехе его источник временно изолируется, существовавшая конфигурация сохраняется, и используется проверенный резервный архив Node.js 20; идентификаторы рантаймов и API деплоя не меняются.

FIX-0828-13: ZIP-выкладка больше не просит сменить формат архива, если сервер занят другой командой

Было

Пока на сервере выполнялась другая команда, POST /v1/infra/servers/:id/upload с zip и extract: true и выкладка galaxy-приложения из zip отвечали отказом установки unzip (UNZIP_PREFLIGHT_FAILED / ошибка сборки) и советовали пересобрать архив в .tar.gz.

Стало

Кратковременная занятость канала команд сервер повторяет сам. Если канал так и не освободился, ответ — 409 с кодом EXEC_BUSY (на выкладке galaxy-приложения — GALAXY_APP_BUSY), retryable: true и заголовком Retry-After. Формат архива менять не нужно. Настоящий отказ установить unzip по-прежнему приходит как 502 UNZIP_PREFLIGHT_FAILED и совет использовать .tar.gz.

Влияние на интеграторов

Повторяйте запрос по Retry-After. Не пересобирайте архив из-за EXEC_BUSY / GALAXY_APP_BUSY. Ветка UNZIP_PREFLIGHT_FAILED не менялась.

FIX-0828-14: платёж с завышенным количеством Ꝟ придерживается, а не отклоняется

Было

Событие payment.paid, в котором metadata.tokens БОЛЬШЕ каталожного количества, получало HTTP 400 с кодом TOKENS_MISMATCH. Отказ терминальный: повтора по этому событию не будет, а деньги у плательщика уже списаны — начисление не происходило никогда и ни в одной очереди разбора платёж не появлялся.

Стало

Такое событие принимается как придержанное: HTTP 200, тело { "ok": true, "held": true, "reason": "TOKENS_ABOVE_CATALOG", "vendor_reason": "catalog-drift-over" }. Ꝟ по-прежнему НЕ начисляются — платёж встаёт в очередь разбора, как и придержание TOKENS_BELOW_CATALOG в обратную сторону. Значение TOKENS_ABOVE_CATALOG в поле reason — новое; направление разведено отдельным словарным именем, потому что суммы к довыдаче у переплаты и недоплаты разные.

Направление вниз (TOKENS_BELOW_CATALOG) не изменилось. Нечитаемое количество по-прежнему отклоняется кодом BAD_TOKENS.

FIX-0828-15: ключ владельца в самоудалении больше не вызывает методы Битрикс24 через V1

Было

Ключ владельца портала с запланированным самоудалением мог продолжать вызывать часть методов Битрикс24 через V1. Ограничение действовало не для всех сгенерированных и рукописных обёрток, методов REST 3.0, прямых вызовов и пакетных запросов.

Стало

Все V1-методы, которые по APP/OAuth-ключу портала отправляют запросы в Битрикс24, возвращают 503 user_self_deletion_pending и Retry-After до отправки запроса. Это относится, в частности, к GET /v1/lists, GET /v1/calendar/settings, GET /v1/chats/recent, GET /v1/workday/status, GET /v1/applications, POST /v1/apps, методам обновления тарифа и активации trial, а также POST /v1/batch.

Для READONLY-ключа владельца в самоудалении этот 503 имеет приоритет над WRITE_BLOCKED_READONLY_KEY. Полностью аутентифицированный ключ активного владельца по-прежнему получает READONLY-запрет; более ранние ошибки аутентификации и состояния аккаунта сохраняют свои коды.

Мета-методы V1, включая обычный GET /v1/me без refresh=tariff, MANAGEMENT control-plane и методы, работающие только с данными платформы Вайбкод, не замораживаются этим изменением.

Влияние на интеграторов

Менять интеграцию не требуется. При ответе 503 повторяйте запрос не раньше значения Retry-After.

BC-0828-16: include требует право на связанную сущность

Поддержка старого формата до: не предусмотрена

Было

Параметр include не проверял, есть ли у ключа право на ту сущность, которую он достаёт. Право проверялось только у сущности, которую вы читаете. Поэтому ключ с одним правом tasks через GET /v1/tasks/{id}?include=responsible получал полную карточку сотрудника, хотя на /v1/users тот же ключ отвечал отказом.

Стало

Право проверяется у обеих сторон связи. Не хватает права на связанную сущность — запрос отклоняется с кодом SCOPE_DENIED и HTTP 403, в тексте названо недостающее право и связь, из-за которой оно понадобилось.

Затронуты связи, у которых стороны требуют разных прав: responsible и creator у задач и owner у рабочих групп требуют user, catalog у разделов товаров требует catalog. Связи внутри одного права — например preset у реквизитов или parentSection у разделов товаров — работают как раньше.

Если ваша интеграция пользуется такой связью, добавьте недостающее право ключу: набор прав правится у ключа в кабинете, перевыпускать его не нужно.

Затронутые эндпоинты: любой эндпоинт, принимающий include — чтение по идентификатору, выдача списка и POST /search. Общее описание механизма — включение связанных записей.

NEW-0828-17: include для реквизитов, рабочих групп и разделов товаров

Три справочные сущности получили связи, поэтому параметр include на них теперь объявлен и работает. У реквизитов появилась связь preset — шаблон реквизитов, определяющий набор полей. У рабочих групп — owner, карточка сотрудника-владельца группы. У разделов товаров сразу две: catalog — торговый каталог, к которому относится раздел, и parentSection — родительский раздел, он приходит пустым у раздела верхнего уровня.

Связанная запись, как и раньше, лежит в поле _included рядом с самой записью, работает на выдаче списка, на чтении по идентификатору и в теле POST /search. Ограничения общие для механизма: не больше трёх связей на запрос, а на выборке больше 200 записей связи не разрешаются, и ответ помечается includeSkipped.

Остальные справочники по-прежнему include не объявляют, и это не упущение. Стадии направления сделок Битрикс24 отдаёт строковым кодом, а не идентификатором записи справочника, поэтому связать их с элементом справочника нечем. У элемента справочника номер воронки имеет смысл только в паре с типом объекта CRM, и один и тот же номер принадлежит одновременно направлению сделок и воронке смарт-процесса. У шаблона документа идентификатор файла не адресуется методами Диска, а нумератор отдельной сущностью в API не представлен. Список участников рабочей группы доступен отдельной ручкой, а не через include.

Затронутые эндпоинты: GET /v1/requisites, GET /v1/workgroups, GET /v1/product-sections, их чтение по идентификатору и POST /search. Общее описание механизма — включение связанных записей.

FIX-0828-18: select вместе с include больше не обнуляет связь на списке и в поиске

Было

Если в одном запросе к списку или к POST /search стояли и select, и include, а поле-ссылка в select названо не было, связанная запись приходила пустой. Например, GET /v1/deals?select=id,title&include=company отдавал _included.company пустым, хотя без select та же связь разрешалась. На чтении по идентификатору проблемы не было.

Стало

Связь разрешается до того, как ответ урезается до запрошенных полей, а поле-ссылка добирается во внутренний запрос само. Публичный ответ при этом не расширяется: в нём остаются только названные вами поля плюс блок _included.

Затронутые эндпоинты: выдача списка и POST /search у любой сущности со связями. Общее описание механизма — включение связанных записей.

NEW-0828-19: профиль текущего сотрудника появился в спецификации и справочнике

Метод GET /v1/users/me работал и раньше, но не был объявлен в GET /v1/openapi.json, поэтому не попадал ни в справочник API, ни в карту эндпоинтов. Клиент, сверявшийся со спецификацией, не находил метод и делал вывод, что его нет.

Теперь операция описана: скоуп user, форма ответа как у GET /v1/users/:id плюс трёхзначное поле isAdmin. Само поведение метода не менялось.

Значение isAdmin равно null, когда проверку выполнить не удалось. Профиль при этом возвращается целиком, поэтому права проверяйте условием isAdmin === true, а не отрицанием.

FIX-0828-20: `topUpAvailable` у коробочного аккаунта отражает доступность именно коробочной кассы

Было

GET /v1/cowork/subscription/preview отвечал коробочному аккаунту topUpAvailable: true, если в его регионе была открыта продажа вообще. Продажа для коробочных аккаунтов идёт через отдельную кассу и открывается отдельно, поэтому признак мог обещать пополнение там, где заказ следующим шагом отклонялся.

Стало

Признак считается по доступности коробочной продажи в регионе аккаунта. Ответ остаётся HTTP 200, набор полей прежний, у облачных аккаунтов значение не изменилось. Пока коробочная продажа закрыта, приходит topUpAvailable: false и currency: null, а попытка начать пополнение отвечает кодом BOX_TOPUP_NOT_AVAILABLE.

FIX-0828-21: ключ авторизации получает права, которые портал реально выдаёт

Было

Если у личного API-ключа приложения не было ни одного права Битрикс24, платформе было нечем спросить портал о доступных правах — и ключ авторизации выпускался с набором crm + placement. Приложение, которому нужны задачи, чат или диск, молча упиралось в отказ по правам, а расширить уже выпущенный ключ нельзя: только перевыпуск.

Стало

Права спрашиваются каналом, которым платформа управляет порталом, поэтому набор больше не зависит от того, есть ли вебхук у конкретного ключа приложения. Ключ авторизации получает пересечение доступных прав портала с поддерживаемыми, как и задумано. Если спросить нечем — поведение прежнее, выпуск не блокируется.

FIX-0828-22: фотографию сотрудника снова можно записать через API

Было Поле personalPhoto объявлено строкой, поэтому любой массив или объект в нём отклонялся с 400 INVALID_PARAMS — а массив [имя файла, base64] и есть единственная форма, которую принимает Битрикс24. На сущностных маршрутах записать фотографию было нельзя ни одной формой: строка с base64 и ссылка возвращали 422, пакетные маршруты — 400. Проходила она только через обёртку POST /v1/users/invite.

Стало personalPhoto принимает на записи файл прямо в теле: массив ровно из двух непустых строк — имя файла и содержимое в base64 — на POST /v1/users и PATCH /v1/users/:id. Чтение поля не изменилось, оно по-прежнему отдаёт URL фотографии. Прочие формы отклоняются как раньше, и это намеренно: вложенная {"personalPhoto": {"fileData": [...]}} принимается порталом с успехом, но при этом снимает фотографию. Пакетные маршруты фотографию отклоняют: подзапрос пакета едет строкой запроса под общим потолком тела, и за пределом длины значение обрезалось бы, отвечая при этом успехом. Обёртка POST /v1/users/invite эту же форму принимает — она переводит имя поля и отдельно проверяет пару, — но её потолок тела остался общим (1 МиБ против 40 МиБ у одиночных маршрутов), поэтому настоящую фотографию отправляйте через POST /v1/users или PATCH /v1/users/:id.

2026-08-27

NEW-0827-1: агрегация помечает неполную сумму рядом с самим числом

Было

POST /v1/{entity}/aggregate с sum / avg / min / max считает по первым 5000 записям под фильтр. Признак неполноты лежал только в data.meta.truncated, а data.aggregates.amount выглядел как { "sum": 1234567 } — обычное число без пометок. Клиент, который читает только сумму, получал заниженный результат под кодом 200 и не имел, за что зацепиться: data.count при этом показывал полное количество записей.

Стало

Когда выборка усечена, каждый объект поля в data.aggregates и в groups[].aggregates дополнительно несёт truncated: true — { "sum": 1234567, "truncated": true }. Тем же условием в data.meta.warnings добавляется предупреждение с кодом AGGREGATE_TRUNCATED. Значения sum / avg / min / max остаются числами, в объект их никто не оборачивает. На полной выборке ответ не меняется вовсе: ключа truncated в объекте поля нет, meta.warnings не появляется. Потолок 5000 и data.count работают как раньше.

У группировки добавилось ещё одно поле — groups[].truncated: при усечении сам объект группы несёт этот признак рядом со своим счётчиком. Признак говорит, что образцом является ответ; точен ли счётчик группы, показывает meta.aggregatePath — на обычном обходе groups[].count посчитан по прочитанному срезу, а в режиме счётчиков по стадиям (meta.aggregatePath: "fanout") он берётся отдельной пробой и точен. На группировке с одним только count это единственная пометка у числа: у count-выражения объекта поля не возникает, и оба бэга aggregates приходят пустыми. data.count и data.meta.totalRecords остаются точными на выборке любого размера и признака не несут.

Вместе с признаком неполноты ответ теперь всегда сообщает размер нехватки. Раньше meta.recordsShortfall приходил только тогда, когда усечение случилось НЕ из-за потолка: на выборке больше 5000 записей ответ поднимал truncated: true, но поля с размером дыры не было вовсе. Именно этот случай — самый частый: на воронке в 20 000 сделок клиент видел «ответ является образцом» и ноль в качестве размера пропажи. Теперь на любой причине усечения рядом с признаком едет квантификатор — meta.recordsShortfall (сколько записей не доехало до чисел) либо meta.pageErrorSample (если срез оборвался ошибкой подстраницы). На полной выборке ни того, ни другого ключа в ответе по-прежнему нет.

Одновременно убрана ложная тревога. Признак усечения раньше поднимался по сравнению «всего записей больше 5000», а это лишь косвенная примета того, что чтение обрежут. На сущностях, где ограничение до Битрикс24 не доезжает — страницы и сайты (landing.*), настройки открытых линий, — обрезания не происходит и строки читаются все. Такой ответ всё равно объявлял себя образцом. Теперь признак ставится по факту: прочитано меньше, чем обещано, — тогда и только тогда. На полностью прочитанной выборке любого размера ответ снова считается полным.

Заодно закрыт разрыв на группировке сделок по стадиям: если обход стадии возвращал меньше строк, чем насчитала проба (обычно из-за прав доступа), ответ раньше приходил с data.meta.truncated: false и выглядел полным. Теперь такой ответ честно поднимает truncated — и вместе с ним получает и пометку у числа, и предупреждение.

BC-0827-2: порядок установки среды выполнения и запуск nginx изменились

Поддержка старого формата до: не предусмотрена

Было

При повторной выкладке среда выполнения устанавливалась после остановки прежней версии приложения. Сбой установки мог оставить приложение остановленным. Для static, php83 и php83-mysql установка пакета могла неявно запустить и включить системный nginx.service, и запрос мог полагаться на этот процесс.

Стало

POST /v1/infra/servers/:id/deploy устанавливает среду выполнения до остановки и замены файлов приложения. Если установка завершается ошибкой, прежнее приложение не останавливается. Установка nginx для static, php83 и php83-mysql не запускает системный nginx.service и не оставляет его впервые включённым. Существующее состояние включения сервиса сохраняется.

Что делать интеграторам

Для static, php83 и php83-mysql проверьте, что команда из поля start сама запускает нужный процесс, слушающий порт приложения внутри app.service. Не полагайтесь на неявный запуск системного nginx.service. Запросы с другими средами выполнения менять не нужно.

NEW-0827-3: причина незавершённого подключения BOX-портала и перенос привязки сотрудника между аккаунтами

Карточка BOX-портала с незавершённым подключением (accessPending: true) в ответе GET /api/portals теперь несёт причину в необязательном поле accessPendingReason: transfer_requested — сотруднику уже показана заявка на перенос его привязки к этому номеру Битрикс24 с другого аккаунта Вайбкод, incomplete — подключение просто не завершено. Такими заявками управляет новый раздел /api/box-transfers/*: держатель привязки получает список своих заявок, открывает заявку по ссылке из письма и принимает либо отклоняет перенос.

FIX-0827-4: незавершённое подключение BOX-портала видно в списке

Было

GET /api/portals молча скрывал BOX-портал, подключение к которому не завершено.

Стало

Карточка такого портала возвращается с новым полем accessPending: true.

FIX-0827-5: приглашение пользователей сохраняет все поля анкеты

Было

POST /v1/users/invite принимал десять записываемых полей анкеты в обычном формате API Вайбкод, но передавал их в Битрикс24 без преобразования. Сотрудник создавался, а фотография, внешний идентификатор, часовой пояс и личные данные оставались пустыми.

Стало

POST /v1/users/invite преобразует эти поля в формат Битрикс24. Клиенту не нужно менять запрос, а прежний формат полей Битрикс24 продолжает работать.

FIX-0827-6: одиночное чтение находит комментарий при строковом идентификаторе

Было

GET /v1/tasks/:taskId/comments/:id мог отвечать 404 NOT_FOUND, если Битрикс24 возвращал идентификатор найденного сообщения строкой. При этом тот же комментарий присутствовал в списке комментариев задачи.

Стало

Эндпоинт нормализует числовой идентификатор из ответа Битрикс24 и возвращает найденный комментарий с числовыми id и authorId.

Влияние на интеграторов

Изменения в клиентах не нужны.

FIX-0827-7: select контактов снова принимает типизированные email-адреса

Было

POST /v1/contacts/search и подзапрос контактов в POST /v1/batch отклоняли emailWork, emailHome и emailMailing с кодом UNKNOWN_SELECT_FIELD, хотя ответы контактов уже содержали эти значения. GET /v1/contacts/fields не перечислял эти имена.

Стало

Обе операции принимают три имени в select и возвращают успешный ответ с выбранными значениями. GET /v1/contacts/fields описывает их как nullable-поля только для чтения.

FIX-0827-8: тарифные заголовки возвращаются для инфраструктурных запросов

Было

Ответы методов семейства /v1/infra/* не содержали X-Tariff-Checked-At и X-Tariff-Is-Commercial, даже когда сверка тарифа завершилась успешно.

Стало

Ответы /v1/infra/* содержат тарифные заголовки по тем же правилам, что и /v1/me: время последней успешной сверки и признак коммерческого тарифа возвращаются при наличии соответствующих данных портала. Статус и тело ответа не изменились.

Влияние на интеграторов

Менять запросы не нужно. Отсутствие заголовка снова означает, что соответствующих данных проверки тарифа нет, а не то, что инфраструктурный маршрут был пропущен.

NEW-0827-9: схема элементов массивов доступна в контрактах полей

GET /v1/orders/fields, GET /v1/basket-items/fields и GET /v1/guide теперь возвращают itemSchema для массивов с объявленной формой элемента. Схема рекурсивно описывает type, readonly, nullable, properties и вложенные itemSchema. Клиенты могут опционально читать новое поле, существующие интеграции продолжают работать без изменений. Отдельный ключ items по-прежнему содержит сырой справочник значений Битрикс24 у полей-перечислений.

FIX-0827-10: списки без общего количества больше не выглядят завершёнными раньше времени

Было

Если Битрикс24 не возвращал общее количество записей, запрос списка с лимитом больше 50 останавливался после первой страницы. В окне до 50 записей полная страница тоже могла ошибочно выглядеть последней, потому что её размер принимался за размер всей коллекции.

Стало

Платформа продолжает чтение после каждой полной страницы и останавливается на короткой странице или на лимите запроса. Когда конец коллекции ещё не доказан, общее количество остаётся неизвестным, а meta.hasMore или truncated сообщает, что записи могут продолжаться.

Влияние на интеграторов

Менять запросы не нужно. Запрошенные окна больше одной страницы больше не усекаются молча; у GET /v1/mail/messages теперь всегда есть булевый признак truncated, а неизвестный total отсутствует вместо null.

Затронутые эндпоинты: GET /v1/humanresources/nodes, GET /v1/mail/mailboxes, GET /v1/mail/messages.

NEW-0827-11: самоописание указывает на документацию промокодов Коворк/Код

Ответы GET /v1/guide и GET /v1/me теперь называют пару промокодов настольного приложения. В guide.ts-разделе cowork появились указатели docs.couponPreview и docs.couponRedeem на страницы POST /v1/cowork/coupon/preview и POST /v1/cowork/coupon/redeem, а правила /v1/me получили пункт про них: какой класс ключа нужен, чем проверка отличается от погашения и почему accessGranted: false показывают отдельным экраном.

Сами эндпоинты работают как прежде — новое только в том, что интегратор и агент находят их описание, не читая журнал.

FIX-0827-12: пределы частоты Коворк/Код больше не называются числом, которого клиент не получает

Было

Справочник GET /v1/guide, правила GET /v1/me и страницы документации раздела Коворк/Код называли пределы частоты конкретным числом: 20 запросов в минуту у POST /v1/cowork/coupon/preview, 10 у POST /v1/cowork/coupon/redeem, 30 у GET /v1/cowork/subscription/preview, 5 у DELETE /v1/cowork/key, 3 за пять минут у POST /v1/cowork/deploy-key, 30 у GET /v1/cowork/applications/defaults и 6 у POST /v1/cowork/applications. Ни одно из этих чисел клиенту не приходило: предел делится между процессами платформы, и заголовок x-ratelimit-limit отдавал соответственно 7, 4, 10, 2, 1, 10 и 2. У deploy-key, key и мастера создания приложения расходились между собой два канала одного эндпоинта — страница обещала одно, заголовок отдавал другое.

Стало

Справочник GET /v1/guide, правила GET /v1/me и страницы документации раздела называют источником действующего значения заголовок ответа x-ratelimit-limit и просят не закреплять число в клиентском коде. Поведение самих эндпоинтов не менялось, ответ остаётся прежним — исправлено описание, которое расходилось с ним. Клиент, читавший заголовок, не меняет ничего.

Что этой записью НЕ закрыто. Машинная схема GET /v1/openapi.json по-прежнему называет числа пяти операциям раздела — subscription/preview, deploy-key, applications/defaults, applications и key, — и эти числа расходятся с заголовком так же, как расходились остальные. Клиент, сгенерированный по схеме, пока обязан читать x-ratelimit-limit, а не значение из описания операции. Верно в схеме одно обещание — «три запроса в час» у POST /v1/cowork/activate-market-trial: там предел умножается на число процессов до деления.

FIX-0827-13: создание линии телефонии возвращает рабочий ключ

Было

POST /v1/telephony-lines возвращал внутренний числовой идентификатор. Его нельзя было использовать в адресе для изменения или удаления созданной линии.

Стало

Поле data.id содержит номер созданной линии. Это значение подходит для последующих запросов изменения и удаления.

Влияние на интеграторов

Дополнительных действий не требуется. Новые ответы создания сразу содержат адресуемый ключ.

NEW-0827-14: списки трудозатрат явно отклоняют неподдерживаемый filter

Было

Неподдерживаемый filter в запросах GET /v1/task-time и GET /v1/tasks/:taskId/time мог вернуть статус 200 и более широкую выборку, чем ожидал клиент.

Стало

Непустой, bracket-, JSON- или повторный filter возвращает 400 UNSUPPORTED_FILTER. Для запросов с именованными параметрами userId, taskId, from и to ответ остаётся HTTP 200, их поведение не меняется.

FIX-0827-15: файл robots.txt PUBLIC-приложения доступен роботам

Было

Точные GET и HEAD на /robots.txt всегда получали локальный запрет шлюза, даже если приложение имело accessPolicy=PUBLIC, работало и само отдавало файл.

Стало

При accessPolicy=PUBLIC и живом туннеле точные GET и HEAD на /robots.txt возвращают ответ приложения: 200 text/plain с полным непустым GET-телом не более 1 MiB (HEAD — без тела) или 304. Для остальных политик, недоступного приложения и любого некорректного ответа шлюз по-прежнему возвращает локальный 200 text/plain с Disallow: /.

Влияние на интеграторов

PUBLIC-приложение теперь может управлять индексацией через свой файл /robots.txt. Для непубличных приложений ничего менять не нужно: запрет остаётся закрытым по умолчанию.

FIX-0827-16: фильтры сущностей больше не теряются при списочных вызовах

Было

list_entities мог передать фильтры как обычные параметры запроса. Из-за этого поле с именем sort конфликтовало с директивой сортировки, а openline-configs мог вернуть полный список вместо отфильтрованного. На envelope-сущностях фильтр также мог потеряться в GET /v1/{entity}/aggregate, POST /v1/batch и POST /v1/{entity}/batch.

Стало

list_entities передаёт поля через filter[...], отдельно от сортировки. Все списочные V1-пути применяют объявленный сущностью envelope, поэтому непустой фильтр доходит до Битрикс24 и несовпадающий фильтр возвращает пустой результат, а не полную коллекцию. Ручной список бронирований принимает ту же bracket-форму для обязательного окна dateFrom/dateTo и сохраняет прежнюю плоскую форму.

Влияние на интеграторов

Менять запросы не нужно. Ранее слишком широкие успешные ответы теперь соответствуют переданному фильтру.

FIX-0827-17: список доступа к приложению содержит только сотрудников портала

Было

GET /v1/infra/servers/{id}/access отдавал в users[] каждую строку аудитории. Поле id элемента объявлено обязательным, но у записи, выданной человеку не из портала сервера, портального номера нет вовсе — такой элемент приезжал бы с пустым id, и клиент, читающий поле как обязательное, ломался бы на нём.

Стало

users[] содержит только записи с портальным номером. Записи, адресованные по сетевому идентификатору, из этого списка исключены — у них другое пространство идентификаторов, и смешивать его с портальным нельзя. На сегодняшних данных ответ не меняется: таких записей ещё не существует. Тот же порядок действует на соответствующем экране в кабинете.

FIX-0827-18: шаг `connect` в статусе ремонта больше не проваливается на подключившемся агенте

Было

Ремонт сервера засчитывал шаг connect только тогда, когда версия поднявшегося агента совпадала с версией, заданной в платформенных настройках. Если агент подключался на другой версии, туннель был поднят и blackholeStatus показывал CONNECTED, но GET /v1/infra/servers/{id}/repair-status отдавал status: failed, step: connect и error: "Agent did not connect".

Стало

Шаг connect завершается по факту подключения агента к Gateway — ровно так, как описан порядок шагов в документации операции. Версия установленного агента по-прежнему возвращается в data.agentVersion и на исход ремонта не влияет.

2026-08-26

FIX-0826-1: socnetGroupId работает на вложенных ресурсах списков рабочих групп

Было

Параметр socnetGroupId учитывался только на самих списках — GET /v1/lists и GET /v1/lists/{iblockId}. На вложенных ресурсах он принимался и молча терялся: GET /v1/lists/{iblockId}/elements, поля, разделы, файлы свойства и все удаления при iblockTypeId=lists_socnet уходили в Битрикс24 без идентификатора группы. Битрикс24 искал инфоблок вне рабочей группы, не находил его и отвечал ошибкой о неверном типе инфоблока — клиент видел 422, хотя группу в запросе указал.

Стало

socnetGroupId доезжает до Битрикс24 со всех эндпоинтов семейства /v1/lists: в строке запроса на чтениях и удалениях, в теле запроса на создании и изменении. Список элементов, полей и разделов списка рабочей группы возвращается так же, как у обычного списка.

Влияние на интеграторов

Менять запросы не нужно. Если обход списков рабочих групп был построен на прямых вызовах lists.* из-за этой ошибки, теперь он собирается на эндпоинтах платформы Вайбкод.

BC-0826-2: exec в galaxy-приложение отвечает CONTAINER_NOT_READY вместо мнимого успеха

Поддержка старого формата до: не предусмотрена

Было

POST /v1/infra/servers/{id}/exec у galaxy-приложения, контейнер которого ещё не поднят, отвечал 200 и success: true, а внутри отдавал exitCode: 1 и текст движка контейнеров в поле stderr. Команда не выполнялась, но ответ выглядел успешным, и клиенту приходилось разбирать сообщение об ошибке строкой, чтобы понять причину.

Стало

Такой вызов отвечает 502 с кодом CONTAINER_NOT_READY и подсказкой hint, которая ведёт к выкладке приложения. Подробности движка контейнеров наружу больше не возвращаются. Клиентам, которые ожидали 200, нужно обрабатывать CONTAINER_NOT_READY как сигнал сначала выложить приложение и затем повторить команду. Разбор ответа строкой не нужен: причина названа кодом. Если host-agent ещё не поддерживает безопасный container_exec, платформа не запускает legacy-команду и отвечает 502 EXEC_FAILED; после обновления агента вызов можно повторить. Ответы успешных команд на поддерживаемой версии агента не изменились.

FIX-0826-3: 404 для имён методов Битрикс24 указывает на маршрут V1

Было

Запросы GET /v1/catalog.product.list, GET /v1/crm.deal.list, GET /v1/crm.user.list и GET /v1/crm.deal.search возвращали общий 404 ROUTE_NOT_FOUND только с указателем на GET /v1/guide. Агенту приходилось самостоятельно искать замену среди маршрутов V1.

Стало

Эти запросы по-прежнему возвращают 404 ROUTE_NOT_FOUND и не вызывают метод Битрикс24, но теперь error.details содержит причину BITRIX_METHOD_AS_PATH, исходное имя метода, объект suggestedEndpoint с правильными HTTP-методом и путём и объект guide с указателем на GET /v1/guide. Подсказки ведут на GET /v1/catalog-products, GET /v1/deals, GET /v1/users или POST /v1/deals/search.

Влияние на интеграторов

Не формируйте пути V1 из имён методов Битрикс24. При error.details.reason = BITRIX_METHOD_AS_PATH повторите запрос по suggestedEndpoint, а требования к параметрам уточните через guide.

FIX-0826-4: Нейтральное смещение в английском описании createdDate

Было

GET /v1/task-time/fields использовал для createdDate пример смещения, специфичный для одного региона.

Стало

Английское описание использует нейтральное смещение +00:00.

Влияние на интеграторов

Действий не требуется. Изменился только пример в описании поля.

NEW-0826-5: создание приложения из десктопа Коворк/Код — две новые ручки

Приложение платформы Вайбкод теперь создаётся прямо из десктопа, без похода в кабинет за ключом.

GET /v1/cowork/applications/defaults отдаёт всё, что мастеру нужно до первого вопроса человеку: пресеты «какие данные портала нужны приложению», режим доступа, которым аккаунт снабжает новые ключи по умолчанию, остаток квоты ключей и параметры сервера для последующего вызова POST /v1/infra/servers. Ручка только читает и ничего не создаёт. Пресет несёт идентификатор и набор скоупов, но не подписи: платформа отвечает за состав пресета, а формулировку клиент локализует по устойчивому идентификатору. Признак placement имеет три значения, а не два: galaxy-preferred — обычный вердикт аккаунта, которому политика разрешает оба размещения, и в него же понижается galaxy у аккаунта на бесплатном тарифе. Когда создание выключено настройкой платформы, ручка отвечает 200 с полем available: false, а не отказом: мастер прячет пункт меню заранее, вместо того чтобы упереться в отказ после ввода имени.

POST /v1/cowork/applications создаёт личное приложение и чеканит его личный ключ, который возвращается один раз. Сервер при этом не создаётся: у личного приложения его нет по определению, он доращивается публикацией и сам привязывается к карточке. Карточка в ответе несёт тот же набор полей, что витрина приложений, поэтому объект «приложение» у клиента остаётся один. Часть признаков эта ручка не наполняет — закрепление, сохранённые версии исходников и идущую операцию читайте из витрины.

Заголовок Idempotency-Key обязателен. Повтор с тем же телом отвечает 201, заголовком Idempotent-Replayed: true и rawApiKey: null — сырой ключ хранится необратимо и повторно не выдаётся. Потерянный ключ этим же ключом Коворк/Код не перевыпускается — идентификатора выданного ключа в ответе нет, поэтому сохраняйте его сразу, а взамен потерянного создавайте приложение заново с новым значением заголовка. Тот же заголовок с другим телом отвечает 409 IDEMPOTENCY_KEY_BODY_MISMATCH; отпечаток берётся с нормализованного тела, поэтому переставленный порядок полей и другой порядок скоупов повтор не ломают.

Скоупы Битрикс24 обязательны и дефолта не имеют. Выданный ключ несёт права на инфраструктуру и хранилище плюс запрошенные скоупы Битрикс24 и намеренно НЕ несёт прав на ИИ и веб-поиск: приложение, созданное этой ручкой, не расходует кошелёк платформы на ИИ.

Обе ручки требуют скоуп vibe:cowork и активную подписку Коворк/Код. Ключ подписки, выписанный для сторонней агентной программы, к ним не допускается — 403 COWORK_HARNESS_KEY_FORBIDDEN.

FIX-0826-6: график работ отвечает понятной ошибкой, когда не передан обязательный id

Было

GET /v1/workday/schedule без обязательного id уходил в Битрикс24 и возвращал 422 BITRIX_ERROR с текстом Битрикс24 о ненайденном параметре. Ни имени параметра в понятном виде, ни указания на этот метод в ответе не было, а машинное описание метода не объявляло id обязательным, поэтому сгенерированный клиент сам собирал такой вызов.

Стало

Запрос без id отклоняется до обращения к Битрикс24 и возвращает 400 MISSING_REQUIRED_PARAMS с указанием, что нужен идентификатор графика, а не пользователя. В машинном описании метода id объявлен обязательным query-параметром.

Влияние на интеграторов

Запрос с id работает как раньше. Если вызов делался без id, вместо 422 придёт 400 — ответ и раньше был ошибкой, менять рабочие запросы не требуется.

NEW-0826-7: приложению не выдаётся право `vibe:infra` на портале с выключенной инфраструктурой

Было

POST /v1/apps со списком прав, где есть vibe:infra, создавал приложение и его ключ с этим правом независимо от того, включена ли инфраструктура на портале.

JSON
POST /v1/apps
{ "title": "My app", "scopes": ["crm", "vibe:infra"] }

HTTP 201
{ "success": true, "data": { "scopes": ["crm", "vibe:infra"] } }

Стало

Если администратор портала выключил работу с серверами, запрос с этим правом отвечает 403 INFRA_DISABLED_FOR_PORTAL, приложение не создаётся.

JSON
POST /v1/apps
{ "title": "My app", "scopes": ["crm", "vibe:infra"] }

HTTP 403
{
  "success": false,
  "error": {
    "code": "INFRA_DISABLED_FOR_PORTAL",
    "message": "Infrastructure is disabled on this portal — the vibe:infra scope cannot be granted"
  }
}

Успешный путь не меняется: на портале с включённой инфраструктурой на тот же запрос ответ по-прежнему HTTP 201. Запрос без этого права проходит и на закрытом портале — там право просто не дописывается по умолчанию, отказа не будет.

NEW-0826-8: право `vibe:infra` нельзя добавить приложению через правку его прав

Было

PATCH /v1/apps/:id со списком прав, куда дописано vibe:infra, проставлял это право парным ключам приложения независимо от того, включена ли инфраструктура на портале. Через этот путь ключ получал доступ к серверам там, где создание такого ключа отказом закрыто.

JSON
PATCH /v1/apps/{id}
{ "scopes": ["crm", "vibe:infra"] }

HTTP 200
{ "success": true, "data": { "scopes": ["crm", "vibe:infra"] } }

Стало

Если администратор портала выключил работу с серверами, попытка добавить право отвечает 403 INFRA_DISABLED_FOR_PORTAL, права приложения и его ключей не меняются.

JSON
PATCH /v1/apps/{id}
{ "scopes": ["crm", "vibe:infra"] }

HTTP 403
{
  "success": false,
  "error": {
    "code": "INFRA_DISABLED_FOR_PORTAL",
    "message": "Infrastructure is disabled on this portal — the vibe:infra scope cannot be granted"
  }
}

Успешный путь не меняется: на портале с включённой инфраструктурой на тот же запрос ответ по-прежнему HTTP 200. Правка приложения, которое это право уже несёт, проходит и на закрытом портале, снятие права разрешено всегда.

NEW-0826-9: управление галактическим сервером закрыто, пока галактики выключены на портале

Было

Флаг галактик проверялся только при создании. Уже созданным галактическим сервером — POST /v1/infra/servers/:id/wake, PATCH /v1/infra/servers/:id/sleep и остальные изменяющие операции — можно было управлять и после того, как администратор портала выключил галактики.

JSON
POST /v1/infra/servers/{id}/wake

HTTP 200
{ "success": true }

Стало

Пока галактики на портале выключены, изменяющие операции над таким сервером отвечают 403 GALAXY_DISABLED.

JSON
POST /v1/infra/servers/{id}/wake

HTTP 403
{
  "success": false,
  "error": {
    "code": "GALAXY_DISABLED",
    "message": "Galaxy feature is not enabled for this portal"
  }
}

На портале с включёнными галактиками ответ по-прежнему HTTP 200. Чтение (GET), обновление статуса (POST /v1/infra/servers/:id/refresh) и удаление сервера остаются доступными всегда: уйти из пилота и убрать за собой можно и после выключения. Обычных серверов правило не касается.

NEW-0826-10: право `vibe:infra` не выдаётся на портале с выключенной инфраструктурой

Было

POST /v1/keys и PATCH /v1/keys/:id выдавали ключу право vibe:infra независимо от того, включена ли инфраструктура на портале: доступность проверял только интерфейс кабинета.

JSON
PATCH /v1/keys/{id}
{ "scopes": ["crm", "vibe:infra"] }

HTTP 200
{ "success": true, "data": { "scopes": ["crm", "vibe:infra"] } }

Стало

Если администратор портала выключил работу с серверами, попытка добавить право отвечает 403 INFRA_DISABLED_FOR_PORTAL.

JSON
PATCH /v1/keys/{id}
{ "scopes": ["crm", "vibe:infra"] }

HTTP 403
{
  "success": false,
  "error": {
    "code": "INFRA_DISABLED_FOR_PORTAL",
    "message": "Infrastructure is disabled on this portal — the vibe:infra scope cannot be granted"
  }
}

Успешный путь не меняется: на портале с включённой инфраструктурой на тот же запрос ответ по-прежнему HTTP 200. Правка ключа, который это право уже несёт, продолжает работать и на закрытом портале: гейт считает добавление права, а не его присутствие в теле запроса. Удаление права разрешено всегда. При создании ключа без явной просьбы право просто не дописывается по умолчанию — отказа не будет.

NEW-0826-11: карта требуемых прав в самоописании ключа

GET /v1/me теперь отдаёт в блоке data.api поле scopeRequirements — карту прав ключа. Раньше границу ключа можно было узнать только вызовом: запрос уходил и возвращался отказом 403 SCOPE_DENIED, называющим недостающее право. Список доступных сущностей показывал только то, что открыто, а закрытое просто отсутствовало, и по отсутствию нельзя было отличить «такой сущности нет в продукте» от «она есть, но вашему ключу закрыта».

Поле granted перечисляет права, которые у ключа есть, отдельно по Битрикс24 и по Вайбкод. Поле missing перечисляет недостающие и для каждого показывает, какие сущности и какие адреса оно откроет, а также howToObtain — способ получения: права Битрикс24 закрепляются за ключом при выпуске, поэтому получить их можно только перевыпуском. Списки адресов обрезаны на пяти элементах, остаток назван в unlocksRemaining рядом с признаком truncated. Поле unobtainable перечисляет права, которые личный ключ-вебхук нести не может в принципе. Поле aliases показывает написания, которые Битрикс24 считает одним правом.

Поле coverage объявляет полноту самой карты: entities всегда complete, а paths пока partial — часть адресов ещё не имеет машинного вердикта, поэтому списки путей занижают. Отсутствие адреса в unlocks не доказывает, что адрес не требует прав: если вызов всё-таки отвечает отказом, верьте отказу, а не карте. Права, которые платформа выдаёт сама и пользователь получить не может, попадают в unobtainable, а не в missing с невыполнимым советом.

Карта описывает только проверку прав на стороне платформы, о чём предупреждает _note. Она не обещает, что вызов пройдёт: право, выданное в кабинете уже после выпуска ключа, попадёт в карту, но Битрикс24 его не признает и ответит BITRIX_ACCESS_DENIED. Отказы по виду ключа, владению, режиму только для чтения, заморозке и тарифу — отдельные оси, в карте они не описаны.

Отбивки не изменились ни кодом, ни текстом: 403 SCOPE_DENIED по-прежнему называет недостающее право. Добавлена предварительная проверка, а не замена отбивки.

FIX-0826-12: сортировка комментариев задачи в скобочной форме `?sort[поле]=направление`

Было

GET /v1/tasks/{taskId}/comments?sort[id]=desc отвечал 500 INTERNAL_ERROR. Скобочную форму сортировки принимают /v1/tasks и /v1/deals, а плоская форма того же запроса — ?sort=id:desc — работала и здесь, поэтому отказ выглядел случайным. Ошибку давала любая скобочная сортировка, включая несортируемое поле: ?sort[createdAt]=desc тоже отвечал 500, тогда как плоский ?sort=createdAt возвращал понятный 400 INVALID_SORT_FIELD с перечнем допустимых полей.

Стало

Скобочная форма разбирается так же, как плоская, и проходит ту же проверку имени поля. ?sort[id]=desc сортирует по ID и отвечает 200; плоская форма ?sort=id:desc не меняется — её ответ остаётся HTTP 200. Несортируемое поле в скобках отвечает 400 INVALID_SORT_FIELD с тем же перечнем полей, что и плоская форма, и текст ошибки называет поле — Unsupported sort 'createdAt:desc'. Сортировка в форме, которую нельзя прочитать однозначно (повторный параметр, смешение плоской и скобочной форм, вложенные или повреждённые скобки, массив либо больше одного двоеточия), тоже отвечает 400, а не молча переосмысливается или теряется. Такой же скобочный sort на списке пунктов чек-листа задачи и в списке порталов платформы теперь отвечает контролируемым 400, а не 500. 500 этот параметр больше не даёт ни в одной форме.

FIX-0826-13: пробуждение приложения на неисправном Galaxy-хосте завершается явной ошибкой

Было

Запросы POST /v1/infra/servers/:id/start и POST /v1/infra/servers/:id/wake для Galaxy-приложения могли выглядеть успешными, хотя гостевая ОС общего хоста уже была признана незагружаемой. Клиент продолжал ждать запуск, который не мог завершиться.

Стало

Для такого Galaxy-приложения оба запроса возвращают HTTP 422 с кодом GUEST_NOT_BOOTING. Клиент может сразу прекратить ожидание и предложить заменить неисправный хост.

FIX-0826-14: отказ Битрикс24 на выписке ключа больше не отдаёт внутренний текст вендора

Было

Когда Битрикс24 отказывал в регистрации на выписке ключа или приложения, в поле error.message уезжала его внутренняя формулировка как есть — например Cannot register webhook: Failed to register webhook in portal. Текст всегда английский, не описан в документации и мог измениться на стороне Битрикс24 в любой момент. Отдельная семья таких отказов (регистрация входящего вебхука) вдобавок отдавала код Битрикс24 напрямую, вместо кода платформы Вайбкод.

Стало

error.message содержит текст платформы Вайбкод, а подробности ответа Битрикс24 остаются в журналах. Отказ регистрации вебхука классифицируется наравне с отказом регистрации приложения и отдаёт REST_REGISTRATION_FAILED. Ответ дополнен полем error.details.incidentCode — шестизначный код обращения, по которому поддержка находит запись в журнале. HTTP-статусы и коды ошибок прежние, клиенту менять ничего не нужно.

2026-08-25

FIX-0825-1: карточка приложения получает сервер и на порталах с галактиками

Было

Приложение, созданное без сервера, а затем опубликованное на портале с галактиками, оставалось в разделе «Приложения» без сервера: GET /v1/applications/{id} отдавал server: null неограниченно долго. Привязка контейнера к уже существующей карточке зависела от настройки, выключенной по умолчанию, поэтому карточка и контейнер жили порознь.

Стало

Контейнер привязывается к уже существующей карточке независимо от этой настройки, и GET /v1/applications/{id} отдаёт сервер сразу после публикации. Настройка по-прежнему управляет только появлением НОВОЙ карточки у приложения, созданного на общем хосте, — её поведение не изменилось.

BC-0825-2: ключ «только чтение» больше не пишет на платформенных ручках

Поддержка старого формата до: не предусмотрена

Было

Режим ключа «только чтение» резал вызовы Битрикс24, но не трогал платформенные ручки V1. Ключом «только чтение» проходили управление серверами (DELETE /v1/infra/servers/{id}, POST /v1/infra/servers/{id}/stop|start|reboot|wake), деплой и запуск команд на сервере, запись в хранилище (POST /v1/storage/objects), обращения (POST /v1/feedback), ключи поиска и ИИ, а также ручки, списывающие вайбы.

Стало

Любая запись на платформенной ручке V1 под ключом «только чтение» отвечает 403 WRITE_BLOCKED_READONLY_KEY до выполнения операции. Ручки, которые проксируют вызов в Битрикс24, работают как прежде: там решение принимает классификатор по имени метода, поэтому чтения с телом запроса (POST /v1/deals/search, POST /v1/batch из одних чтений и подобные) не затронуты.

Исключений два. POST /v1/apps — ключом «только чтение» по-прежнему можно завести приложение и парный ключ В РЕЖИМЕ «только чтение», но нельзя выписать ключ «чтение и запись». DELETE /v1/infra/servers/{id}/lock — снятие залипшего лока не меняет состояния сервера и остаётся доступным: на него ссылается поле recoveryAction в ответе EXEC_BUSY.

Если приложению нужны эти операции, переключите ключ в режим «чтение и запись» на странице /keys. Проверить режим и доступность создания сервера можно через GET /v1/me: у ключа «только чтение» в ответе появилось поле writeRestriction — код отказа, область его действия и адрес страницы про режимы. Оно нужно потому, что тело ответа называет пишущие ручки в трёх десятках мест (подсказки хранилища, обращений, автосохранения исходников, коворка); это адреса, а не разрешения, и поле говорит это прямо. Доступность конкретной операции по-прежнему в capabilities.

FIX-0825-3: /v1/me перестал объявлять создание приложения недоступным ключу «только чтение»

В ответе GET /v1/me слот capabilities.apps.create под ключом в режиме «только чтение» приходил с available: false и причиной WRITE_BLOCKED_READONLY_KEY. Это было неверно: POST /v1/apps отказывает такому ключу только при попытке завести приложение и парный ключ в режиме «чтение и запись», а приложение В РЕЖИМЕ «только чтение» тот же ключ заводит и получает 201.

Слот теперь приходит доступным, а ограничение описано в его поле note. Клиент, который ветвится по capabilities — а /v1/me для этого и нужен, — больше не пропускает работающую операцию.

BC-0825-4: ключ десктопа Коворка больше не меняет регистрацию OAuth-приложений

Поддержка старого формата до: не предусмотрена

Было

Ключ с системным правом vibe:cowork мог создать OAuth-приложение на аккаунте Битрикс24 (POST /v1/apps), изменить его (PATCH /v1/apps/{id}), снести (DELETE /v1/apps/{id}) и перепривязать к другим учётным данным (POST /v1/apps/{id}/relink-oauth). Тот же ключ при этом уже был закрыт от инфраструктуры, депо исходников и публикации в каталог, то есть запрет был неполным.

Стало

Все четыре операции отвечают 403 INFRA_FORBIDDEN_FOR_COWORK_KEY; в details.deployableKeys перечислены пригодные ключи владельца. На POST /v1/apps отказ приходит до проверки тела, поэтому невалидное тело тоже получает этот код, а не VALIDATION_ERROR. Чтения семейства (GET /v1/apps, GET /v1/apps/{id}) не изменились.

Что делать

Выполнять эти операции обычным ключом приложения: право vibe:cowork выдаётся платформой и предназначено только для данных. Ключи-кандидаты приходят в details.deployableKeys того же ответа — имя, префикс и последние символы, без секрета. Окна поддержки старого поведения нет: право vibe:cowork не выдаётся пользователям и не появляется в диалогах ключей, а единственный его носитель этих операций не вызывает.

BC-0825-5: повторная загрузка файла заменяет содержимое вместо ошибки 500

Поддержка старого формата до: не предусмотрена

Было

Загрузка на уже занятый логический key через POST /v1/storage/objects/upload отвечала 500 с кодом P2002. При этом байты в хранилище уже были перезаписаны, а sizeBytes и sha256 объекта оставались от прежней версии, поэтому размер в списке и в счёте не соответствовал реальному файлу.

Стало

Повторная загрузка на тот же key заменяет содержимое: 200, тот же object.id (выданные ранее ссылки продолжают работать), а sizeBytes, sha256, contentType и новое поле contentUpdatedAt соответствуют новым байтам.

Это верно для ключа приложения. У личного ключа разработчика без привязки к приложению повторная загрузка по-прежнему создаёт новый объект со своим id — поведение не изменилось, оно чинится отдельно.

Когда заменить объект на месте нельзя, вместо 500 приходит 409 с внятным кодом: STORAGE_KEY_DELETED (объект удалён и держит имя до уборки), STORAGE_MULTIPART_IN_PROGRESS (идёт составная загрузка), STORAGE_UPLOAD_PENDING (по ключу висит незавершённая бронь предподписанной ссылки), STORAGE_KEY_OWNED_ELSEWHERE (имя занято другим объектом приложения), STORAGE_KEY_CONFLICT (объект изменился во время загрузки — повторите запрос).

Что нужно сделать

  1. Поле visibility при замене не может менять видимость объекта: не передавайте его либо передавайте текущее значение. Иное значение отвечает 400 STORAGE_VISIBILITY_MISMATCH, и текущее значение указано в сообщении. Раньше такой запрос отвечал 500, но байты при этом всё же заменялись — то есть клиент, игнорировавший ошибку, публиковал обновления и после фикса перестанет.
  2. Замена через предподписанную ссылку (POST /v1/storage/objects) больше не выдаётся на существующий объект: 409 STORAGE_KEY_EXISTS. Content-Type в такой ссылке не подписывается, поэтому заменять содержимое можно только прямой загрузкой. Незавершённая бронь, выданная вам же, при этом переиспользуется — приходит 200 с тем же object.id и свежей ссылкой; видимость брони такая ссылка не меняет, поэтому расхождение отвечает 400 STORAGE_VISIBILITY_MISMATCH.
  3. Составная загрузка (POST /v1/storage/objects/multipart/create) на занятый ключ тоже отвечает 409 вместо 500: STORAGE_KEY_EXISTS, STORAGE_KEY_DELETED, STORAGE_MULTIPART_IN_PROGRESS, STORAGE_UPLOAD_PENDING или STORAGE_KEY_CONFLICT (объект создал параллельный запрос). Замена объекта через составную загрузку не поддерживается — используйте другой ключ.
  4. В ответах появилось поле object.contentUpdatedAt — момент, когда содержимое последний раз стало актуальным. У объектов, записанных до этого обновления, оно null.

BC-0825-6: поля привязки и автора у комментария таймлайна больше не выглядят изменяемыми

Поддержка старого формата до: не предусмотрена

Было

GET /v1/timelines/fields отдавал entityType, entityId и authorId как обычные записываемые поля. PATCH /v1/timelines/{id} с любым из них отвечал 200 и success: true, но значение не менялось — повторное чтение показывало прежнее. При этом id и createdAt в том же ответе честно отклонялись с 400 READONLY_FIELD, то есть справочник полей был правдив избирательно. Интегратор и AI-агент считали, что сменили автора или перенесли комментарий на другую запись, тогда как не менялось ничего.

Стало

Три поля помечены в справочнике неизменяемыми, и попытка их записать отклоняется с 400 READONLY_FIELD вместо тихого успеха. Причины у полей разные, и справочник теперь их различает:

  • entityType и entityId — доступны только при создании: комментарий привязывается к записи в момент добавления, перенести его на другую запись нельзя;
  • authorId — только для чтения: автора Битрикс24 берёт из учётных данных, которыми сделан вызов, поэтому задать его нельзя ни при обновлении, ни при создании.

Изменяемым остаётся comment — это единственное поле, которое принимает и обновление на стороне Битрикс24.

Что делать интеграторам

Уберите entityType, entityId и authorId из тела PATCH /v1/timelines/{id} — они и раньше не применялись, а теперь запрос с ними получит 400 READONLY_FIELD. Отдельно проверьте создание: POST /v1/timelines с полем authorId тоже переходит с «201, значение проигнорировано» на 400 READONLY_FIELD, потому что автора нельзя задать и при создании. Поля entityType и entityId на создании по-прежнему обязательны и принимаются. Если код полагался на «успешный» ответ как на признак смены автора или переноса комментария, это ожидание не выполнялось и до правки: значение оставалось прежним. Комментарий по-прежнему обновляется обычным PATCH с полем comment. Привязку задавайте при создании через POST /v1/timelines, автора сменить нельзя — вызовите метод от имени нужного пользователя.

Окно поддержки прежнего поведения не предусмотрено намеренно: прежнее поведение и было дефектом — оно молча теряло переданное значение, и сохранять его на срок значило бы продлевать тихую потерю данных.

BC-0825-7: базовая версия для выкладки требуется и когда верхнюю версию сохранил другой человек

Поддержка старого формата до: не предусмотрена

Было

POST /v1/infra/servers/{id}/deploy требовал baseVersionId только на сервере, у которого есть живая команда разработки. Если владелец добавлял сотрудника, тот выкладывал свою версию, а затем владелец снимал его с команды, требование снималось вместе с последним участником — и следующая выкладка молча затирала сохранённую работу.

Стало

Метка baseVersionId обязательна и тогда, когда верхняя версия в хранилище исходников сервера сохранена не тем, кто выкладывает сейчас, — независимо от того, есть ли у сервера команда прямо сейчас. Отказ прежний — 409 BASE_VERSION_REQUIRED с номером актуальной версии.

Что делать интеграторам

Отправлять baseVersionId при каждой выкладке. Клиент, который уже это делает, не меняет ничего. Клиент, который полагался на «команда пуста → метка не нужна», получит 409 BASE_VERSION_REQUIRED на сервере с чужой сохранённой версией — в том числе после передачи владения приложением, когда верхнюю версию сохранил прежний владелец. Обработка та же, что уже написана для сервера с командой: прочитать номер актуальной версии из тела отказа и повторить выкладку, указав его как базу. Старое поведение не сохраняется ни на какой срок: именно оно и приводило к потере чужой работы.

NEW-0825-8: участник команды разработки сервера теперь видит его приложение в витрине

Участник команды разработки сервера теперь видит приложение, привязанное к этому серверу, в GET /v1/applications (со значением viewerState: "shared") и может прочитать его карточку через GET /v1/applications/{id} — раньше карточка отвечала 403 FORBIDDEN, потому что доступ проверялся только по владению и по политике доступа сервера, без учёта членства в команде.

FIX-0825-9: include публикуется только для сущностей со связями

Было

В OpenAPI и MCP-справке параметр include объявлялся для list, get и search у всех сущностей. Для сущностей без доступных связей запрос с этим параметром завершался 400 INVALID_INCLUDE с пустым списком доступных связей.

Стало

OpenAPI объявляет include только у сущностей с доступными связями, а MCP-справка предлагает сначала проверить capability через discover или get_fields. Проверка во время запроса и ответ 400 INVALID_INCLUDE для неподдерживаемого include не изменились.

Влияние на интеграторов

Генераторы клиентов больше не получают неподдерживаемый include из OpenAPI, а MCP-агенты получают явное указание сначала проверить capability. Общий optional-ключ MCP сохраняется. Существующие корректные запросы продолжают работать без изменений, а прежние неподдерживаемые запросы получают тот же ответ 400 INVALID_INCLUDE.

BC-0825-10: Потолок inline-архива в теле деплоя сужен до 96 МБ

Поддержка старого формата до: не предусмотрена

Было

POST /v1/infra/servers/:id/deploy с кодом во встроенном source.content, POST /v1/infra/servers/:id/upload со встроенным content и POST /v1/infra/servers с полем source при создании принимали тело до 500 МБ. Отказ сверх потолка приходил кодом PAYLOAD_TOO_LARGE.

Стало

Тело этих трёх запросов ограничено 96 МБ. Единица — само HTTP-тело, а не архив: source.content / content это base64, который тяжелее исходных байт примерно на треть, поэтому 96 МБ тела соответствуют архиву примерно в 72 МБ. Тело сверх потолка отклоняется кодом 413 INLINE_SOURCE_TOO_LARGE — новым, вместо прежнего PAYLOAD_TOO_LARGE на этих трёх входах. Отказ приходит по заголовку Content-Length до чтения тела, поэтому он детерминированный — то же тело повторной отправкой не пройдёт. Трафик это не экономит: тело загружается целиком, и отказ приходит после того, как загрузка закончилась. Конверт ошибки несёт error.hint с полями reason, recovery, recoveryAction и note — готовым рецептом перехода.

Многосоставная форма (multipart/form-data) POST /v1/infra/servers/:id/deploy под тот же потолок попадает условно. Файловая часть уходит в хранилище потоком, и тогда её предел остаётся прежним — 500 МБ архива, — только когда одновременно верны три условия: вызывающий обращается к серверу как его прямой владелец, а не через карточку чужого приложения или через менеджмент-ключ; сервер либо не galaxy-приложение, либо для вызывающего включены обе настройки ссылочного деплоя galaxy-приложений; хранилище исходников включено — и на уровне платформы, и для этого портала. Если хотя бы одно условие не выполнено, поток на потоковую запись не переключается, и форма принимает архив тем же буферным способом, что и base64-поля выше, — под тем же потолком в 72 МБ архива и с тем же кодом 413 INLINE_SOURCE_TOO_LARGE и error.hint.

Прежний код PAYLOAD_TOO_LARGE никуда не делся: он по-прежнему приходит на краевом nginx /v1/ (потолок 500 МБ) и на остальных маршрутах платформы со своими лимитами — переименований нет.

Без изменений:

  • source.url — по-прежнему принимается до 500 МБ, платформа скачивает архив по ссылке сама;
  • source.versionId — деплой уже сохранённой версии, до 500 МБ архива;
  • потолок сохранения версии исходников на POST /v1/infra/servers/:id/sources и POST /v1/apps/:id/sources — 500 МБ, не тронут.

Что делать интеграторам

Архив крупнее 72 МБ отправляйте версионным путём в два вызова — он работает одинаково для личного ключа (vibe_api_*) и для ключа OAuth-приложения, а прямому владельцу сервера этих двух вызовов достаточно:

POST /v1/infra/servers/:id/sources   # сырые байты архива, Content-Type: application/gzip, --data-binary
POST /v1/infra/servers/:id/deploy    # {"source":{"versionId":"vN"}}

Если же сервер доступен через карточку приложения или менеджмент-ключ, а не напрямую владельцем, к тем же двум вызовам добавляется третий: {versionId} может ответить SOURCE_VERSION_REQUIRES_APP или SOURCE_VERSION_NOT_FOUND — тогда получите подписанную ссылку GET /v1/infra/servers/:id/sources/vN/download и разверните архив по ней: {"source":{"url":"<ссылка>"}}. Тот же путь и есть страховка от условного потолка многосоставной формы — версионный деплой ни при каких условиях не буферизует архив целиком, поэтому его 500-мегабайтный предел безусловен.

Сужено на уровне наблюдаемого поведения: тело в сотни мегабайт в base64-форме (и многосоставный архив, для которого не выполнилось хотя бы одно из условий выше) приходилось держать в памяти целиком на всё время запроса, и такой запрос мог оборваться без ответа, задев параллельные запросы на этом же процессе.

NEW-0825-11: `GET /v1/me` называет архивный эквивалент inline-потолка отдельным полем

Было

Блок deployment.limits нёс потолок встроенного тела одним полем uploadInlineMax. Единица там — HTTP-тело, а source.content и content едут в base64, поэтому размер архива, который в это тело поместится, клиент считал сам.

Стало

Рядом появилось поле uploadInlineMaxArchive — тот же потолок, выраженный в размере АРХИВА: три четверти от uploadInlineMax, потому что base64 тяжелее исходных байт примерно на треть. Поле добавлено к ответу и ничего в нём не убирает: uploadInlineMax остаётся на прежнем месте с прежним смыслом, и клиент, который его не читает, работает без правок. Оба значения приходят строками с единицей измерения — например 96MB и 72MB.

Что делать интеграторам

Ничего обязательного. Если вы сами пересчитывали потолок тела в размер архива, возьмите готовое значение из uploadInlineMaxArchive — оно приходит из той же константы, что и отказ 413 INLINE_SOURCE_TOO_LARGE, поэтому разойтись с фактическим поведением не может.

BC-0825-12: POST /v1/apps соблюдает политику портала «кто может создавать приложения»

Поддержка старого формата до: не предусмотрена

Было

Публичный маршрут сверялся только с устаревшим режимом «по списку». Портал, где администратор Битрикс24 разрешил создавать приложения одним администраторам или запретил создание совсем, всё равно позволял создать приложение любым личным API-ключом — при том что то же действие в кабинете отвечало 403.

Стало

POST /v1/apps отвечает 403 с кодом APP_CREATION_RESTRICTED, когда политика портала не разрешает вызывающему создавать приложения. Режим «по списку» работает как прежде: участник из списка приложение создаёт, остальные получают 403. На порталах, где создание открыто всем, не меняется ничего.

Что делать интеграторам

Ключ, выписанный на портале с ограничением, начнёт получать 403 APP_CREATION_RESTRICTED там, где раньше приходил 201. Попросите администратора Битрикс24 выдать право на создание приложений — или ведите создание ключом того, у кого это право есть.

FIX-0825-13: Установка среды Node.js 20 не повторяет уже выполненные шаги

Было

При повторном развёртывании среды Node.js 20 через POST /v1/infra/servers/:id/deploy платформа заново устанавливала Node.js и pm2. Недоступность реестра npm могла занять всё время шага и завершиться без точной причины.

Стало

Платформа пропускает установку уже доступных Node.js 20 и pm2. Если pm2 отсутствует, его установка ограничена по времени и числу повторов, а ошибка или превышение лимита времени останавливает развёртывание с явной диагностикой.

Влияние на интеграторов

Повторные развёртывания завершаются быстрее и не требуют изменений со стороны интеграторов. Ошибка установки pm2 теперь сразу видна как причина неуспешного развёртывания.

FIX-0825-14: привязка плейсмента отвечает 400 на слишком длинный iconName вместо 502 без причины

Было

POST /v1/placements/bind принимал options.iconName длиной до 255 символов. Битрикс24 отклоняет значение длиннее 50, поэтому запрос уходил в аккаунт и возвращался оттуда как 502 BITRIX_UNAVAILABLE с текстом «Failed to register placement on Bitrix24 via dev key». Какое именно поле не подошло, из ответа понять было нельзя.

Стало

Граница длины в схеме совпадает с границей Битрикс24: значение длиннее 50 символов отклоняется сразу, а 400 называет поле options.iconName. Значения, которые привязывались раньше, продолжают привязываться — длиннее 50 аккаунт не принимал никогда, а подставляемый по умолчанию значок fa-cube в границу укладывается.

2026-08-24

BC-0824-1: тип дела нельзя изменить после создания

Поддержка старого формата до: не предусмотрена

Было

PATCH /v1/activities/:id принимал поле typeId. Значение до записи не доходило: тип дела фиксирован при создании, поэтому оно отбрасывалось. Запрос, в котором typeId был единственным полем, оставался с пустым набором полей и возвращал невнятный отказ «Fields is not specified.» — по нему нельзя понять, что причина именно в неизменяемости типа. Запрос, где рядом было хотя бы одно изменяемое поле, отвечал успехом, и вызывающая сторона считала, что тип сменился.

Стало

typeId объявлен полем, которое задаётся только при создании. PATCH /v1/activities/:id с этим полем возвращает 400 READONLY_FIELD и называет поле. Так же отвечают обе пакетные операции обновления. POST /v1/activities поле принимает как раньше — там он обязателен. В выдаче GET /v1/activities/fields у typeId теперь стоит признак createOnly.

Что делать интеграторам

Уберите typeId из тела PATCH /v1/activities/:id. Если тип действительно нужно поменять, создайте дело с нужным типом и удалите прежнее — изменить тип существующего дела нельзя. Признак createOnly в ответе GET /v1/activities/fields позволяет отличать такие поля до отправки запроса.

FIX-0824-2: платный портал больше не получает отказ с предложением оплатить тариф повторно

Было

POST /v1/apps мог вернуть платному порталу 403 с предложением оплатить тариф, если установка приложения через Битрикс24 завершалась отказом.

Стало

Если Битрикс24 подтверждает оплату тарифа, метод возвращает технический отказ 502 CONNECTOR_REST_UNAVAILABLE с безопасной причиной и без предложения повторной оплаты. Подтверждённое отсутствие оплаты по-прежнему возвращает тарифный отказ 403.

Влияние на интеграторов

Менять запросы не нужно. Технический отказ можно повторить, тарифный требует оплаты.

BC-0824-3: блок deployment у аккаунтов Беларуси меняет форму вместе с моделью доступа

Поддержка старого формата до: не предусмотрена

Было

Аккаунт Беларуси попадал в бакет ограниченного доступа при трёх условиях сразу: есть демо-подписка, нет платной подписки и нет истории коммерческих оплат. Тариф Битрикс24 — платный он или бесплатный — на попадание в бакет не влиял, а вот аккаунт, который платил когда-либо раньше, в бакет не попадал вовсе. В GET /v1/me такой аккаунт получал блок deployment без под-блока galaxyApp, с primary = standalone и с полем placementNote, объяснявшим, что своя галактика ему не создаётся и разворачивать приложение нужно в два шага. Одношаговое создание с полем source отвечало 400 SOURCE_AT_CREATE_GALAXY_ONLY.

Стало

На тарифной модели бакета ограниченного доступа нет вовсе, поэтому аккаунт Беларуси выходит из него в обе стороны сразу: на платном или демо-тарифе Битрикс24 он получает полный доступ, на бесплатном — отказ. В обоих случаях форма блока deployment меняется одинаково — при условии, что режим галактик аккаунту открыт и своей галактики у него ещё нет (оба карв-аута названы ниже): под-блок deployment.galaxyApp появляется, поле deployment.placementNote исчезает, а deployment.primary переключается со standalone на galaxyApp. У аккаунта с полным доступом одношаговое создание с полем source при этом начинает работать.

Что делать интеграторам

Определяйте модель размещения по НАЛИЧИЮ под-блока deployment.galaxyApp, как и предписывает описание ответа. Ветку, привязанную к присутствию placementNote или к значению primary, перепишите: оба поля меняются вместе с моделью доступа аккаунта, а не только с его тарифом. Разрешение на создание сервера читайте отдельно — в capabilities.servers.create: блок deployment описывает КОНТРАКТ размещения и у аккаунта без доступа тоже присутствует, а POST /v1/infra/servers такому аккаунту ответит 402. Два карв-аута, у которых форма ответа не меняется вовсе. Первый — аккаунт с выключенным режимом галактик: обоих полей у него нет ни до, ни после. Второй — аккаунт, у которого галактика УЖЕ есть: placementNote объяснял отсутствие собственной галактики, поэтому владелец живого galaxy-хоста и в бакете получал galaxyApp и не получал placementNote. Изменение приходит к аккаунту в момент перевода его на тарифную модель, поэтому обе формы ответа встречаются одновременно у разных аккаунтов.

NEW-0824-4: новый код отказа доступа для аккаунтов Беларуси

В Беларуси доступ к инфраструктуре Вайбкод открывает платный тариф Битрикс24, демо-тариф Битрикс24 ИЛИ платная подписка Битрикс24 Маркет Плюс — достаточно любого одного из трёх. Аккаунт, у которого нет ни одного, получает 402 с error.code = BY_PAID_ONLY вместо прежнего MARKETPLACE_REQUIRED. Тот же код приходит на создание сервера (POST /v1/infra/servers), на публикацию приложения, на пробуждение уснувшей машины и на выписку нового ключа, а в GET /v1/me он же приезжает в capabilities.servers.create.reason.

Изменение аддитивное: форма тела ответа прежняя, прочие ветки отказа не тронуты. Разбирайте новый код так же, как остальные отказы 402, — читайте error.code, показывайте error.userMessage, ведите человека по details.upgradeUrl. Если разбор кодов у вас исчерпывающий, заведите ветку BY_PAID_ONLY: без неё отказ провалится в default.

Переход поэтапный. Код появляется у аккаунта не раньше, чем платформа переведёт его на тарифную модель доступа; до перевода тот же аккаунт получает прежний MARKETPLACE_REQUIRED. Планировать разбор поэтому нужно на оба кода сразу, а не на один.

FIX-0824-5: подсказка о создании серверов в ответе о ключе перестала звать в тупик

Было

Аккаунт, которому доступ открывает тариф Битрикс24, а не подписка, получал в GET /v1/me слот capabilities.servers.create с общей формулировкой: серверы доступны на коммерческих тарифах или во время активного пробного периода. Она врала дважды. Пробный период такому аккаунту не положен — право на него считается по подписочной модели, — а адрес, по которому тариф выбирается, в тексте не назывался вовсе. Отдельно у аккаунтов Беларуси поле alternatives[].url вело на кассу подписки внутри самого аккаунта и продолжало бы вести туда и после перевода аккаунта на тарифную модель, где отказ подпиской не лечится.

Стало

Слот называет рабочие пути и адрес: платный или демо-тариф Битрикс24, а для Беларуси ещё и платная подписка. Текст в capabilities.servers.create.userMessage теперь совпадает с телом 402, которое вернёт POST /v1/infra/servers на том же аккаунте, — один код отказа больше не говорит на двух поверхностях разное. У аккаунтов Беларуси, переведённых на тарифную модель, alternatives[].url ведёт на страницу выбора тарифа, а не на кассу подписки.

Влияние на интеграторов

Действий не требует: изменились значения текстового поля и адреса, структура ответа прежняя. Правка касается Казахстана и Узбекистана сразу, Беларуси — по мере перевода аккаунта на тарифную модель. Если вы показываете пользователю собственный текст вместо userMessage, сверьте его: обещание пробного периода на этих аккаунтах неверно.

FIX-0824-6: отказ по тарифу в Казахстане и Узбекистане больше не зовёт оформить подписку

Было

Портал из Казахстана или Узбекистана на бесплатном тарифе получал в теле 402 код MARKETPLACE_REQUIRED, а поле error.userMessage звало подключить подписку. В этих странах подписка не продаётся, поэтому названное действие клиент выполнить не мог: текст вёл в тупик, а платный и демо-тариф Битрикс24 — рабочие пути к доступу — в нём не назывались вовсе.

Стало

Такой портал получает код KZ_PAID_ONLY или UZ_PAID_ONLY, а текст называет оба рабочих пути — платный или демо-тариф Битрикс24 — и адрес, где тариф выбирается. Бренда подписки в тексте больше нет.

Новый код приходит на всех поверхностях, где отказ в доступе виден клиенту: создание сервера (POST /v1/infra/servers), публикация приложения, пробуждение уснувшей машины и выписка нового ключа. В GET /v1/me он же приезжает в capabilities.servers.create.reason.

Код в теле ошибки для этих порталов сменился с MARKETPLACE_REQUIRED на KZ_PAID_ONLY / UZ_PAID_ONLY. Действий на стороне клиента это не требует: обе ветки уже были в контракте и отдавались в других сценариях, структура тела не изменилась. Проверьте только, что разбор кодов не падает в default — если у вас исчерпывающий switch, ветка *_PAID_ONLY в нём уже есть.

BC-0824-7: повторная авторизация бота стала безопасной и явной

Поддержка старого формата до: не предусмотрена

Было

POST /v1/bots/:botId/reauth мог снять любое отключение бота, а ответ 410 BOT_DISABLED не сообщал клиенту, допустимо ли автоматическое восстановление.

Стало

410 BOT_DISABLED содержит boolean error.details.reauthAllowed. Вызывайте POST /v1/bots/:botId/reauth автоматически только при reauthAllowed=true; сервер выдаёт этот сигнал только для доступного на запись бота, отключённого из-за ошибок авторизации, и включает его поэтапно. Ручной вызов также остаётся проверкой учётных данных активного бота после переноса владения. Для остальных отключённых состояний endpoint сразу отвечает 409 BOT_REAUTH_NOT_ALLOWED; при параллельном изменении состояния — 409 BOT_REAUTH_STATE_CHANGED. Обновите клиентов, которые вызывали повторную авторизацию для любого BOT_DISABLED.

FIX-0824-8: деплой galaxy-приложения предупреждает о сбросе переменных окружения

Было

Успешный /deploy без пользовательского env пересоздавал galaxy-приложение без прежних переменных, но не сообщал об этом в ответе.

Стало

Ответ содержит строку в warnings[], которая просит передавать полный env при каждом деплое. Деплой участника команды разработки, где переменные владельца сохраняются, ложного предупреждения не получает.

Влияние

Статус и поведение деплоя не изменились; клиентам следует читать существующий массив warnings[].

NEW-0824-9: связи задач стали доступны через API

Было

Карточка задачи в Битрикс24 показывает блок «Связанные задачи», а через API их получить было нечем: полей связей нет ни в списке задач, ни в записи по идентификатору, в описании полей задачи они не описаны, отдельного эндпоинта не существовало. Дашборд, которому нужен граф зависимостей, собрать его не мог.

Стало

Появились два эндпоинта только для чтения. GET /v1/tasks/:taskId/dependencies отдаёт связи одной задачи массивом пар «идентификатор и название», отсортированным по идентификатору. POST /v1/tasks/dependencies/bulk принимает до 50 задач и отвечает по каждой отдельно: успешные в results, отказавшие в errors, счётчики в meta.

Пакетный вызов отвечает 200 даже тогда, когда отказали все подвызовы — частичный отказ и есть его смысл, поэтому смотреть надо errors, а не только results. Дубли в списке идентификаторов схлопываются молча, и счётчик запрошенных считает уникальные, поэтому «запрошено» всегда равно сумме «успешно» и «отказано».

Границы, названные явно

Отдаются только предшественники — задачи, от которых зависит запрошенная. Обратного направления Битрикс24 в API не отдаёт, и дашборд строит его инверсией собранных пар. Тип связи не читается ни одним методом портала, поэтому в ответе его нет вовсе: пустое или угаданное значение в контракте хуже, чем отсутствие поля.

Связи, добавленные через диаграмму Ганта, этими эндпоинтами не видны: портал хранит их отдельно и не возвращает ни одним методом чтения. Отличить «связей нет» от «связи есть, но в невидимом хранилище» нельзя ни здесь, ни в сыром REST.

Задачи, у которых связей больше, чем эндпоинт отдаёт, получают явный отказ, а не усечённый список: ответ либо полный, либо отказ.

Влияние на интеграторов

Ломающих изменений нет — оба маршрута новые. Запись связей отдельного эндпоинта не получила: набор по-прежнему меняется полем DEPENDS_ON при обновлении задачи, и семантика там — полная замена набора, а не добавление.

FIX-0824-10: опрос ботов останавливается при неактивной подписке Маркетплейса

Было

Маршруты существующего бота повторяли запросы в Битрикс24 после истечения подписки Маркетплейса и возвращали общий 403 BITRIX_ACCESS_DENIED на каждый вызов. Цикл polling не получал однозначного сигнала остановки.

Стало

Первый подтверждённый отказ паркует бота. Следующие вызовы отвечают локально HTTP 403 с кодом B24_MARKET_SUBSCRIPTION_REQUIRED, заголовком Retry-After: 3600 и error.details.retryable:false, не обращаясь в Битрикс24. После продления GET /v1/me?refresh=tariff автоматически восстанавливает всех ботов аккаунта.

Что делать интеграторам

Остановите polling при этом коде. После продления обновите тариф указанным вызовом и только затем запустите цикл снова.

BC-0824-11: открытие рабочего дня больше не подтверждает истёкшее состояние

Поддержка старого формата до: не предусмотрена

Было

POST /v1/workday/open отвечал 200 success:true, когда Битрикс24 оставлял рабочий день в статусе EXPIRED. Новый день при этом не открывался.

Стало

Неизменившийся статус EXPIRED возвращается как HTTP 409 с success:false и кодом WORKDAY_EXPIRED. Инструкция предлагает прочитать статус того же сотрудника, закрыть истёкший день с датой его timeStart и отчётом, затем повторить открытие.

Что делать интеграторам

Обрабатывайте success:false и HTTP 409 как явный признак, что день не открыт; сначала разрешите состояние прежнего дня по подсказке ответа.

NEW-0824-12: события календаря связываются с элементами CRM

У события календаря появилось поле crmFields — привязка встречи к элементам CRM: сделкам, лидам, контактам и компаниям. Поле читается и записывается, объявлено в GET /v1/calendar-events/fields и доступно в select.

Значение — массив типизированных ссылок: D_<id> сделка, C_<id> контакт, L_<id> лид, CO_<id> компания. На создании и обновлении значение обязано быть массивом; [] снимает все привязки, а отсутствие поля в теле сохраняет уже записанные. Очистка пустым массивом работает на POST и PATCH; в батч-запросе пустой массив отклоняется вместо тихого успеха — подкоманда батча передать его не может. У POST /v1/batch это INVALID_PARAMS в data.errors под id вызова, у POST /v1/calendar-events/batch — 400 BATCH_ITEM_VALIDATION на весь пакет, где имя INVALID_PARAMS и индекс элемента приходят внутри message. Событие без привязок читается как [] — пустое значение никогда не приходит как null или пустая строка, поэтому проверка crmFields.length безопасна всегда. Неизвестный префикс или ссылка на несуществующую запись отклоняются.

Затронутые эндпоинты: GET /v1/calendar-events, GET /v1/calendar-events/:id, POST /v1/calendar-events/search, POST /v1/calendar-events, PATCH /v1/calendar-events/:id, GET /v1/calendar-events/fields.

FIX-0824-13: приложение на незагружающемся galaxy-хосте удаляется без живого туннеля

Было

DELETE /v1/infra/servers/{id} по galaxy-приложению отвечал 502 с кодом GALAXY_HOST_UNREACHABLE, если хост был не на связи, — независимо от причины. Когда гостевая операционная система хоста не загружается вовсе (поле provisionErrorCode карточки сервера равно GUEST_NOT_BOOTING), туннель не появится уже никогда, поэтому повтор запроса не помогал ни через минуту, ни через сутки. Удалить сам хост тоже было нельзя: он отвечал 409 с кодом GALAXY_HAS_APPS, потому что приложения на нём формально оставались живыми. Владелец оказывался в замкнутом круге и продолжал занимать слот квоты машиной, которая уже не работает.

Стало

Если платформа доказала, что гостевая операционная система хоста не загружается, приложение на нём удаляется без обращения к хосту: DELETE /v1/infra/servers/{id} отвечает 200, запись приложения закрывается, освобождаются его токены доступа, домен и карточка в каталоге. Удалив приложения по одному, владелец затем удаляет и сам хост обычным вызовом — отказ GALAXY_HAS_APPS больше не возникает. Код GALAXY_HOST_UNREACHABLE сохраняет прежний смысл для всех остальных случаев: хост временно не на связи, повтор осмыслен. ⚠️ Метка «не загружается» сама по себе 200 не гарантирует: перед удалением без обращения к хосту платформа ещё и убеждается через шлюз, что живого туннеля нет. Проверка fail-safe — если шлюз недоступен, ответил без списка соединений или хост тем временем поднялся, ответ остаётся 502. Клиентам менять ничего не нужно — цикл повторов для этого состояния просто перестаёт быть нужным.

BC-0824-14: незнакомое имя поля в select отклоняется с 400 почти на всех сущностях

Поддержка старого формата до: 18.02.2027

До сих пор такой запрос отвечал 200, а запрошенного поля в записи просто не было — то есть «старый формат» здесь означает неполный ответ, а не рабочий.

Было

Имя поля, которого у сущности нет, уходило в отбор молча. Запрос выполнялся, поле в записях отсутствовало, а объяснение лежало в meta.warnings — там, куда обычно не смотрят. Опечатка в select поэтому выглядела как успешный запрос с загадочно неполными записями:

GET /v1/deals?select=id,titel      →  200, в записях только id

Ошибкой такое имя отвечали только события календаря.

Стало

Запрос отклоняется до вызова Битрикс24 — 400 с кодом UNKNOWN_SELECT_FIELD и списком допустимых имён в сообщении. Этот список и есть точный ответ на вопрос «что можно запрашивать».

Действует на сущностях, у которых набор полей выверен по Битрикс24: сделки, контакты, компании, лиды, предложения, дела, адреса, смарт-процессы, товары и разделы товаров, каталоги и их товары и разделы, заказы и статусы заказов, статусы, валюты, разделы календаря, файлы, папки, хранилища, подразделения, рабочие группы, шаблоны документов. На остальных поведение прежнее — предупреждение в meta.warnings.

Отдельный класс — имя, которое у сущности ЕСТЬ, но никогда не возвращается: GET /v1/{entity}/fields показывает такое поле с notReturned: true. В когорте это, например, currencyId у товаров и sort у разделов товаров. Оно тоже отклоняется 400, но СВОИМ кодом SELECT_FIELD_NOT_RETURNED и сообщением, которое называет причину. Код отдельный намеренно: UNKNOWN_SELECT_FIELD утверждает «такого имени нет», а справочник сущности это имя публикует — клиент, честно взявший имя оттуда, иначе читал бы ответ как «справочник врёт».

Проверяется только имя. Пользовательские поля (UF_*, ufCrm*) принимаются как раньше и на любой сущности; свойства товаров принимаются каждое на СВОЕЙ сущности: PROPERTY_295 — на товарах, property295 — на товарах каталога, где их номера назначает портал; чужое написание метод не понимает, и его гейт отклоняет. Значение * (и UF_*) по-прежнему означает «вернуть все поля», и опечатка рядом с ним отклонения не вызывает — приходит предупреждение. Отбор полей в обоих пакетных вызовах — и в общем, и в пакете по одной сущности — отклоняет только свой подвызов, соседние выполняются.

Что делать интеграторам

Сверьте имена полей в своих select со списком из сообщения об ошибке или со схемой GET /v1/{entity}/fields. Запрос, который раньше «работал», но возвращал записи без части запрошенных полей, теперь ответит 400 с точным указанием, какое имя не найдено — это и есть его исходная ошибка.

FIX-0824-15: схема поля `datePeriod` теперь видна в `GET /v1/bookings/fields`

Было

Поле datePeriod описывалось как "type": "object" без вложенной схемы. Форму объекта нельзя было узнать из самого контракта — клиент отправлял {} или строку с датой и получал 422 BITRIX_ERROR, и только по тексту ошибки догадывался о нужных ключах.

Стало

У полей типа object с известной вложенной формой ответ несёт ключ properties с рекурсивной схемой вложенных ключей. Для datePeriod это from и to, у каждого timestamp (number, Unix-секунды) и timezone (string, зона в формате IANA). Тот же ключ появился у requisiteLink в GET /v1/orders/fields и на обеих поверхностях сразу — в fieldsDetailed у GET /v1/guide тоже, поэтому форму видно и без токенов портала. Массивы объектов схему элемента по-прежнему не несут. Изменение аддитивное: прежние ключи описаний полей остались на месте, проверка данных при создании не ужесточалась.

NEW-0824-16: этапы Scrum-канбана и список спринтов в API

Появились четыре операции для работы с колонками Scrum-доски. Этапы принадлежат спринту, а не рабочей группе: у разных спринтов одного проекта наборы колонок независимы, поэтому адрес коллекции начинается со спринта.

GET /v1/scrum/sprints возвращает спринты проекта — это единственный способ получить sprintId, который нужен остальным трём операциям; без параметра groupId придут все спринты, доступные ключу.

GET /v1/scrum/sprints/:sprintId/stages отдаёт колонки спринта, приводя id, sort и sprintId к числам. POST /v1/scrum/sprints/:sprintId/stages создаёт колонку и возвращает её перечитанной целиком, вместе с подставленными значениями по умолчанию. PATCH /v1/scrum/stages/:stageId переименовывает, перекрашивает и переставляет колонку.

Проверки на стороне платформы Вайбкод закрывают места, где Битрикс24 отвечает успехом и сохраняет другое значение: имя длиннее 255 символов, цвет длиннее шести символов и значение type вне набора NEW, WORK, FINISH теперь получают понятный отказ вместо тихой подмены. Цвет можно передавать и с ведущим # — он будет срезан.

Перенос колонки в другой спринт не выставлен: sprintId в теле запроса отклоняется. Обновление несуществующей или недоступной колонки отвечает кодом STAGE_NOT_FOUND_OR_NO_ACCESS; различить эти два случая нельзя, потому что Битрикс24 отвечает на них одинаково.

FIX-0824-17: код ACCOUNT_FROZEN описан в спеке создания сервера

Ответ 402 у POST /v1/infra/servers перечислял только отказы по тарифу, пробному периоду и подписке Маркетплейса. Кода ACCOUNT_FROZEN, которым платформа отвечает порталу с замороженным кошельком, в перечислении не было, хотя ручка отдавала его и раньше: проверка баланса срабатывает до обработчика, и создание сервера под неё подпадает.

Из-за пропуска клиент, сгенерированный по /v1/openapi.json, считал этот код невозможным и терял отказ, у которого есть понятное действие — пополнить баланс. Поведение ручки не менялось, изменилось только описание.

2026-08-23

BC-0823-1: OpenAPI не предлагает поля только для создания в PATCH

Поддержка старого формата до: не предусмотрена

Было

Поля, которые API принимает только при создании записи, выглядели в GET /v1/openapi.json как обычные свойства тела PATCH. Сгенерированный по этой схеме клиент мог отправить их при обновлении и получить 400 READONLY_FIELD.

Стало

Для сущностей с полями только для создания PATCH использует отдельную входную схему без таких полей. У остальных сущностей PATCH по-прежнему использует прежнюю входную схему. Входная схема POST в обоих случаях сохраняет контракт создания.

Что делать интеграторам

Перегенерируйте SDK по новой OpenAPI-схеме и используйте тип, на который ссылается requestBody конкретного PATCH. Для затронутых сущностей это UpdateInput, для остальных остаётся Input. Удалите из тела обновления поля, которые разрешены только при создании; POST по-прежнему использует Input.

NEW-0823-2: совместная работа нескольких сотрудников над кодом приложения

Владелец сервера может собрать команду разработки: сотрудники портала работают с сервером своими личными ключами, без передачи ключа владельца и без перепривязки сервера. Роль «Разработчик» получает выкладку, команды, файлы, логи и хранилище исходников; роль «Администратор» — дополнительно управление машиной: жизненный цикл (стоп, старт, перезагрузка, расписание сна, починка), настройки и безопасность, тариф и диск, резервные копии, свой домен, карточку каталога, аудиторию приложения и расходы. Состав команды, удаление сервера, ссылки доступа и перепривязка ключа остаются за владельцем. Состав команды правит владелец или администратор портала на вкладке «Совместная работа».

Права даёт членство, а не ключ, поэтому снятие из команды закрывает доступ сразу же. Возможность включается флагом server-collaboration.

У выкладки участника три отличия от выкладки владельца. Переменные окружения сохраняются: поле env из его запроса не применяется (в ответе приходит предупреждение), а существующие переменные приложения переживают выкладку — в том числе у приложений в галактике, где раньше их гасила любая выкладка. Карточка каталога Битрикс24 и анонс подписчикам остаются за владельцем: displayName, description и рассылка changelog от роли «Разработчик» игнорируются с предупреждением, а само примечание к версии сохраняется в хранилище исходников.

Плюс защита от затирания чужой работы: поле baseVersionId в теле POST /v1/infra/servers/:id/deploy («я основывался вот на этой версии исходников», например v12). Если в хранилище появилась более новая версия, выкладка отклоняется кодом SOURCE_VERSION_STALE с номером актуальной версии и ссылкой на её скачивание — вместо молчаливой перезаписи.

Заявленная версия проверяется на существование: номер впереди хранилища отклоняется кодом BASE_VERSION_NOT_FOUND с номером актуальной версии — иначе защиту снимал бы любой несуществующий номер.

На сервере, где владелец собрал команду разработки, это поле обязательно, как только в хранилище есть хотя бы одна версия: выкладка без него отвечает BASE_VERSION_REQUIRED и называет актуальную версию. Так выкладка вслепую поверх чужих правок отклоняется, даже если о защите не знали. На сервере без команды поле по-прежнему необязательно, и выкладка без него работает как раньше. Чтобы номер версии было откуда взять, ответ операции скачивания версии теперь несёт поле versionId рядом со ссылкой — Хранилище исходного кода.

Отказ 409 EXEC_BUSY теперь называет занявшего сервер: в error.hint.holder приходят имя сотрудника, вид операции и время её начала, а error.hint.reason начинается с имени. У фоновой операции платформы человека-инициатора нет, там holder.name приходит пустым. Поле аддитивное — прежние поля отказа не изменились.

Участник теперь видит сервер в API, а не только работает с ним вслепую. GET /v1/infra/servers отдаёт и те серверы, где человек состоит в команде разработки: такие строки несут блок access с полем via: "collaborator", ролью, перечнем доступных действий и списком открытых вызовов, а собственные строки ключа — access.via: "owner". GET /v1/infra/servers/:id отвечает участнику 200 с урезанной карточкой (без IP, доступа по SSH и управляющего ключа; расходы и порог сна — по роли) вместо прежнего 404. GET /v1/me перечисляет членства в infra.collaboratorServers. Пробуждение машины POST /v1/infra/servers/:id/wake открыто и роли «Разработчик» — раньше право было объявлено матрицей, но вызов отвечал 404. Поиск сотрудника для настройки аудитории, GET /v1/infra/servers/:id/b24-users, открыт роли «Администратор» — раньше отвечал отказом, хотя саму аудиторию эта роль уже настраивает.

Дополнительно: администратор команды теперь может через свой личный API-ключ выполнять все операции сервера, разрешённые его роли, — управление жизненным циклом (остановка, запуск, перезагрузка, сон, ручное восстановление, расписание пробуждения, метрики), настройки (SSH-доступ, порт, подписки на события портала, переключение режима BLACKHOLE/OPEN через PATCH /v1/infra/servers/:id/mode), карточку каталога (PATCH /v1/infra/servers/:id — имя и описание) и аудиторию доступа (политика доступа, список пользователей и подразделений). Раньше эти операции через V1-ключ участника отвечали 403/404 независимо от роли — работали только через личный кабинет. Списки членств в V1 научились отдавать страницу: GET /v1/infra/servers принимает необязательные page и limit и возвращает рядом честный total. Без параметров ответ прежний и полный — контракт не меняется. В /v1/me блок infra.collaboratorServers получил поле total.

Удаление версий исходников остаётся за владельцем: DELETE /v1/infra/servers/:id/sources/:versionId и массовая чистка POST /v1/infra/servers/:id/sources/cleanup отвечают участнику любой роли 403 NOT_AUTHORIZED. Список, скачивание, депонирование и теги ему по-прежнему открыты.

Выкладка участника, которая обязана сохранить переменные окружения, отказывается работать, если прочитать их не удалось: вместо молчаливого выката с пустым окружением приходит 502 GALAXY_PRESERVED_ENV_UNREADABLE с признаком retryable. Приложение при этом продолжает работать на прежней версии.

Отказ по роли отделён от «сервера нет». Управляющая операция, которой роль участника не покрывает, отвечает 403 SERVER_ROLE_FORBIDDEN; в error.hint приходят его роль, необходимый порог, отказанное действие и перечень вызовов, которые ему открыты. Для постороннего ключа существование сервера по-прежнему скрыто — там остаётся 404.

Приложение, у которого удалён владелец, остаётся жить только если в его команде есть администратор — он может подхватить приложение вместо ушедшего. Команда из одних разработчиков приложение не удерживает: управлять им некому, и оно убирается как обычное приложение без владельца.

Поле appUrl в GET /v1/infra/servers, GET /v1/infra/servers/:id и GET /v1/me (блок infra.collaboratorServers) больше не обещает участнику команды ссылку, которую он не вправе открыть. Раньше поле возвращалось непустым, как только у сервера был поддомен, независимо от того, разрешает ли аудитория приложения открыть его именно этому человеку — переход по такой ссылке отвечал отказом. Теперь appUrl пуст, если аудитория (accessPolicy) не открывает приложение всем сотрудникам портала и у участника нет личного гранта доступа. Есть исключение: участнику, которому доступ открыт через подразделение, ссылка пока не показывается, хотя открыть приложение он может — такому участнику её называет владелец или администратор команды.

FIX-0823-3: публичные адреса платформы больше не указывают мимо стенда

Было

Метаданные OAuth (GET /.well-known/oauth-authorization-server) собирали issuer и адреса эндпоинтов от переменной APP_URL, которую деплой бэкенду не задаёт, с запасным значением http://localhost:3000. При незаданной переменной документ объявлял адрес, недоступный ни одному клиенту.

Welcome-страница GET /v1/me — другая причина при том же итоге: она строила от домена инстанса ВСЕ адреса разом, и картинки, и кнопки «Документация» / «На главную». На стенде кнопки уводили в боевой кабинет.

Стало

Метаданные OAuth резолвятся общим механизмом, который в production не принимает loopback-значение и откатывается на публичный домен инстанса.

Welcome-страница разводит два адреса: картинки по-прежнему тянутся с домена инстанса (иначе не откроются), а всё кликабельное ведёт в кабинет того стенда, с которого страницу открыли.

2026-08-22

NEW-0822-1: промокод на занятом месте: предпросмотр отдаёт варианты, погашение их исполняет

Было

POST /v1/cowork/coupon/preview отвечал только тем, что даёт код, а на занятом месте погашение отказывало: «у вас уже оплачен тариф» — и всё, сделать было нечего.

Стало

Предпросмотр возвращает поле decision — что произойдёт с этим местом и что можно предложить человеку: apply (применить), extend (продлить срок того же тарифа), choose (выбор из вариантов, у «применить сейчас» указано losesDays — сколько суток оплаченного срока сгорит) или refuse с кодом отказа. Выбранная ветка передаётся в POST /v1/cowork/coupon/redeem новым необязательным полем action (apply | extend | force | resume-and-apply); без поля поведение прежнее.

Действие проверяется заново в момент погашения: если место успело измениться, ответ — 409 COUPON_ACTION_NOT_AVAILABLE, промокод при этом НЕ тратится. Правильная реакция — перечитать предпросмотр и предложить варианты, которые вернулись сейчас. Незнакомое значение action отвечает 400 INVALID_ACTION — раньше такое тело давало 400 INVALID_CODE, то есть указывало на поле кода, которое в порядке.

BC-0822-2: ответ на выпуск и перевыпуск ключа приведён к документированной форме

Поддержка старого формата до: не предусмотрена

Было

POST /v1/keys и POST /v1/keys/:id/rotate на части порталов возвращали строку ключа целиком, вычитая только секретные поля. Вместе с документированной формой в ответ попадали внутренние поля платформы, которых нет ни в описании формы ключа, ни в ответах GET /v1/keys и GET /v1/keys/:id: preMigrationScopes, ownerActive, deletedAt, isOAuthApp, appId, purpose, scopesAuthoritative, linkedServerId, userAgentAutoModel, webhookScopesRepairedAt, tokenExpiresAt.

Стало

Оба метода возвращают ровно документированную форму ключа — тот же набор полей, что и GET /v1/keys/:id, плюс одноразовый rawKey. Перечисленные внутренние поля из ответа убраны; ни одно из них не было описано в документации и не является частью контракта.

Если ваша интеграция читала любое из них, замените источник: состояние ключа — в status, режим доступа — в accessMode, канал выписки — в issuedVia. Документированные поля, сроки и коды ошибок не менялись.

BC-0822-3: ротация ключа подчиняется подписке маркетплейса наравне с созданием

Поддержка старого формата до: не предусмотрена

Было

На облачном портале без действующей подписки маркетплейса POST /v1/keys/:id/rotate выписывал новый ключ, тогда как POST /v1/keys на том же портале отвечал отказом с требованием подписки. Ротация оставалась обходным путём вокруг этого требования.

Стало

Оба метода отвечают одинаково. Без действующей подписки ротация возвращает 403 с тем же кодом, что и создание: B24_MARKET_SUBSCRIPTION_REQUIRED, либо INT_TARIFF_REQUIRED там, где доступ определяется платным тарифом, а не подпиской.

Затронуты только облачные порталы без подписки. Коробочные порталы, порталы с действующей подпиской и уже выписанные ключи не меняются: ничего не отзывается и не останавливается, ротация возобновляется сразу после активации подписки.

NEW-0822-4: ключ сообщает, каким каналом он выписан, а отказ — точную причину

Было

POST /v1/keys и POST /v1/keys/:id/rotate не сообщали, каким каналом ключ выписан на портале. На порталах, где входящий вебхук выдаёт только модуль платформы, оба метода отвечали ошибкой выписки, даже когда сама выписка на портале работала.

Стало

Форма ключа несёт необязательное поле issuedVia — канал выписки; оно приходит в ответах POST /v1/keys, POST /v1/keys/:id/rotate, GET /v1/keys, GET /v1/keys/:id и PATCH /v1/keys/:id. Тело отказа получило необязательное поле error.reason с точной причиной, а error.code у прежних отказов остался прежним. Порталы, где ключи выдаёт модуль платформы, теперь обслуживаются обоими методами.

Ответ на создание и ротацию теперь одинаков независимо от канала выписки — раньше набор полей у части каналов был уже. Поле message остаётся человекочитаемым и может меняться: разбирайте отказ по error.code, а не по тексту.

FIX-0822-5: отказ перевыпуска по правам приходит одинаково на любом аккаунте

Было

POST /v1/keys/:id/rotate у ключа, на котором из прав остались только права контекста приложения — placement, entity, userfieldtype, — отвечал по-разному в зависимости от того, каким каналом аккаунт выписывает ключи. Там, где ключ выдаёт модуль платформы, приходил 400 PERSONAL_KEY_WEBHOOK_SCOPES_INVALID, а на остальных аккаунтах — общий 502 BITRIX_UNAVAILABLE после неудачной попытки выписки.

Стало

Набор прав проверяется до обращения к аккаунту, поэтому отказ один и тот же везде — 400 PERSONAL_KEY_WEBHOOK_SCOPES_INVALID, тот же код, что у создания и обновления ключа. Менять на клиенте нечего: отказ стал точнее и приходит раньше. Чтобы перевыпуск прошёл, добавьте прежнему ключу право на данные через PATCH /v1/keys/:id.

NEW-0822-6: запросы платформы в Bitrix24 несут подписанный признак происхождения

Было

Портал отвечал отказом по тарифу либо по отсутствию подписки на Маркет, и запрос платформы был для него неотличим от любой другой интеграции.

Стало

Каждый запрос в Bitrix24 несёт заголовок X-Vibecode-Origin — короткоживущий подписанный токен. Токен называет источник: cowork для ключа оплаченного места Коворк/Код, app для остальных ключей пользователей, vibecode для служебных запросов самой платформы. Портал с установленным модулем связи с Вайбкод допускает по этому признаку запросы Коворк/Код; остальные значения допуска не дают. Порталы без модуля заголовок игнорируют, поведение прежних интеграций не меняется.

FIX-0822-7: Надёжная загрузка исходников при деплое на отдельную VM

До: source.url на отдельной виртуальной машине скачивала только сама клиентская VM. Один повтор оркестратора не переживал перемежающиеся сбои egress/NAT, а клиент получал дословный текст сетевой ошибки агента.

После: для серверов, включённых в поэтапный rollout, платформа скачивает архив и передаёт его на VM потоком через туннель, выполняя до четырёх попыток с backoff в общем бюджете пяти минут. Совместимый VM-download fallback и публичный /upload в том же rollout получают ограниченные повторы, безопасную сетевую политику и стабильные сообщения; до включения сохраняется прежний контракт. Код DEPLOY_FAILED сохранён; ошибка шага download в новом режиме дополнена полями causeCode, retryable и attempts. Подробнее — в документации деплоя.

Влияние: менять запросы не нужно. Автоматизация может ветвиться по новым полям вместо разбора текста ошибки; архивы до 500 МБ передаются потоково и не занимают inline-memory слот. У публичного /upload в новом URL-режиме итог долгой фазы приходит под HTTP 200 с keepalive, поэтому успех или ошибку нужно определять по success и error.code в JSON; проверки до запуска и режим до включения сохраняют прежние HTTP-статусы.

BC-0822-8: перевыпуск и возврат в работу ключей, выданных платформой, закрыты

Поддержка старого формата до: не предусмотрена

Было

POST /v1/keys/:id/rotate перевыпускал любой ключ владельца, а PATCH /v1/keys/:id возвращал любой ключ в ACTIVE и продлевал ему срок — в том числе ключи, которые платформа выдаёт на своих отдельных эндпоинтах: ключ настольного приложения Cowork/Code, ключ агента и проектный ключ для деплоя. В выдаче GET /v1/keys такой ключ от личного почти не отличается, поэтому скрипт «перевыпустить или включить обратно все свои ключи» доходил до него наравне с остальными: перевыпуск отвечал 201 со свежим rawKey, включение — 200, причём значение ключа при включении не менялось и снова начинало работать прежнее.

Стало

Обе операции над таким ключом отвечают 403: перевыпуск — с кодом SYSTEM_KEY_ROTATE_FORBIDDEN, возврат в ACTIVE и продление срока — с кодом SYSTEM_KEY_REACTIVATE_FORBIDDEN. В тексте ответа сказано, где именно ключ этого класса выдаётся заново. Ключ в обоих случаях остаётся в прежнем состоянии. Обе операции обходили проверки, которые стоят на выдаче каждого такого ключа: у ключей с правом vibe:cowork — доступ к Cowork/Code, политику аккаунта «сторонние клиенты» и состояние подписки, у проектного ключа для деплоя — три платформенных выключателя, требование ключа Cowork/Code и активную подписку. Перевыпуск вдобавок выдавал бессрочное значение: у проектного ключа для деплоя это снимало семидневный срок жизни, единственное, что ограничивает утёкший ключ. Отзыв ключа и сокращение срока по-прежнему разрешены: это способ остановить ключ, а не выдать его. Прежнее поведение снято сразу, без переходного срока.

Что делать интеграторам

Исключите из массового перевыпуска и массового включения ключи, которые выдаёт платформа: ключ настольного приложения Cowork/Code, ключ агента и проектный ключ для деплоя. Первые два узнаются в ответе GET /v1/keys по праву vibe:cowork; у проектного ключа для деплоя отдельного признака в выдаче нет, поэтому 403 с этими кодами считайте окончательным ответом по ключу и не повторяйте запрос. Ключ настольного приложения выдаётся заново подключением приложения Cowork/Code, ключ агента — перевыпуском ключа агента в кабинете, проектный ключ для деплоя — повторным вызовом POST /v1/cowork/deploy-key. Остальные ключи перевыпускаются и включаются как раньше.

NEW-0822-9: POST /v1/cowork/deploy-key закрыт для ключа внешнего агента

Было

Ручка проверяла только право vibe:cowork.

Стало

У POST /v1/cowork/deploy-key ключ, выписанный для стороннего агента, получает 403 COWORK_HARNESS_KEY_FORBIDDEN. Такой ключ живёт в чужом приложении, а выдача project-deploy ключа отзывает прежний и переносит на новый серверы и приложения владельца. Десктопные и агентские ключи не затронуты.

NEW-0822-10: Реактивация ключа подписки проверяет гейты выдачи

Было

Ключ, оплачиваемый подпиской, снова становился активным обычным изменением поля status, а срок действия снимался значением null. Проверялась только необратимая блокировка, поэтому отзыв ключа администратором Битрикс24 отменялся одним запросом, причём сам секрет не менялся.

Стало

PATCH /v1/keys/:id отвечает 403 с кодом соответствующего гейта, если запрос переводит ключ подписки в ACTIVE либо отодвигает или снимает expiresAt, а доступ к Коворку, политика Битрикс24 «сторонние клиенты» или состояние подписки закрыты. Сужение срока, отзыв и правка остальных полей работают как раньше. Обычные ключи не затронуты.

NEW-0822-11: Ротация ключа подписки проверяет гейты выдачи

Было

Ротация переносила права и назначение ключа дословно, не спрашивая ничего.

Стало

POST /v1/keys/:id/rotate для ключа, оплачиваемого подпиской, отвечает 403 кодом соответствующего гейта, если закрыт доступ к Коворку, выключена политика портала «сторонние клиенты» или подписка неактивна. Обычные ключи ротируются как раньше.

NEW-0822-12: POST /v1/keys отклоняет тарификацию по подписке

Было

Поля billing в теле не существовало, неизвестный ключ отбрасывался схемой молча.

Стало

У POST /v1/keys значение billing: "subscription" отклоняется кодом BILLING_MODE_NOT_SUPPORTED. Ключи, оплачиваемые подпиской Cowork/Code, выдаются только из кабинета платформы Вайбкод: там стоят проверки доступа, политики портала и состояния подписки, которых у публичного маршрута нет. billing: "wallet" и запросы без этого поля работают как раньше.

FIX-0822-13: Отказ по неактивной подписке Cowork/Code ведёт на страницу подписки

Было

Отказ 402 с кодом cowork_subscription_inactive советовал активировать подписку вызовом POST /api/cowork/subscription. Этот маршрут работает только в сессии кабинета, и клиенту, который ходит с API-ключом, он недоступен. Сторонний агентный клиент показывает поле message дословно, поэтому совет выглядел выполнимым, а выполнить его было нечем.

Стало

Тот же отказ на POST /v1/chat/completions называет состояние и место, где оно меняется: подписку возобновляют в разделе Cowork/Code в кабинете платформы Вайбкод, ключ при этом остаётся прежним. Код, статус и остальные поля тела не изменились.

2026-08-21

FIX-0821-1: iblockTypeId=structure принимается в /v1/lists

Было

iblockTypeId принимал только lists, lists_socnet и bitrix_processes. Значение structure (тип инфоблока графика отсутствий) давало 400 INVALID_IBLOCK_TYPE, хотя GET /v1/lists/:iblockId/type его возвращал.

Стало

structure входит в допустимый набор на всех маршрутах /v1/lists. Ответ Битрикс24 на этом типе (данные или 422/403) больше не подменяется нашим 400. Прочие неизвестные типы по-прежнему дают 400 INVALID_IBLOCK_TYPE. Значение по умолчанию не изменилось — lists.

Влияние на интеграторов

Запросы к инфоблокам типа structure, включая штатный график отсутствий absence, теперь доходят до Битрикс24. Существующие интеграции с другими типами менять не нужно.

FIX-0821-2: currencyId и явный select невозвращаемых полей

Было

POST /v1/products и PATCH /v1/products/:id принимали валюту только под именем currency. Явный select поля с признаком notReturned: true принимался без предупреждения, хотя значения в ответе не было.

Стало

Методы записи товара принимают currencyId как дополнительное имя currency. Если переданы оба имени, используется currency, а в ответах по-прежнему приходит только currency. Дополнительное имя помечено как writeOnly и notReturned в /fields, /v1/guide и OpenAPI. Явный select любого невозвращаемого канонического имени теперь добавляет предупреждение UNKNOWN_SELECT_FIELD: это products.currencyId, tasks.realStatus, product-sections.sort, bank-details.entityTypeId, telephony-lines.serverName, bizproc-templates.templateData и catalog-products.iblockSection. Нативное имя Битрикс24 select=CURRENCY_ID по-прежнему выбирает читаемое поле products.currency. Поведение каждого имени описано в справочниках полей товаров, задач, разделов товаров, банковских реквизитов, линий телефонии, шаблонов бизнес-процессов и товаров каталога. Фильтрация по валюте не поддерживается и возвращает 400 UNSUPPORTED_FILTER.

Влияние на интеграторов

Запросы чтения продолжают возвращать 200 и остальные выбранные поля, но теперь содержат предупреждение. Уберите невозвращаемые имена из select: для валюты используйте currency, для фактического статуса задачи — status.

FIX-0821-3: фильтр товаров по описанию больше не отклоняется

Было

GET /v1/products с filter[description] и POST /v1/products/search с тем же фильтром отвечали 400 UNSUPPORTED_FILTER, хотя описание сохранялось при создании товара.

Стало

Точное равенство и $in по полю description принимаются так же, как по name. Операторы, включая $contains, по-прежнему отвечают 400. Фильтры по price и currency не изменились.

FIX-0821-4: отказ при создании сотрудника без отдела называет поле, которое нужно передать

Было

POST /v1/users без departmentId на портале с установленным модулем extranet отвечал 422 с текстом no_extranet_field. Поля с таким именем нет ни в теле запроса, ни в выдаче GET /v1/users/fields, и ничто в ответе не указывало на отдел — по такому отказу нельзя было понять, что именно исправить. Сотрудник при этом не создавался.

Стало

Ответ дополнен подсказкой: она называет поле в обоих написаниях — UF_DEPARTMENT для прямого вызова и departmentId для обёртки POST /v1/users (оба принимаются), указывает источник значений GET /v1/departments, приводит корневой отдел как рабочий пример и упоминает POST /v1/users/invite, который подставляет отдел сам. Для внешнего пользователя названа альтернатива — EXTRANET вместе с SONET_GROUP_ID вместо отдела. Описание поля departmentId в справочнике полей теперь тоже говорит про это требование, поэтому условие видно до отправки запроса.

Влияние на интеграторов

Действий не требуется: код ответа и структура конверта не изменились, добавилась только подсказка. Поле departmentId намеренно не сделано обязательным на стороне API Вайбкод — портал без модуля extranet принимает создание без отдела, и жёсткое требование сломало бы такие запросы.

NEW-0821-5: метаданные диалога Открытой линии через API

Добавлен эндпоинт POST /v1/openlines/dialogs/lookup — обёртка над методом Битрикс24 imopenlines.dialog.get. Возвращает карточку одного диалога Открытой линии (название, тип, линию, число сообщений, даты) по одному из идентификаторов: chatId, dialogId или sessionId. Переписку метод не отдаёт. Требует скоуп imopenlines.

В ответе есть производное поле lineId — идентификатор линии, разобранный из привязки диалога; он даёт вход в GET /v1/openline-configs/:id. Поиск по sessionId позволяет найти диалог прямо по идентификатору сессии из поля id ответа POST /v1/openlines/sessions/search.

BC-0821-6: структура в скалярном поле на записи отклоняется вместо тихой потери значения

Поддержка старого формата до: не предусмотрена

Было

Запись сущности принимала в поле, объявленное скаляром, объект или массив и отвечала успехом. POST /v1/deals с телом {"title": {"a": 1}} возвращал 201, а в карточке сделки оказывалась строка Array: Битрикс24 приводит массив к строке именно так. Значение восстановить было нельзя, и ошибки клиент не видел.

Стало

Такой запрос отклоняется до вызова Битрикс24 — 400 с кодом INVALID_PARAMS и именем поля. Правило применяется к полям, объявленным как string, number, boolean, date и datetime, на записи сущностей по путям /v1/<entity>: создание и обновление, POST /v1/batch, пакетная запись по сущности и импорт.

Поведение, которое НЕ изменилось: число и логическое значение в строковом поле по-прежнему принимаются (Битрикс24 сохраняет 123456 и 1 — значение не теряется), null по-прежнему принимается, а поля, объявленные как object, array и многозначные, структуру принимали и принимают.

Влияние на интеграторов

Проверьте код, который собирает тело записи из внешнего источника: если в скалярное поле уезжал объект или массив, раньше запрос завершался успехом с потерей значения, а теперь вернёт 400. Отправляйте в такое поле скалярное значение.

Границы названы явно. Имя, которого нет в схеме сущности (пользовательские поля UF_*, propertyNNN, опечатка), проверке не подлежит — у него нет объявленного типа. Форма отказа различается по поверхности, и различается охват. Общий пакетный вызов отклоняет только свой подвызов и кладёт отказ в data.errors["<id>"] отдельными полями code и message. Пакетная запись по одной сущности и импорт отклоняют весь запрос: 400 с кодом BATCH_ITEM_VALIDATION или IMPORT_ITEM_VALIDATION, а номер элемента и INVALID_PARAMS — внутри message. Поэлементных результатов в таком ответе нет, и в Битрикс24 не уходит ни одной записи. Несколько обособленных маршрутов записи проверку пока не проходят — адреса, комментарии задач, шаблоны документов, настройки открытых линий и товарные строки; там прежнее поведение сохраняется, и они закрываются отдельно.

FIX-0821-7: PATCH /v1/infra/servers/:id/access-policy сохраняет список доступа при смене политики

Было

При переключении PATCH /v1/infra/servers/:id/access-policy с NAMED_USERS или DEPARTMENT на любую другую политику список доступа (пользователи и отделы, добавленные через POST /access) стирался безвозвратно — хотя документация обещала, что записи сохраняются в базе и просто не применяются, пока политика не станет именной снова.

Стало

Список сохраняется при уходе с NAMED_USERS/DEPARTMENT — поведение снова соответствует документации.

Влияние на интеграторов

Действий не требуется — поведение приведено в соответствие с уже опубликованной документацией.

BC-0821-8: занятость общего exec-канала галактики отдаётся как 409, а не 502

Поддержка старого формата до: не предусмотрена

Было

POST /v1/infra/servers/:id/exec для приложения в галактике (kind: "GALAXY_APP") отвечал 502 с кодом EXEC_BUSY, когда общий exec-канал хоста был занят. Ни заголовка Retry-After, ни полей retryable / retryAfter в ответе не было, поэтому машинный клиент читал отказ как сбой шлюза и не повторял вызов. Тот же по смыслу отказ на POST /v1/infra/servers/:id/deploy при этом уже приходил как 409 с сигналом повтора.

Подсказка error.hint в ответах 409 EXEC_BUSY на галактических маршрутах предлагала расклинить канал вызовом POST /v1/infra/servers/:id/unstick. Владельцу приложения и владельцу хоста этот вызов недоступен по контракту — он отвечает 409 GALAXY_UNSTICK_UNSUPPORTED, потому что exec-канал общий для всех приложений хоста.

Стало

Занятость общего exec-канала хоста приходит как 409 с кодом EXEC_BUSY, заголовком Retry-After и полями retryable: true / retryAfter (секунды) — тем же контрактом, что и остальные отказы «занято». Прочие ошибки выполнения у приложения в галактике по-прежнему приходят со статусом 502.

На одном статусе 409 теперь живут два разных состояния, и они машинно различимы по наличию поля error.hint.autoExpiresInSeconds: лок самого приложения несёт его (остаток времени жизни лока), занятость общего канала хоста — нет. По recoveryAction их различать нельзя: у приложения в галактике и у самого галактического хоста это поле конкретного вызова не называет вовсе — только «подождать» и «повторить», — потому что снятие лока способно оборвать выполняющуюся операцию, а на общем хосте вместе с ней и команды соседних приложений. Машинный совет поэтому в обоих состояниях один — подождать и повторить по Retry-After.

⚠️ Сказанное относится к галактическим состояниям, а не к маршруту /exec целиком. На отдельной виртуальной машине (kind: "STANDALONE") recoveryAction того же 409 по-прежнему называет DELETE /v1/infra/servers/:id/lock, безусловно, — и вызов этот против идущего деплоя снимает пер-серверную сериализацию, на которую деплой опирается. Поэтому исполнять recoveryAction не читая error.hint.recovery нельзя ни на одном состоянии. Адрес снятия лока остаётся в error.hint.recovery, с подставленным идентификатором сервера, требованием ключа-владельца и оговоркой, что звать его следует, только убедившись, что ничего не выполняется.

Тем же контрактом ответило и чтение журнала. GET /v1/infra/servers/:id/logs у приложения в галактике идёт через тот же общий exec-канал хоста, и при занятом канале отвечал 200 с пустым списком logs — то есть утверждал, что у приложения нет вывода. Теперь занятость приходит как 409 с Retry-After, а прочие сбои чтения — как 502 с кодом агента.

Что делать интеграторам

Клиенту, который ветвился по HTTP-статусу и считал 502 на /exec терминальным сбоем, надо перестать это делать: занятость теперь приходит как 409 и её следует повторять с интервалом из Retry-After. Клиенту, который читал error.code, менять нечего. Если ваш сценарий вызывал POST /v1/infra/servers/:id/unstick по подсказке платформы — на галактических серверах он не работал и раньше, замените его повтором вызова, а при устойчивом отказе обращайтесь в поддержку. Если вы разводите два состояния 409 на /exec в коде — сверяйтесь с наличием error.hint.autoExpiresInSeconds, а не с текстом recoveryAction.

BC-0821-9: пакет по одной сущности объясняет, что сделал с select

Поддержка старого формата до: не предусмотрена

Было

POST /v1/{entity}/batch с действием list применял select к ответу, но молчал обо всём, что при этом отбросил. Имя, которого у сущности нет, просто исчезало: запись приходила суженной, а объяснения не было нигде — ни ошибки, ни предупреждения. У одиночного списка и у глобального пакета такое имя перечисляется предупреждением, и только у этой двери отбор полей был полностью безмолвным.

Стало

Подвызов несёт своё meta.warnings с кодом UNKNOWN_SELECT_FIELD и именем поля — ровно той же формы, что у одиночного списка. Предупреждение принадлежит СВОЕМУ подвызову: соседние элементы пакета своё meta не получают, и пустым массивом ключ не приезжает — сказать нечего, значит ключа нет.

Сущности, у которых незнакомое имя отвечает ошибкой (сегодня это события календаря), теперь отклоняют такой подвызов и здесь — UNKNOWN_SELECT_FIELD до вызова Битрикс24. Отказ per-call, как в глобальном пакете: падает только этот элемент, остальные сорок девять выполняются. Раньше на этой двери жёсткий отбор не срабатывал вовсе.

Значение * по-прежнему означает «вернуть все поля»: отбора нет, и незнакомое имя рядом с ним ничего не отклоняет. Предупреждение при этом приходит: select вида *,titel вернёт все поля и скажет, что второе имя ничего не значит, — ровно как на одиночном списке и в общем пакете.

Влияние на интеграторов

Аддитивная половина ничего не ломает: meta — новый необязательный ключ рядом с data и total. Ломающая — жёсткий отбор: подвызов, который раньше отвечал суженной записью и кодом 200, на событиях календаря теперь отвечает error в своём слоте. Окна поддержки нет намеренно: прежнее поведение и было дефектом — запрошенное имя молча пропадало, и отличить это от «поля нет в записи» было нечем. Проверьте обработчики, которые считали отсутствие ошибки признаком того, что все имена из select распознаны.

NEW-0821-10: транскрипт сессии Открытой линии через API

Добавлен эндпоинт POST /v1/openlines/sessions/history — обёртка над методом Битрикс24 imopenlines.session.history.get. Возвращает переписку последней сессии чата Открытой линии по его идентификатору: сообщения, участников и метаданные файлов одним ответом. Требует скоуп imopenlines.

Вход только по идентификатору чата (chatId, принимается и форма chat2043). По нему берётся последняя сессия чата. Пагинации у метода нет — транскрипт приходит целиком. Для постраничного чтения сообщений остаётся GET /v1/chats/:dialogId/messages.

Эндпоинт включается платформой Вайбкод постепенно: пока он не включён, вызов отвечает 403 OPENLINES_HISTORY_DISABLED — это признак того, что возможность ещё не активирована, а не ошибка интеграции.

NEW-0821-11: мультипарт-деплой galaxy-приложения хранит архив в хранилище исходников

Multipart-выкладка galaxy-приложения больше не собирает архив целиком в памяти платформы: там, где включено депонирование multipart, архив уезжает версией в хранилище исходников и попадает на хост по подписанной ссылке. Где хосту разрешено забирать архив самому, распознанный tar.gz он скачивает сам — со сверкой размера и контрольной суммы, записанных за версией, — и тогда причина неудачной загрузки приходит отдельным кодом UPLOAD_DOWNLOAD_FAILED / UPLOAD_EXTRACT_FAILED / UPLOAD_NO_SPACE в хвосте buildLog; ZIP и нераспознанный формат едут прежним путём, через агента. Предел на размер архива в теле запроса при этом не меняется — тело по-прежнему проходит через платформу. Версия остаётся в депо и тогда, когда деплой провалился: её видно в списке версий и можно выложить повторно по source.versionId.

2026-08-20

NEW-0820-1: снимок подписки Коворка описан в спецификации API

Было

Ручка GET /v1/cowork/me работала и была указана в путеводителе /v1/guide, но в спецификации V1 её не было. Сгенерировать по спеке клиент или проверить форму ответа было нельзя — оставалось читать путеводитель глазами.

Стало

Операция описана: тариф и состояние подписки, три окна квоты как целые проценты с датой сброса, дата следующего списания. Отдельно отмечено, что тело успешного ответа — сам объект, без обёртки success, тогда как ошибки приходят в обычном конверте { success: false, error: { code, message } }. Блоки offPeak и relief описаны как отсутствующие при выключенной возможности: проверять нужно наличие ключа, а не сравнивать значение с null.

FIX-0820-2: примеры `include` используют имена связей

Было

OpenAPI и MCP показывали для параметра include имена связанных сущностей во множественном числе, из-за чего запрос завершался ошибкой INVALID_INCLUDE.

Стало

Примеры используют фактические имена связей contact,company.

FIX-0820-3: MCP корректно передаёт параметры действий с записями таймлайна

Было

Инструмент manage_timeline_log принимал только строковый id, не передавал тело запросов pin и unpin, а также query-параметры запросов get_note и delete_note.

Стало

Инструмент принимает строковый или числовой id и передаёт обязательные параметры этих действий в /v1/timeline-logs/*. Описание удаления теперь прямо сообщает, что personal-ключи не могут удалять такие записи: удаление доступно только тому же OAuth-приложению, которое создало запись.

NEW-0820-4: разделы календаря получили маршруты схемы и поиска

Было

Для calendar-sections отсутствовали GET /v1/calendar-sections/fields и POST /v1/calendar-sections/search, поэтому агент не мог заранее узнать поля и допустимые типы календарей или использовать стандартную поверхность поиска.

Стало

GET /v1/calendar-sections/fields возвращает статическую схему с обязательными полями и типами user, group, company_calendar, location. POST /v1/calendar-sections/search поддерживает owner-контекст через filter.type и filter.ownerId; дополнительные фильтры честно отклоняются как неподдерживаемые методом Битрикс24.

FIX-0820-5: персональные ключи показывают только исполнимые права

Было

GET /v1/me мог сообщать персональному ключу права placement, entity и userfieldtype, хотя эти возможности требуют контекста OAuth-приложения. Создание и изменение ключа также принимало userfieldtype как обычное право.

Стало

GET /v1/me, создание и изменение персональных ключей исключают placement, entity и userfieldtype. Право userfieldconfig остаётся доступным. OAuth-ключи приложений не изменились.

Влияние на интеграторов

Дополнительных действий не требуется. Для встраиваний и пользовательских типов полей используйте OAuth-ключ приложения.

FIX-0820-6: право на задачи действует на всех операциях задач, как бы ключ ни был выписан

Было

У ключа с правом на задачи часть операций задач отвечала отказом по правам — какая именно, зависело от того, каким способом портал выписал вебхук ключа. В журнале задачи Битрикс24 различает два написания одного права (task и tasks), и они открывают разные группы операций: старые операции задач требуют первого написания, новые — второго. Ключ сохранял то написание, которое выбрал владелец, и на части путей выписки на портал уходило только оно. Внутри POST /v1/batch такой отказ приходил внутри ответа 200 — по подкоманде, а не по всему запросу.

Стало

Право на задачи регистрируется на портале в обоих написаниях независимо от способа выписки, поэтому операции задач доступны все. Ключи, выписанные раньше, приводятся к тому же состоянию при переподключении вебхука. Состав прав самого ключа и ответ GET /v1/me не меняются — клиенту менять ничего не нужно.

FIX-0820-7: создание бот-чата отдаёт идентификатор, который принимают остальные действия

Было

Инструмент manage_bot_chat после создания чата показывал на виду числовой идентификатор — data.chat.id и data.recentConfig.chatId. Следующие действия с этим числом — GET /v1/bots/:botId/chats/:dialogId, выход из чата, передача владения — отвечали ошибкой BITRIX_ERROR «Указанный чат не существует»: для Битрикс24 голое число в dialogId означает личный диалог с пользователем с таким номером, а не групповой чат с таким id. Пригодный идентификатор вида chat471 лежал глубже, в data.chat.dialogId, и в описании инструмента этот формат назван не был.

Стало

Ответ создания чата начинается с поля chatId со значением вида chat471 — именно его принимают остальные действия инструмента. Поля Битрикс24 data.chat.id и data.recentConfig.chatId не изменились и остались числовыми. Описание параметра chatId теперь называет оба формата: chatN — групповой чат, голое число — личный диалог с пользователем.

Влияние на интеграторов

Клиентам MCP дополнительных действий не требуется. Ответ POST /v1/bots/:botId/chats для REST не менялся: там по-прежнему нужно брать chat.dialogId, а не числовой chat.id. Запрос GET /v1/bots/:botId/chats/:dialogId и остальные действия принимают и chatN, и число как идентификатор личного диалога.

FIX-0820-8: отказ ИИ-провайдера: понятный текст вместо чужой прозы и одинаковая форма ошибки в потоке и без него

Было

Когда ИИ-провайдер отклонял обращение платформы, POST /v1/chat/completions, POST /v1/embeddings и POST /v1/audio/transcriptions отдавали 502 ai_provider_unavailable, а в error.message приезжал текст провайдера дословно. Провайдер при этом мог назвать ошибкой авторизации собственный инфраструктурный сбой — и клиент читал в ответе слово про авторизацию там, где ни его ключ, ни его сеть ни при чём, и уходил проверять их.

Поток (stream: true) отдавал ту же ошибку в другой форме, чем обычный запрос: code приходил заглавными буквами вместо строчных, поля type не было вовсе, а retryAfter и retryable доставались только обрыву простоя и перегрузке. Клиент, который ветвится по error.type или по retryable, не мог отличить временный отказ от окончательного и не знал, сколько ждать.

Стало

Текст ответа зависит от того, чей креденшл отклонил провайдер. Свой ключ, подключённый вызывающим (BYOK), — сообщение провайдера проходит как раньше: оно адресовано владельцу ключа и говорит ему, что делать. Креденшл портала — ответ называет объект и того, кто может его обновить, и прямо предупреждает, что повтор не поможет. Креденшл платформы — ответ сообщает, что дело не в ключе вызывающего и не в его сети. Статус 502, код ai_provider_unavailable и поле providerStatusCode во всех случаях прежние.

Ошибка в потоке приняла ту же форму, что и без потока: code строчными буквами, поле type на месте, а всякий кадр с паузой retryAfter теперь несёт и retryable: true. Поля появились у временной недоступности провайдера, у ограничения частоты и у остывания после череды неудачных вызовов — раньше их нёс только обрыв простоя.

Признаки повторяемости согласованы с текстом ответа: отказ, который повтор не вылечит, не несёт ни паузы, ни retryable. Это отказ по содержимому запроса (ai_provider_rejected) и отклонённый провайдером креденшл портала или свой ключ вызывающего — там ответ прямо говорит, что нужно обновить креденшл. Временная недоступность провайдера повторяемой остаётся.

FIX-0820-9: вложение с расширением, не совпадающим с содержимым, больше не отклоняется

Было

POST /v1/feedback/attachments сверял тип, объявленный клиентом, с сигнатурой самого файла и отвечал 400 MIME_MISMATCH, если они расходились. Тип клиент берёт из расширения, поэтому JPEG, сохранённый как image.png, приезжал как image/png и получал отказ — хотя оба формата разрешены, а файл цел.

Стало

Обработка ведётся от фактического формата файла. Расхождение между двумя разрешёнными форматами (PNG, JPEG, WebP, GIF) принимается, файл переупаковывается по содержимому, и его mime в ответе — результат переупаковки, как и раньше. MIME_MISMATCH остался за единственным случаем: сигнатура файла не распознана вовсе.

FIX-0820-10: MCP сохраняет единый формат идентификатора чата

Было

Действие manage_chat.add_users передавало значение вида chat457 в числовой backend-маршрут без нормализации, поэтому Bitrix24 отвечал ошибкой про пустой ID чата. Созданный чат нельзя было покинуть через тот же MCP-инструмент, а find выглядел как текстовый поиск.

Стало

add_users и новое действие leave принимают стандартное значение dialogId вида chatN и передают числовой ID маршруту. add_users сохраняет прежний положительный числовой ID чата для совместимости, а новое необратимое действие leave требует однозначный chatN; вызывающий должен преобразовать числовой ID из ответа создания в chatN перед вызовом leave. Отсутствие или некорректный ID отклоняется до сетевого вызова. Инструмент предупреждает, что владелец должен сначала передать владение чатом, используя числовой ID без префикса chat. Описание find прямо указывает обязательные entityType и entityId для поиска чата, связанного с CRM-сущностью.

FIX-0820-11: адреса применяют select — запись сужается, а незнакомое имя больше не теряется

Было

Параметр select описан для всех сущностей, но у адресов он не делал ничего. Список, поиск и получение записи по составному ключу отвечали полной записью со всеми полями, сколько бы имён ни перечислил вызывающий, а незнакомое имя пропадало без следа — ни ошибки, ни предупреждения. Это была единственная сущность, где отбор полей молчал полностью.

Стало

Все три двери адресов применяют select так же, как остальные сущности: в записях остаются только перечисленные поля, принимаются канонические имена и исходные имена Битрикс24 (CITY проецирует city), а * по-прежнему означает «вернуть все поля».

Незнакомое имя ведёт себя по-разному на разных дверях. Получение записи по составному ключу отбирает поля на стороне Вайбкод, поэтому имя, которого нет в схеме, приходит предупреждением UNKNOWN_SELECT_FIELD в meta.warnings, а сама запись возвращается — блок meta там добавляется только при наличии предупреждений. Список и поиск передают перечисленные имена дальше в Битрикс24, поэтому исход там задаёт аккаунт: не знающий такого поля отклоняет весь вызов — ответ 422 BITRIX_ERROR, имя названо в сообщении, данные не приходят.

Составной ключ адреса — typeId, entityTypeId, entityId — возвращается всегда, даже если в select его не перечислили. У большинства сущностей ту же роль играет одно поле id: по нему запись отличают от соседней и по нему же строят адрес для изменения и удаления. У адресов эту роль выполняют все три поля сразу.

Отдельно: имя id в select подчиняется тому же разделению. На получении по составному ключу оно больше не считается опечаткой — собственного id у адресов нет, поэтому раньше select=id,city приносил предупреждение о поле id, а теперь такой запрос читается как «покажи, что это за запись» и возвращает составной ключ. На списке и поиске имя id уходит в Битрикс24 и подчиняется той же проверке аккаунта.

Отбор полей перестал срезать ключ маршрута у смарт-процессов и телефонных линий

У большинства сущностей адрес операции строится по id, и select=id,… работал как ожидается. Но есть две, где ключ маршрута зовётся иначе, и отбор полей его выбрасывал: у смарт-процессов это entityTypeId, у телефонных линий (/v1/telephony-lines) — number. Теперь оба поля остаются в ответе, даже если в select их не перечислили: GET /v1/telephony-lines?select=name отдаёт {name, number} вместо {name}.

У смарт-процессов это снимает ловушку: в записи есть ещё и внутреннее поле id, которое в адресах операций не участвует, — раньше при отборе полей оставалось именно оно, и адрес, собранный по нему, приводил к другой записи.

На остальных сущностях правило прежнее и его стоит помнить: если вы перечисляете поля в select, перечисляйте и id — методы Битрикс24, которые отбор уважают (сделки, контакты, компании, лиды, предложения, элементы смарт-процессов), возвращают ровно запрошенное, и незапрошенный id в ответ не попадёт.

Влияние на интеграторов

Менять ничего не нужно, если select к адресам не передавался — ответ такой же, как раньше. Вызов, который передавал select и рассчитывал получить полную запись, теперь получит только запрошенные поля: это и есть задокументированное поведение параметра, приведённое в соответствие с остальными сущностями. Имена полей сверяйте с GET /v1/addresses/fields.

BC-0820-12: подставленный токен бота проверяется по длине и алфавиту

Поддержка старого формата до: не предусмотрена

Было

В PATCH /v1/bots/:botId можно было подставить любое значение fields.botToken — например support-bot-2026. Вызов отвечал 200, портал принимал новый токен. Проверок длины и формы не было: кап в 40 символов Битрикс24 применяет при регистрации бота и при переводе его в режим вебхука, а на обычном обновлении — нет.

Стало

Подставленный токен проверяется до вызова портала: от 32 до 40 символов из алфавита [A-Za-z0-9_-]. Любое присутствующее в запросе значение, которое в эту границу не укладывается — короткое, пустое, с посторонними символами или вовсе не строка, — отбивается с 400 и кодом BOT_TOKEN_INVALID; портал при этом не вызывается и токен бота не меняется. Границы те же, что и при регистрации бота, а нижняя совпадает с длиной токена, который платформа выдаёт сама.

Причина в том, что этот же токен аутентифицирует события, приходящие боту на POST /api/bot/webhook: короткий или угадываемый токен позволял бы подделать событие бота без всякой аутентификации.

Что делать интеграторам

Если вы подставляете токен сами, используйте случайное значение на 32 символа и длиннее (например 32 шестнадцатеричных символа) из алфавита [A-Za-z0-9_-]. Ничего не подставляете — делать нечего: токен выдаёт платформа, и он границу проходит. Окно поддержки прежнего поведения не предусмотрено намеренно: слабый токен и раньше оставлял бота нерабочим, потому что платформа такое значение у себя не сохраняла.

FIX-0820-13: события бота, приходящие вебхуком без адреса портала, больше не теряются

Было

Приёмник POST /api/bot/webhook опознавал бота по паре «портал + номер бота»: номер бота — внутренняя последовательность портала, а не глобальный идентификатор, поэтому портал приходилось определять по auth.domain или auth.member_id из тела запроса. У бота, зарегистрированного через входящий вебхук, ни одно из этих полей не является надёжным: адрес портала приходит не в каждом конверте, а member_id опознаётся только у порталов, у которых он известен платформе. Событие без адреса портала приёмник отбивал 403 AUTH_FAILED, а повторных отправок у imbot-вебхуков нет: сообщение пользователя терялось безвозвратно, и на стороне платформы это выглядело как «боту ничего не писали».

Стало

Бот опознаётся по auth.application_token с верхнего уровня — для вебхук-бота это значение само по себе указывает на конкретного бота, портал для этого больше не нужен. Прежний путь по домену сохранён и работает как раньше: он обслуживает регистрации, у которых токен приходит в другой форме. Проверка подлинности токена не ослаблена — сравнение осталось constant-time, а событие с чужим номером бота в теле по-прежнему отбивается.

Заодно у отбивок приёмника появились отдельные коды — BOT_WEBHOOK_AUTH_FAILED, BOT_WEBHOOK_ID_MISMATCH, BOT_WEBHOOK_INVALID_BOT_ID, BOT_WEBHOOK_BOT_DISABLED — так потеря событий видна в статистике отказов, а не только в логах.

Ещё одно изменение в PATCH /v1/bots/:botId

Токен, подставленный вызывающим в fields.botToken, теперь записывается и на стороне платформы. Раньше он уходил только в Битрикс24, а платформа оставалась на прежнем значении — бот молча становился нерабочим в обе стороны: исходящие вызовы получали 401, входящие события отбивались.

Влияние на интеграторов

Действий не требуется. HTTP-коды ответов приёмника не изменились (403 / 400 / 410), поле error в теле осталось прежним — рядом с ним добавилось поле code. Ботам, чьи события раньше отбивались, доставка включается сама, без перерегистрации и без смены токена.

FIX-0820-14: порталу уходят только права Битрикс24, а платформенный ключ не получает вебхук при перевыпуске

Было

При выписке вебхука на портал уходил не только набор прав Битрикс24, но и платформенные права Вайбкода — vibe:infra, vibe:ai, vibe:search, vibe:storage. Битрикс24 таких прав не знает и молча их игнорировал, поэтому реальные права ключа от этого не менялись, но набор на проводе у разных способов выписки был разный: кабинет отправлял одно, POST /v1/keys — другое, коробочный канал — третье.

Из того же расхождения следовал видимый эффект на перевыпуске секрета. Ключ, у которого в наборе только платформенные права, вебхука на портале не получает — регистрировать нечего. Но перевыпуск такого ключа отправлял на портал набор из одних платформенных прав, и в ответ приходил вебхук, которого у ключа до перевыпуска не было: поле b24Ready менялось с false на true, хотя обращаться к Битрикс24 этим ключом по-прежнему было нельзя.

Стало

Порталу уходят только права самого Битрикс24 — одинаково на всех каналах выписки. Если после этого прав Битрикс24 в наборе не остаётся, вебхук не запрашивается вовсе: ключ живёт как платформенный, b24Ready остаётся false и на создании, и на перевыпуске.

Влияние на интеграторов

Форма ответа, коды ошибок и хранимый набор прав ключа не изменились: vibe:* по-прежнему видны в scopes и по-прежнему открывают платформенные разделы /v1/ai, /v1/search, /v1/storage, /v1/infra. Действий не требуется. Отличие заметит только тот, кто перевыпускал ключ без прав Битрикс24 и ожидал у него b24Ready: true — теперь такой ключ честно отвечает false, как и при создании.

FIX-0820-15: на международном сегменте отказ по тарифу называет план Vibe+

Было

Порталу на бесплатном тарифе Битрикс24 приходил отказ INT_TARIFF_REQUIRED, и его userMessage называл условием доступа платный тариф Битрикс24. Страница цен при этом говорит, что полный доступ к платформе Вайбкод открывает план линейки Vibe+ — клиент читал два разных условия в одном продукте.

Стало

На международном сегменте userMessage этого кода называет план Vibe+ — то же условие, что на странице цен. Затронуты все поверхности кода: тело отказа при создании и пробуждении инфраструктуры, слот capabilities.servers.create в GET /v1/me, страница- заглушка шлюза и тост выписки ключей.

Машинное поле details.requiredTariffs не изменилось: оно по-прежнему перечисляет тарифы, которые снимают отказ. Совет о покупке строится по нему, человеческая строка объясняет причину. Сам код отказа, его HTTP-статус и структура конверта не изменились.

Порталы Казахстана и Узбекистана, самохостящиеся аккаунты и российский сегмент получают прежнюю копию — линейку Vibe+ там не продают.

NEW-0820-16: именной промокод — код срабатывает только у адресата

Было

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

Стало

Код можно выпустить на конкретную почту. Такой код проверяется по почте аккаунта Вайбкод: не совпала — POST /v1/cowork/coupon/redeem отвечает 409 с кодом COUPON_NOT_ASSIGNED_TO_YOU, а сам код остаётся неизрасходованным и по-прежнему доступен адресату. Предпросмотр POST /v1/cowork/coupon/preview возвращает тот же код в поле reason.

Коды без адресата ведут себя как раньше — их погашает любой сотрудник портала.

NEW-0820-17: блокировка утёкшего ключа сообщает, погашены ли его ссылки доступа

POST /v1/platform/keys/revoke-leaked теперь гасит вместе с ключом и выписанные им ссылки доступа, а в ответе появилось поле tokensRevoked. Ключ блокируется всегда; tokensRevoked: false означает, что сам ключ уже мёртв, а его ссылки ещё живы — гашение не прошло из-за временного сбоя базы. Повторите тот же вызов: он идемпотентен и доводит дело до конца.

FIX-0820-18: отзыв ключа закрывает и выданные им ссылки доступа

Было

PATCH /v1/keys/:id со статусом REVOKED гасил сам ключ, но не трогал токены доступа, которые этот ключ успел выписать: постоянные ссылки на приложение и bearer-токены продолжали работать до собственного срока годности, а он доходит до десяти лет. Владелец отзывал ключ и считал доступ закрытым, хотя ссылки оставались живыми.

Стало

Отзыв гасит выданные ключом токены доступа вместе с ним и снимает их в шлюзе. Поведение совпало с удалением ключа, где так было всегда, и одинаково на обеих поверхностях — через API и через кабинет. Ссылки, выданные ключами, которые отозвали РАНЬШЕ этого обновления, тоже закрыты: их гасит разовая правка данных, а не только новый порядок. Прочие изменения ключа (имя, лимит, режим доступа) токены по-прежнему не трогают.

2026-08-19

FIX-0819-1: отправка сообщений ботом принимает верхнеуровневый dialogId

Было

При отправке сообщения ботом через MCP верхнеуровневый dialogId не попадал в запрос POST /v1/bots/:botId/messages, а запрос без адресата возвращал ошибку платформы.

Стало

Верхнеуровневый dialogId передаётся как канонический адресат, а прежний body.dialogId остаётся совместимым запасным форматом. Запрос без непустой строки dialogId возвращает MISSING_PARAMS до отправки сообщения.

FIX-0819-2: заголовок `X-Tariff-Checked-At` отдаётся только после удачной сверки тарифа

Было

Отметка ставилась после ЛЮБОЙ попытки сверки тарифа с Битрикс24, включая неудачную: портал, у которого сверка упала по таймауту или по нехватке прав ключа, всё равно получал свежий X-Tariff-Checked-At. Отличить «тариф прочитан час назад» от «час назад попытались и не смогли» по заголовкам было нельзя.

Стало

Заголовок отдаётся, только когда последняя сверка действительно прочитала тариф. Неудачная попытка заголовок не выставляет вовсе — его отсутствие теперь значит «достоверной сверки нет», а не «портал никогда не проверяли». Заголовок X-Tariff-Is-Commercial не изменился и приходит как раньше.

FIX-0819-3: уточнено встраивание личным ключом и обработан OAuth без state

Было

GET /v1/me ошибочно утверждал, что личный ключ вообще не может опубликовать встроенное приложение. Если Битрикс24 возвращал OAuth-код без state, /v1/bitrix-handler оставался на промежуточной странице.

Стало

portalEmbedding описывает рабочий цикл POST /v1/apps → OAuth-авторизация → POST /v1/apps/:id/publish и отдельно предупреждает, что личный ключ не вызывает placements/bind напрямую и сам не даёт transparent auth. OAuth-callback без state безопасно перенаправляется на контролируемую страницу ошибки без передачи кода.

FIX-0819-4: денежные пути переживают гонку за общий баланс портала

Было

Под редкой гонкой одновременных записей в баланс портала (Serializable-транзакция Postgres, конфликт 40001) один из конкурирующих денежных путей мог завершиться отказом и не быть доведён до конца: приём платежа по вебхуку платёжного провайдера, события Битрикс24 (начисление и отзыв бонуса, отзыв пакета), реалтайм-списание за веб-поиск, продление подписки и смена тарифа Коворка, суточные краны (в т.ч. истечение пакетов вайбов) — все они писали баланс без повтора и падали на первой же конкурентной попытке.

Стало

Каждый из этих путей переживает конфликт сериализации: платформа автоматически повторяет конкретную денежную операцию до достижения результата (с конечным числом попыток и джиттером между ними), поэтому редкое наложение двух одновременных операций на одном балансе больше не теряет деньги и не отдаёт отказ вызывающей стороне.

Влияние на интеграторов

Ничего менять не нужно. Вебхуки платёжного провайдера и события Битрикс24 обрабатываются штатно даже при наложении с другой операцией на тот же баланс — дополнительных ручных ретраев со стороны интегратора это изменение не требует.

BC-0819-5: поле freshnessWindowMinutes больше не приходит в ответах

Поддержка старого формата до: не предусмотрена

Было

GET /v1/me отдавал в блоке capabilities.apps.sourceStorage поле freshnessWindowMinutes со значением 10, а отказ 409 SNAPSHOT_REQUIRED у POST /v1/apps/:id/publish нёс то же поле внутри hint. Поле называло окно в минутах, в пределах которого сохранённый снапшот исходников считался пригодным для публикации.

Стало

Поля нет ни в описании возможностей, ни в подсказке отказа. Оно приходит, только когда публикация проверяет возраст снапшота, а сейчас она его не проверяет — публиковать можно снапшот любой давности. Отдельного null вместо поля не появляется: поле просто отсутствует. Остальной состав блока capabilities.apps.sourceStorage и остальные поля hint не менялись.

Что делать интеграторам

Читайте freshnessWindowMinutes как необязательное поле: если ваш код требует его наличия, приводит к числу без проверки или строит на нём таймер повторного сохранения — уберите эту зависимость. Отсутствие поля трактуйте как «возраст снапшота не проверяется». Поле вернётся только в том случае, если публикация снова начнёт проверять возраст, и тогда его значение снова будет осмысленным. Полная схема подсказки — Хранилище исходников.

BC-0819-6: публикация отказывает, если файлы сохранённой версии исходников удалены

Поддержка старого формата до: не предусмотрена

Было

POST /v1/apps/:id/publish выбирал сохранённую версию исходников по записи о ней, не проверяя, живы ли её файлы. Поэтому версия, у которой файлы уже вычищены из хранилища или помечены на удаление, годилась для публикации: запрос отвечал 200, приложение получало статус PUBLISHED, а скачивание опубликованной версии затем отвечало 410 SOURCE_VERSION_BYTES_PURGED. Опубликованными оказывались исходники, которых нет.

Стало

Версии без файлов публикация не выбирает вовсе. Если других сохранённых версий у приложения или у сервера нет, ответ — 409 SNAPSHOT_REQUIRED с hint.reason = app_snapshot_missing или server_snapshot_missing: файлов нет, значит и снапшота нет. Если такая версия передана явным sourceVersionId, ответ тот же 409, а hint.lastSnapshot приходит как null. Это изменение не связано с проверкой возраста снапшота и действует всегда.

Что делать интеграторам

Проверяйте ответ публикации на 409 SNAPSHOT_REQUIRED и в том сценарии, где раньше он приходить не мог, — у приложения, чьи файлы исходников удалены из хранилища или помечены на удаление. Восстановление одно: сохраните архив заново через POST /v1/apps/:id/sources или POST /v1/infra/servers/:id/sources и повторите публикацию. Срок хранения и удаление файлов описаны в разделе Хранилище исходников.

FIX-0819-7: публикация приложения больше не отказывает из-за возраста сохранённых исходников

Было

POST /v1/apps/:id/publish отвечал 409 SNAPSHOT_REQUIRED, если сохранённый снапшот исходников был старше десяти минут, даже когда сам код не менялся. Проверка измеряла не соответствие исходников, а время с последнего сохранения, поэтому обычный порядок «деплой, затем авторизация приложения на портале, затем публикация» упирался в отказ: шаг авторизации выполняет человек, и он легко занимает больше окна. Чтобы пройти дальше, приходилось повторно сохранять тот же самый архив через POST /v1/apps/:id/sources или POST /v1/infra/servers/:id/sources. В подсказке отказа приходило hint.reason со значением app_snapshot_stale или server_snapshot_stale.

Стало

Возраст сохранённых исходников публикацию не ограничивает: подойдёт снапшот любой давности. Отказ 409 SNAPSHOT_REQUIRED остаётся только тогда, когда пригодного снапшота нет вовсе, и в этом случае hint.reason содержит app_snapshot_missing или server_snapshot_missing. Значения app_snapshot_stale и server_snapshot_stale в ответе больше не встречаются — они возвращаются, только если публикация снова начнёт проверять возраст снапшота.

Влияние на интеграторов

Менять ничего не нужно: у публикации просто исчез один из поводов для отказа. Повторное сохранение неизменившегося архива перед публикацией теперь лишнее — его можно убрать из сценария. Если ваш код разбирает hint.reason, ветки под значения app_snapshot_stale и server_snapshot_stale перестанут срабатывать, но остаются валидными: набор значений поля не менялся.

FIX-0819-8: Публикация приложения больше не помечает версию исходников как успешно задеплоенную

Было

После успешной публикации платформа записывала на выбранную версию исходников статус деплоя success — независимо от того, чем деплой этой версии закончился и был ли он вообще. В списке версий (GET /v1/infra/servers/:id/sources), в файле описи и в реестре источников версия неудачного деплоя после публикации выглядела успешной, а версия, сохранённая вручную, получала статус деплоя, которого не было.

Стало

Публикация записывает только отметку о себе — linkedDeployId вида publish:<время>. Поле deployStatus остаётся тем, чем его сделал деплой: success, failed либо пусто у версий, сохранённых вручную.

Влияние на интеграторов

Менять ничего не нужно. Если вы читали deployStatus как признак «эта версия опубликована» — это никогда не было его смыслом; признак публикации несут пометка published в tags и префикс publish: в linkedDeployId.

BC-0819-9: промокод на закрытом рабочем месте: код отказа `COUPON_SEAT_CANCELLED` больше не возвращается

Поддержка старого формата до: не предусмотрена

Промокод теперь применяется и на закрытом рабочем месте — это способ вернуть ушедшего клиента. Погашение включает подаренный тариф с момента применения, место снова становится активным, и отметка закрытия с него снимается.

Из-за этого пропал код отказа COUPON_SEAT_CANCELLED: возвращать его больше нечему. Если ваш код разбирает причины отказа списком, уберите его — ветка стала недостижимой.

Один отказ на закрытом месте остался, но с другим кодом. Место, закрытое досрочно при неистёкшем оплаченном сроке, отвечает COUPON_SEAT_ALREADY_PAID_LONGER: погашение перезаписало бы оплаченный срок подарочным, и купленные месяцы исчезли бы со строки, по которой считается возврат.

Затронутые эндпоинты: POST /v1/cowork/coupon/preview, POST /v1/cowork/coupon/redeem

FIX-0819-10: промокод больше не съедает оплаченный срок на понижённом месте

Было

Если платформенный админ понижал место с предоплаченного тарифа, на строке оставался оплаченный срок (дата окончания в будущем и цена купленного месяца), но списание уже не планировалось. Погашение промокода на таком месте проходило: POST /v1/cowork/coupon/redeem отвечал успехом, а выданный подарок переписывал дату окончания оплаченного срока на срок подарка. Купленные месяцы исчезали со строки, по которой считается возврат.

Стало

Такое погашение отклоняется кодом COUPON_SEAT_PAID_TERM_ACTIVE — новым в наборе отказов POST /v1/cowork/coupon/redeem. Оплаченный срок на месте не меняется, промокод остаётся у человека и гасится после окончания срока. Прочие отказы набора и их условия прежние.

BC-0819-11: отвязка мест встраивания, которую не удалось подтвердить, больше не выглядит успехом

Поддержка старого формата до: не предусмотрена

Было

POST /v1/apps/{id}/unpublish отвечал 200 и очищал placements независимо от того, сняла ли платформа привязки на Битрикс24. POST /v1/apps/{id}/publish и PATCH /v1/apps/{id} с изменённым набором мест встраивания отвечали 200, даже если снять лишние места не удалось. POST /v1/placements/unbind удалял место встраивания из списка приложения, не дождавшись подтверждения от аккаунта.

Стало

Снятие с публикации по-прежнему отвечает 200 и переводит приложение в UNPUBLISHED, но placements теперь содержит коды, снять которые не удалось, а warnings — причину по каждому. Публикация и PATCH при неподтверждённом снятии отвечают 502 с кодом PLACEMENT_UNBIND_FAILED и списком кодов в error.placements; приложение при этом не публикуется и метаданные каталога не сохраняются. Отвязка одного места встраивания при неподтверждённом ответе аккаунта отвечает 502 с кодом BITRIX_UNAVAILABLE и оставляет место в списке приложения.

Отдельно: POST /v1/apps/{id}/publish и PATCH /v1/apps/{id} начали отвечать 503 с кодом NETWORK_DEVKEY_REQUIRED, если аккаунт переведён на транспорт ключа разработчика, а у автора приложения такого ключа нет. Раньше этот код у обеих ручек не встречался.

Что делать интеграторам

Возможность раскатывается постепенно и включается по аккаунтам: до включения на вашем аккаунте все четыре эндпоинта ведут себя как прежде, неподтверждённое снятие по-прежнему выглядит успехом, а коды PLACEMENT_UNBIND_FAILED и NETWORK_DEVKEY_REQUIRED не встречаются вовсе. Код BITRIX_UNAVAILABLE на отвязке одного места был и до включения — меняется только его условие: после включения им же отвечает неподтверждённое снятие. После включения проверяйте placements в ответе снятия с публикации: пустой список означает, что снято всё. На 502 с кодом PLACEMENT_UNBIND_FAILED повторите запрос — перечисленные места встраивания на аккаунте остались. Если сценарий полагался на 200 как на признак снятия, замените проверку на пустоту placements. На 503 с кодом NETWORK_DEVKEY_REQUIRED повтор не поможет: попросите автора приложения переподключить аккаунт.

Затронутые эндпоинты: POST /v1/apps/{id}/publish, POST /v1/apps/{id}/unpublish, PATCH /v1/apps/{id}, POST /v1/placements/unbind

NEW-0819-12: коробочный портал может подключить демо Маркетплейса

Было

POST /v1/portals/{id}/activate-market-trial и POST /v1/cowork/activate-market-trial отказывали коробочному порталу всегда: 409 TRIAL_ACTIVATION_UNAVAILABLE, причина недоступности в /v1/cowork/state — not_cloud. Демо Маркетплейса было доступно только облачным порталам.

Стало

Коробочный портал активирует демо через те же ручки. Доступность решает Битрикс24 по лицензии коробки, поэтому причина отказа стала точнее: demo_used, когда демо выдать нельзя, и not_supported, пока доступность ещё не прочитана. Ответы и коды ошибок не менялись.

Возможность включается постепенно и по умолчанию выключена.

BC-0819-13: разбор тела запроса у POST /v1/cowork/deploy-key

Поддержка старого формата до: не предусмотрена

Было

Запрос с заголовком Content-Type: application/json и пустым телом отвечал 400. Запрос с заголовком Content-Type: text/plain и непустым телом принимался.

Стало

Пустое тело принимается с любым заголовком: ручка тело не читает. Непустое тело неизвестного типа отвечает 415.

Что делать

Убрать Content-Type: text/plain из вызова либо не передавать тело. Прежнее поведение снимается в момент выката, окна поддержки нет.

NEW-0819-14: отзыв ключа устройства Cowork/Code самим ключом

Было

Ключ устройства отзывался только из кабинета: обе ручки выхода принимают сессионную авторизацию, а приложению доступен лишь ключ. Позвать из приложения было нечего.

Стало

Появился DELETE /v1/cowork/key. Ручка гасит предъявленный ключ — идентификатор не передаётся, поэтому отозвать чужой ключ нельзя. Нужны скоуп vibe:cowork и ключ класса десктопа Cowork/Code, иначе 403 COWORK_DESKTOP_KEY_REQUIRED; без скоупа — 403 INSUFFICIENT_SCOPE. Повторный вызов тем же секретом отвечает 401 KEY_INACTIVE. Темп — 5 запросов в минуту на ключ, сверх лимита 429 RATE_LIMITED.

Ручка работает и при нулевом балансе, и при исчерпанной суточной квоте: аварийный выход не запирается.

2026-08-18

BC-0818-1: контракт управления приложениями и исходниками уточнён

Поддержка старого формата до: не предусмотрена

Было

OpenAPI не описывал точные успешные ответы, лимиты метаданных и часть кодов ошибок для операций хранилища исходников приложений. OAuth-ключ одного приложения мог изменить другое приложение, а также читать и изменять исходники сервера того же автора, принадлежавшего другому ключу. Лимит примечания считал UTF-16 code units, поэтому часть допустимых строк с Unicode отклонялась. Временная ссылка на скачивание могла сохраняться промежуточным кешем.

Стало

Контракт восьми операций описывает фактические схемы ответов, лимиты, коды ошибок и матрицу доступа. OAuth-ключ приложения теперь может напрямую управлять только своим приложением и обращаться к исходникам сервера, принадлежащего этому же ключу. При публикации собственного приложения явный sourceServerId может выбрать сервер того же автора под личным ключом, но не сервер другого OAuth-приложения. Личные ключи автора и ключи администратора сохраняют доступ. Аудит V1-операций с приложениями сохраняет Vibe UUID владельца ключа, а не числовой ID пользователя Bitrix24. Лимит примечания считается по Unicode code points. Ответы с временными ссылками приложения и сервера помечены Cache-Control: private, no-store, а ссылка и содержащийся в ней путь хранилища явно обозначены как краткоживущий bearer credential.

Переходное окно не предоставляется: прежний доступ OAuth-ключа приложения к ресурсам другого ключа был дефектом разграничения доступа.

FIX-0818-2: `?refresh=tariff` у коробочного портала освежает и состояние подписки Маркетплейса

Было

GET /v1/me?refresh=tariff у коробочного портала перепроверял тариф, но состояние подписки Маркетплейса отдавал из прошлого снимка. Клиент, оплативший подписку прямо перед вызовом, продолжал получать прежний ответ, пока состояние не обновит фоновая проверка.

Стало

Запрос перепроверяет и это состояние, а ответ собирается уже по свежим данным. Оплата, прошедшая перед вызовом, видна сразу.

NEW-0818-3: состав рабочей группы с ролями участников

Появился эндпоинт GET /v1/workgroups/:groupId/users — он возвращает участников рабочей группы вместе с их ролью в ней. Раньше фасад /v1/workgroups отдавал только владельца и число участников, поэтому приложение, которому нужно различать руководителя, модератора и рядового участника, сделать это не могло.

Каждая строка ответа несёт userId и role. Поле role содержит букву Битрикс24 как есть: A — владелец, E — модератор, K — участник. Незнакомая буква проходит насквозь, а не приводится к известной и не отбрасывается, поэтому проверяйте те значения, которые знаете, а всё остальное трактуйте как отсутствие прав.

Параметров постраничной выдачи у операции нет, состав возвращается целиком, и meta.total всегда равен числу строк в data. Пустой состав — обычный ответ, а владелец группы не обязательно присутствует среди строк: его идентификатор берите из GET /v1/workgroups/:id. Ответ 404 означает, что группы нет либо она не видна той учётной записи Битрикс24, от имени которой действует ключ, — Битрикс24 не различает эти случаи.

Эндпоинт отдаёт данные о составе, а не решение о доступе: что именно он покажет, зависит от личности за ключом, а признак администратора портала — отдельный факт, его отдаёт GET /v1/users/me. Вложенные ресурсы недоступны через POST /v1/batch, поэтому операция вызывается только по своему пути. Нужен скоуп sonet_group.

FIX-0818-4: сделки: семь полей из GET /v1/deals/fields теперь принимаются в фильтре

Было

GET /v1/deals/fields перечислял поля, которые фильтр не принимал. Запрос POST /v1/deals/search с filter[leadId], filter[quoteId], filter[taxValue], filter[originId], filter[originatorId], filter[additionalInfo] или filter[lastActivityBy] отклонялся с 400 UNKNOWN_FILTER_FIELD ещё до обращения к Битрикс24, хотя Битрикс24 эти поля в фильтре принимает. Отчёт, отбирающий сделки по лиду, построить было нельзя.

Стало

Все семь полей объявлены в схеме сделок и принимаются в filter — и в GET /v1/deals, и в POST /v1/deals/search, и в POST /v1/deals/aggregate. В select они принимались и раньше, но с предупреждением UNKNOWN_SELECT_FIELD — теперь предупреждения нет. Сортировка по ним работала и до этого изменения и не менялась. Имена полей в ответах прежние. Набор осей группировки groupBy не менялся.

Два уточнения по значениям. У leadId, quoteId, originId, originatorId и additionalInfo в GET /v1/deals/fields и в OpenAPI теперь стоит nullable — эти поля возвращают null у сделки, которая не создавалась из лида, из предложения или импортом. Раньше их в спецификации не было вовсе, так что клиент, сгенерированный по ней, обязан быть готов к null. И у трёх строковых полей (originId, originatorId, additionalInfo) пустая строка от Битрикс24 приводится к null — так же, как у всех остальных строковых полей API. На замере 300 сделок пустая строка не встретилась ни разу, Битрикс24 отдаёт по этим полям null, поэтому смены поведения на реальных данных мы не ожидаем; но если ваш код сравнивает такое поле с "", сравнивайте с пустым значением.

Шесть полей — utmSource, utmMedium, utmCampaign, utmContent, utmTerm и contacts — по-прежнему отклоняются в фильтре и сортировке: Битрикс24 сам их там не поддерживает. Отказ оставлен намеренно, иначе выборка была бы молча неверной. Теперь причина видна заранее — GET /v1/deals/fields описывает её в поле description. Для контактов фильтруйте по contactId (основной контакт) или по contactIds (любой из привязанных).

У лидов, предложений и элементов смарт-процессов пять UTM-полей остаются доступны в ответах, но больше не проходят локальную проверку фильтра и сортировки: Битрикс24 не принимает их в crm.item.list. Вместо ошибки Битрикс24 422 запрос теперь заранее получает 400 UNKNOWN_FILTER_FIELD или 400 UNKNOWN_SORT_FIELD, не обращаясь к Битрикс24. Остальные операции с этими полями не изменились.

FIX-0818-5: пустой результат методов рабочего дня возвращается как null

Было

При пустом успешном результате Битрикс24 поле data могло содержать служебную оболочку с полями result, total и next вместо значения результата.

Стало

POST /v1/workday/open, POST /v1/workday/close, POST /v1/workday/pause, GET /v1/workday/status, GET /v1/workday/settings и GET /v1/workday/schedule возвращают data: null, если успешный результат пуст.

Влияние на интеграторов

Клиентам не требуется извлекать пустое значение из служебной оболочки ответа Битрикс24.

NEW-0818-6: справочник полей заказов и статусов заказа отдаёт названия и пояснения

Было

GET /v1/orders/fields и GET /v1/order-statuses/fields отвечали на каждое поле только типом и флагом только-для-чтения. Ни человекочитаемого названия, ни пояснения не было — всего 54 поля, по которым клиент мог опираться только на имя поля. Сгенерировать по такой схеме форму или типизированную модель было нечем: имя вроде recountFlag или empStatusId само по себе ничего не объясняет.

Стало

Каждое поле обеих сущностей несёт label (короткое название) и description (пояснение) на языке сегмента. Пояснение говорит то, чего не передаёт тип: почему price принимается только при создании (Битрикс24 пересчитывает сумму из позиций корзины), что requisiteLink приходит пустым массивом, когда связь не задана, что clients, payments, basketItems и propertyValues возвращаются только в карточке заказа, и что Битрикс24 требует поле type при каждом обновлении статуса. Ответ вырос, набор полей и их типы не изменились — старые запросы работают как прежде.

NEW-0818-7: машинный выпуск промокодов: три метода V1 и два новых доступа интеграционного ключа

На платформе Вайбкод появились три метода V1 для интеграции, которая раздаёт промокоды сама: POST /v1/platform/coupons/issue выпускает партию кодов внутри уже заведённой кампании, GET /v1/platform/coupons/campaigns отдаёт список кампаний, доступных для выпуска, а GET /v1/platform/coupons/campaigns/{slug} — одну кампанию по её коду. Авторизация — платформенный интеграционный ключ в заголовке Authorization: Bearer, доступы coupons:issue и coupons:read соответственно; их выдаёт администратор платформы при выписке ключа.

Ключ выпускает коды, но не заводит кампании и не двигает их потолок: и то и другое остаётся за администратором платформы, иначе потолок кампании перестал бы быть потолком. Кампания адресуется тем же кодом (slug), который стоит в начале каждого выпущенного ею промокода, — у интегратора и у поддержки один идентификатор на всё. Поле remainingToIssue отвечает, сколько кодов ещё разрешено выпустить; null означает, что потолка у кампании нет.

Заголовок Idempotency-Key обязателен. Коды возвращаются один раз и на стороне платформы не хранятся — лежат только их хеши, — поэтому повторить ответ нечем: запрос с уже использованным ключом получает 409 IDEMPOTENCY_KEY_ALREADY_USED со ссылкой на выпущенную партию, а не второй набор кодов. Выпуск в кампанию-черновик отклоняется 409 COUPON_CAMPAIGN_IN_DRAFT: коды, розданные до активации кампании, при погашении отказали бы, а перевыпустить их нечем. Остальные отказы: 409 COUPON_CAMPAIGN_NOT_ISSUABLE — кампания завершена или в архиве, 409 COUPON_CAMPAIGN_CAP_REACHED — партия не влезает в потолок, 403 INSUFFICIENT_SCOPE — у ключа нет нужного доступа, 404 CAMPAIGN_NOT_FOUND — кампании с таким кодом нет.

Погашение промокода машинным методом не выполняется и в этот выпуск не входит: код активирует человек в кабинете, под своей сессией и на своём портале.

NEW-0818-8: промокоды на тарифы Cowork/Код и коды отказа при активации

На платформе Вайбкод появились промокоды. Партнёр получает код от организатора и активирует его в кабинете Cowork/Код: на его месте включается подаренный тариф на срок, оплаченный платформой, без списания с баланса портала. Кампании, выпуск партии кодов и отзыв невыданных кодов ведёт администратор платформы; активация — действие пользователя в кабинете, публичного метода API у неё нет.

Отказ при активации приходит кодом в поле code, и большая часть причин сведена к одному коду COUPON_INVALID намеренно: «не найден», «отозван», «уже активирован», «истёк», «кампания закончилась» и лимиты кампании неразличимы между собой, чтобы отказ не подтверждал существование действующего кода. Различимы только причины, с которыми человек может что-то сделать: COUPON_TOO_MANY_ATTEMPTS — слишком много попыток подряд; COUPON_PORTAL_ACCESS_GATED — порталу ещё не открыт доступ в Cowork/Код; COUPON_SEAT_PAUSED, COUPON_SEAT_CANCELLED и COUPON_SEAT_CANCELLATION_SCHEDULED — состояние места мешает применить подарок; COUPON_TIER_DOWNGRADE_BLOCKED, COUPON_SEAT_ALREADY_ON_TIER, COUPON_SEAT_ALREADY_PAID_LONGER и COUPON_SEAT_IS_PAID — действующий тариф уже не ниже подаренного или оплачен дальше. Отдельно стоит CONCURRENT_REDEMPTION: несколько кодов одной кампании активировали одновременно, попытку достаточно повторить.

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

NEW-0818-9: промокод в приложении Cowork/Код: проверка кода и погашение по ключу десктопа

Приложение Cowork/Код теперь принимает промокод само, не отправляя человека в кабинет. Появились два метода: POST /v1/cowork/coupon/preview показывает, что даёт код (тариф, срок, название кампании), ничего не меняя, а POST /v1/cowork/coupon/redeem его гасит и включает подаренный тариф на срок, оплаченный платформой Вайбкод.

Оба метода требуют ключ десктопа Cowork/Код с доступом vibe:cowork. Агентский ключ с тем же доступом получит 403 COWORK_DESKTOP_KEY_REQUIRED: погашение необратимо и одноразово, а за таким ключом нет человека, который принимает решение.

Проверка отвечает 200 и в теле сообщает valid: false с причиной COUPON_INVALID, если код не годится. Причина одна на все случаи «код не подходит» — так метод не подсказывает подбирающему, чем именно отличается несуществующий код от отозванного. Годность здесь означает «код живой и кампания открыта»: условия самого рабочего места (уже оплачено, уже этот тариф) проверяются при погашении и могут дать отказ после успешной проверки.

Погашение возвращает выданный тариф, срок и поле accessGranted. false означает, что тариф выдан, но администратор портала ещё не открыл доступ к Cowork/Код — покажите это отдельно, иначе человек увидит успех и упрётся в закрытую дверь.

Затронутые эндпоинты: POST /v1/cowork/coupon/preview, POST /v1/cowork/coupon/redeem

NEW-0818-10: витрина приложений в API: список и карточка

В API Вайбкод появилось семейство приложений: GET /v1/applications отдаёт список, GET /v1/applications/:id — одну карточку. Выборка идёт по владельцу ключа, а не по самому ключу, поэтому в список попадают и приложения, созданные в кабинете, — их серверы привязаны к другим ключам того же человека и в GET /v1/infra/servers не видны.

Параметр scope принимает mine (свои), shared (доступные вам чужие — как открытые лично вам, так и открытые всему порталу) и feed (общий список, значение по умолчанию), плюс постраничные page и limit. Карточка доступна и получателю доступа, а не только владельцу; отношение зрителя к приложению приходит в поле viewerState.

Рядом с total в ответе списка приходит truncated. У скоупа feed порядок приложений считает платформа, поэтому выборка ограничена сверху: при truncated: true значение total — это тот предел, а не полное число приложений, и страниц за ним нет. Считать число страниц как total / limit можно, только когда truncated равно false; при true за остатком следует идти в скоупы mine и shared, у которых предела нет.

Каждая карточка несёт два дополнительных блока. sources сообщает, есть ли сохранённые версии исходников, и отдаёт идентификатор последней в том же виде, который принимает скачивание версии, — обращаться за списком версий заранее не нужно. activeOperation показывает идущую операцию: вид, шаг и время старта; значение unknown означает, что операция начиналась, но её исход неизвестен. Оба блока наполняются только для того, кто приложением управляет: у зрителя, которому приложение просто открыли, sources приходит пустым (hasVersions: false, оба поля null), а activeOperation — null. Форма ответа при этом не меняется, поэтому важно не сделать неверный вывод: пустой sources у чужого приложения означает «эти данные не отдаются», а не «версий нет».

Важная оговорка про activeOperation: null означает «операции с сохранённой записью нет», а не «с приложением ничего не происходит». Признак покрывает выкладку, починку, изменение тарифа сервера и перенос контейнера между галактиками — остальные действия платформа не журналирует и в этом поле не показывает.

Открывать приложение не нужно вычислять самому: карточка отдаёт готовую пару openUrl и openTarget. Значение openTarget сейчас одно — app, то есть открывается собственный адрес приложения; оба поля null, когда открывать нечего. Список значений закрытый и может пополниться, поэтому неизвестное значение стоит трактовать как «открывать нечем», а не как ошибку.

⚠️ У приложения, встроенного в Битрикс24, оба поля приходят null — платформа пока не знает адреса, по которому его открывают внутри портала. Собственный адрес такого приложения в openUrl намеренно НЕ подставляется: он ведёт на страницу входа шлюза, а не в приложение, и переход по нему выглядел бы удачным, не будучи им.

Различить эти два состояния — «встроено, открывается внутри Битрикс24» и «ещё не опубликовано» — позволяет поле isEmbedded: у обоих пара открытия пуста, а тексты для пользователя разные. Выводить встройку из наличия сервера НЕЛЬЗЯ: встроенное приложение без собственного сервера — штатное состояние (встройку сделали, код ещё не выкладывали), и признак от сервера не зависит вовсе. Поле приходит и владельцу, и тому, кому приложение открыли.

У сервера появился признак reachable — true, когда сервер и работает, и отвечает по сети. Отдельно от status, потому что это разные факты: сервер бывает поднят, а сетевой туннель до него не поднят, и по одному status такое приложение выглядит рабочим. Проверить это со стороны клиента нельзя, поэтому признак считает платформа.

Признак уже включает состояние RUNNING: истинным при другом состоянии сервера он не приходит, поэтому конъюнктить его со status не нужно. ⚠️ У приложения в галактике (server.kind: "GALAXY_APP") вторая половина признака берётся с ХОСТА галактики, а не с самого контейнера — связность держит хост, у контейнера своего туннеля нет по устройству. Отсюда следствие, о котором лучше знать заранее: только что созданный контейнер, ещё не дошедший до RUNNING (а доходит он лишь после первой загрузки исходников), приедет с reachable: false даже при полностью живом хосте. Это означает «контейнер пока не поднялся», а не «хост недоступен» — различить два случая по одному признаку нельзя, для этого есть status.

У сервера появилось поле lastDeployedAt — момент последней успешной выкладки. Это единственный в разделе признак того, что приложением живут: updatedAt двигает только правка карточки, а sources.latestSavedAt означает «код сохранён» и приходит лишь тому, кто приложением управляет. Отметку ставит сама выкладка в момент фиксации успеха, поэтому провалившаяся выкладка её не двигает. ⚠️ Истории у поля нет: у серверов, созданных до появления поля, оно приходит null до первой следующей выкладки — восстанавливать историю мы не стали, потому что доступный источник покрывает только один из трёх путей выкладки, и дата появилась бы у одних видов серверов и отсутствовала у других.

Ещё две вещи, о которых лучше знать заранее. id приложения из этого раздела и id из GET /v1/apps — разные значения разных сущностей: здесь карточка приложения, там регистрация приложения на портале Битрикс24. И одно и то же по смыслу название приходит в них под разными именами: name здесь и title там. Переносить одно в другое нельзя, поля не синхронизируются.

Порядок в списке назван прямо, чтобы постраничный обход был воспроизводимым. У скоупа feed: закреплённые вами → свои → чужие, внутри по updatedAt от свежих к старым, при равных метках — по id. У mine и shared: по createdAt от свежих к старым, при равных метках — по id. Вторичный ключ здесь не формальность: у приложений, созданных пакетно, метки времени совпадают, и без него две страницы одного обхода могли пересечься или пропустить приложение. ⚠️ updatedAt двигается только правкой карточки — выкладка её не трогает, поэтому «свежесть» в ленте означает «когда карточку меняли», а не «когда приложение выкладывали».

2026-08-17

FIX-0817-1: повторный деплой PostgreSQL-рантайма не падает на существующей базе

Было

POST /v1/infra/servers/:id/deploy с одним из PostgreSQL-рантаймов при повторном запуске пытался снова создать базу app, и шаг установки рантайма мог завершиться ошибкой.

Стало

Рантаймы node20-pg, node20-pg-redis, node20-rag, python311-pg и python311-rag создают базу app только при её отсутствии. Настоящая ошибка создания по-прежнему останавливает деплой.

Влияние на интеграторов

Повторный деплой с тем же PostgreSQL-рантаймом не требует ручного удаления базы или смены рантайма.

FIX-0817-2: справочник статистики звонков показывает рабочие фильтры и сортировку

Было

Машинная подсказка для GET /v1/calls/statistics предлагала несуществующие поля диапазона дат и не объясняла, что поле сортировки и направление передаются отдельно.

Стало

Подсказка и OpenAPI показывают сравнения filter[>CALL_START_DATE] / filter[<CALL_START_DATE], пару sort + order и фактическую одностраничную пагинацию до 50 записей. Соседние операции управления звонками теперь также используют реальные пути с :callId и фактические тела запросов для регистрации, завершения, расшифровки, автообзвона и обратного звонка.

Влияние на интеграторов

Используйте канонические операторы Bitrix24 в ключах фильтра и передавайте направление сортировки отдельным параметром order.

FIX-0817-3: выдача ключа деплоя Cowork/Code возвращает флот, потерянный прежними выдачами

Было

POST /v1/cowork/deploy-key переносил связи на свежий ключ только с того ключа, который активен на момент выдачи. Всё, что осталось на ключах, отозванных раньше, под перенос не попадало: серверы не возвращались в GET /v1/infra/servers ни при одной последующей выдаче, отвечали 404 на запрос по адресу и 403 WRONG_KEY на публикацию. Если действующего ключа деплоя у владельца не было вовсе, выдача не переносила ничего — то есть повторный запрос ключа такую потерю тоже не лечил. Оставался только ручной путь: привязка сервера к действующему ключу в кабинете.

Стало

Выдача забирает связи со всех ранее отозванных проектных ключей того же владельца и портала, а не только с текущего действующего — до пяти ключей за вызов, начиная с самого свежего. Владение серверами, карточка приложения, живые токены доступа и область видимости операций деплоя переезжают на свежий ключ в той же транзакции, что и сама выдача, поэтому список серверов и публикация работают сразу после ответа. Случай «действующего ключа нет» больше не особый: связи переносятся и на первую выдачу.

Влияние на интеграторов

Менять ничего не нужно. Оговорка прошлой правки о том, что потерянное возвращается только вручную через кабинет, снята — достаточно запросить ключ ещё раз. Если связей накопилось больше, чем на пяти ключах, остаток заберут следующие выдачи; ключ живёт семь дней, поэтому отдельного действия это не требует.

NEW-0817-4: компенсации квоты видны в ответах подписки Cowork/Code

GET /v1/cowork/me и GET /v1/cowork/state отдают блок relief — отметки о том, когда поддержка платформы Вайбкод сбрасывала счётчики расхода и когда выдавала временное увеличение лимитов. Обе отметки приходят с точностью до часа и видны 7 суток, дальше поле пустое. Вместе с блоком сводка по подписке начала отдавать boostPct и boostExpiresAt — размер и срок действующего увеличения, которые до этого были только в полном состоянии.

Пустое relief.boostGrantedAt не означает, что увеличения нет: отметка нужна для разового уведомления и живёт 7 суток, а само увеличение выдают на срок до 30 суток. Действует увеличение или нет, показывает только boostPct.

Возможность включается по аккаунтам. Пока она не включена, всех перечисленных ключей нет в теле ответа вовсе, поэтому проверяйте наличие ключа, а не его значение. Прежние вызовы работают без изменений.

NEW-0817-5: словарь типов уведомлений

Появился GET /v1/notifications/schema — справочник модулей портала и типов уведомлений, которые они отправляют. Параметров у операции нет.

Ответ несёт массив modules, у каждого модуля идентификатор, название и список типов. Идентификатор модуля совпадает с полем notifyModule уведомления в ленте, идентификатор типа — с notifyEvent, поэтому справочник закрывает задачу «показать человеку понятное название вместо технического кода» и годится для экранов настройки уведомлений.

Битрикс24 отдаёт справочник картой, ключ которой повторяет идентификатор модуля внутри записи, — API Вайбкод разворачивает её в массив. Порядок элементов приходит от портала и контрактом сортировки не является. Состав справочника зависит от портала и меняется с обновлениями Битрикс24, поэтому ответ стоит кэшировать, но перечитывать при встрече незнакомого модуля или типа.

NEW-0817-6: настройки календаря портала доступны через API

Добавлен эндпоинт GET /v1/calendar/settings — он возвращает настройки календаря Битрикс24: начало и конец рабочего дня, выходные дни недели, праздники, рабочие субботы и первый день недели. Это те же значения, по которым интерфейс Битрикс24 размечает нерабочие дни. Параметров у запроса нет, нужен ключ со скоупом calendar.

Фиксированные поля ответа приходят в camelCase — workTimeStart, weekHolidays, yearHolidays. Адреса разделов для нестандартных типов календаря приходят по одному полю на тип, с именем в том виде, в каком его отдаёт портал, поэтому набор полей ответа не фиксирован.

Владельцу ключа, который не является сотрудником портала, Битрикс24 отдаёт сокращённый набор из двух полей со значениями по умолчанию вместо настроек портала. По самим значениям такой ответ от настроенного портала не отличается, поэтому в него добавляется meta.warnings с кодом calendar_settings_partial.

У эндпоинта есть собственный лимит — 120 запросов в минуту на портал, помимо общего лимита обращений к Битрикс24. Настройки меняются редко, поэтому ответ рассчитан на кэширование на стороне клиента, а не на частый опрос.

События и разделы календаря по-прежнему доступны как сущности — /v1/calendar-events и /v1/calendar-sections.

2026-08-16

NEW-0816-1: иконку приложения можно загружать растром, а портал Битрикс24 присылает её сам

Загрузка иконки POST /v1/infra/servers/:id/icon принимает не только SVG, но и растровые форматы — PNG, JPG, GIF, WEBP, до 5 МБ и до 4 мегапикселей (например, 2000×2000). Формат определяется по содержимому файла, а не по имени и не по заголовку Content-Type. Что бы вы ни загрузили, по URL отдачи иконка по-прежнему возвращается как PNG 256×256: платформа масштабирует изображение в квадрат с сохранением пропорций и прозрачными полями. Прежние вызовы с SVG работают без изменений: ограничения SVG — 256 КБ и прежний потолок в 4096×4096 точек — не менялись.

Новые коды отказа: ICON_UNSUPPORTED_FORMAT — формат не распознан или не поддерживается, ICON_TOO_LARGE — файл больше допустимого, ICON_TOO_MANY_PIXELS — в изображении больше точек, чем платформа берётся распаковать, ICON_RASTERIZE_FAILED — файл не удалось прочитать.

В кабинете загрузка иконки, наоборот, сузилась до растра: карточки сервера и приложения принимают PNG, JPG, GIF и WEBP, а SVG теперь отклоняют — этот формат остаётся только в API.

Вторая половина изменения — на стороне портала: владелец приложения может сменить иконку прямо в карточке каталога Битрикс24, и портал присылает её на платформу Вайбкод вместе с названием и описанием. Иконка попадает в ту же карточку каталога, что и загруженная через API.

NEW-0816-2: два поля о временном увеличении лимитов в состоянии Коворк/Код

Ответ GET /v1/cowork/state дополнен парой полей о временном увеличении лимитов: boostPct — размер прибавки в процентах (100 означает удвоение), boostExpiresAt — момент её окончания. Когда увеличения нет, поля равны 0 и null.

Доли использования (pctUsed) во всех трёх окнах уже посчитаны с учётом прибавки, поэтому отдельно её применять не нужно — поля нужны, чтобы показать человеку размер и срок.

NEW-0816-3: новый отказ 402 company_budget_exhausted на вызовах, которые тратят вайбы

Было

Единственной причиной отказа по деньгам на /v1/search/* и на AI-вызовах была нехватка средств на счету портала — INSUFFICIENT_BALANCE. Ограничить расход отдельного сотрудника или всего аккаунта администратор не мог.

Стало

Администратор аккаунта может задать месячный бюджет метрируемого расхода — на весь аккаунт и на отдельного сотрудника. Когда бюджет исчерпан, вызовы веб-поиска и research, а также AI-вызовы — POST /v1/chat/completions, POST /v1/embeddings и POST /v1/audio/transcriptions — которые тратят вайбы, получают 402 с кодом company_budget_exhausted.

Тело отказа несёт два дополнительных поля. scope говорит, чей бюджет исчерпан: USER — личный бюджет вызывающего, PORTAL — бюджет всего аккаунта. canRequest говорит, можно ли из продукта запросить увеличение: для личного бюджета true, для бюджета аккаунта false — его поднимает только администратор.

Отказ приходит только на тех вызовах, которые действительно списывают вайбы: поиск на собственном ключе клиента стоит ноль и продолжает работать при исчерпанном бюджете, и так же продолжают работать AI-вызовы на собственном ключе, вызовы внутри тарифной квоты и вызовы, покрытые подпиской. Уже начатый вызов дорабатывается: ограничение действует со следующего запроса.

Бюджеты включаются отдельно и по умолчанию выключены, поэтому у аккаунтов без них поведение не меняется.

FIX-0816-4: выдача ключа деплоя Коворка больше не теряет серверы

Было

POST /v1/cowork/deploy-key отзывал предыдущий ключ, но оставлял на нём всё, что на него ссылалось: владение серверами, карточку приложения и живые токены доступа. Инфраструктурные чтения версионированного API привязаны к вызывающему ключу, поэтому после каждой повторной выдачи прежние серверы пропадали из GET /v1/infra/servers, отвечали 404 на запрос по адресу и 403 WRONG_KEY на публикацию. Вернуть их можно было только через кабинет.

Стало

Выдача переносит эти связи на свежий ключ — так же, как это делает ротация ключа. Сразу после ответа список серверов и публикация работают под новым ключом, включая приложения-контейнеры в режиме Галактик.

Вместе с ними переезжает и область видимости запущенных операций, поэтому GET /v1/infra/operations/{operationId} продолжает отдавать исход выкладки, начатой под прежним ключом. Прежде клиент, у которого оборвался транспорт и который поэтому запросил новый ключ, получал 404 на собственную операцию — неотличимо от «такой операции не существует».

Влияние на интеграторов

Менять ничего не нужно: начиная с этой правки повторная выдача ключа связи не теряет.

⚠️ Правка работает вперёд и не возвращает то, что уже потеряно. Перенос идёт с ключа, который активен на момент выдачи, а осиротевшие связи лежат на ключах, отозванных раньше, — под этот перенос они не попадают. Если серверы пропали до выката, верните их вручную: в кабинете Вайбкод на карточке сервера выберите привязку к действующему ключу.

Ключ живёт семь дней и по расписанию не перевыпускается. Если к нему не обращались дольше срока, серверы остаются за истёкшим ключом и снова становятся видны после следующей выдачи — она переносит связи уже с него.

2026-08-15

BC-0815-1: комбо-рантаймы с базой данных больше не проезжают в галактику молча

Поддержка старого формата до: не предусмотрена

Было

Деплой приложения в галактику с рантаймом, обещающим базу данных (node20-pg, node20-mysql, node20-redis, node20-mysql-redis, node20-pg-redis, node20-rag, python311-pg, python311-mysql, python311-redis, python311-rag, php83-mysql), завершался успехом. Суффикс базы срезался, приложение получало только языковой образ: ни СУБД, ни переменных подключения (DATABASE_URL, PG*). Ошибка всплывала при первом обращении к базе.

Стало

Такой деплой отклоняется сразу: 400 с кодом GALAXY_RUNTIME_DB_UNSUPPORTED, полем details.suggestedRuntime (языковая замена — например node20 вместо node20-pg) и подсказкой по восстановлению. Каталог GET /v1/infra/runtimes отвечает новым полем supportedPlacements у каждого рантайма: ["standalone"] — только отдельная виртуальная машина, ["standalone","galaxy"] — ещё и галактика. Поля name и packages не изменились: они описывают установку на отдельной машине, где комбо-рантайм действительно ставит базу.

Что делать интеграторам

Перед деплоем в галактику фильтруйте каталог по supportedPlacements со значением galaxy. Приложению в галактике нужна база — возьмите языковой рантайм и передайте строку подключения к внешней базе через env. Нужна база на той же машине — создайте отдельную виртуальную машину (placement: "dedicated", создание без source) и задеплойте на неё исходный рантайм вторым шагом. Уже работающие приложения не затронуты: отказ возникает только на новом деплое.

NEW-0815-2: список привязанных мест сообщает, сверялись ли с аккаунтом

Ответ GET /v1/placements получил три необязательных поля. Поле portalSync говорит, чем закончилась сверка перечня с аккаунтом Битрикс24: ok — аккаунт вернул все привязанные коды, drift — часть кодов аккаунт не вернул, unknown — сверки не было. Причина значения, отличного от ok, приходит в portalSyncReason — no_oauth_session, empty_vibe_list, b24_unreachable, unpublished или missing_on_portal. При расхождении коды, которые числятся привязанными в Вайбкод и не вернулись от аккаунта, перечислены в missingOnPortal, и на них же появляется строка в warnings.

Приложение, снятое с каталога, расхождением не считается: значение unpublished говорит, что места на аккаунте сняла сама платформа, а коды сохранены для повторной публикации. Сверка выполняется, когда вместе с ключом приложения передан токен сессии и у приложения есть привязанные места. Без этих условий поле portalSync приходит со значением unknown — прежнее поведение ответа не менялось, просто теперь оно названо явно.

Раньше исход сверки приходилось выводить из наличия и длины массива handlers, а это не различало «аккаунт ответил и кода в ответе нет» и «ответ аккаунта получить не удалось»: в обоих случаях приходил пустой массив. Теперь во втором случае поле handlers не приходит вовсе, а причина видна в portalSyncReason. Прежние вызовы продолжают работать, новые поля дополняют ответ.

FIX-0815-3: квота ИИ считается по тарифу во всех западных зонах

Было

Тариф аккаунта Битрикс24 записывается как «зона аккаунта плюс редакция» — например jp_pro100. Приставку зоны платформа снимала не у всех: из западных зон разбирались только десять, и аккаунт в зоне cn, id, it, vn, jp, ms, th, hi, co или ae не совпадал ни с одной настройкой тарифа. Такому аккаунту месячная квота ИИ считалась по резервному правилу, а не по его тарифу.

Стало

Приставка снимается во всех двадцати одной западной зоне, и квота считается по настройке того тарифа, который у аккаунта на самом деле. Менять на своей стороне ничего не нужно; у затронутых аккаунтов размер месячной квоты меняется со следующего расчётного периода — вверх или вниз, в зависимости от того, как настроен их тариф.

NEW-0815-4: приложение можно опубликовать в каталоге Битрикс24 отдельным вызовом

Появился вызов POST /v1/infra/servers/:id/b24-catalog/publish — он создаёт карточку приложения в каталоге приложений Вайбкод на портале Битрикс24. Раньше карточка появлялась только после успешного развёртывания, поэтому приложение, поднятое иначе, в каталоге отсутствовало, и выдача доступа сотрудникам его не показывала.

Вызовы GET /v1/infra/servers и GET /v1/infra/servers/:id теперь отдают блок b24CatalogSync с полями status, itemId, attempts, pendingOp и eligible. Признак «приложение в каталоге» — непустой itemId. Поле eligible отвечает на другой вопрос: может ли карточка существовать в принципе — у рантайма агента, хоста галактики, сервера без субдомена и сервера, на который ещё не выкладывали приложение, её не будет.

Прежние вызовы работают без изменений: правка политики доступа и списка доступа карточку по-прежнему не создаёт, она обновляет уже существующую.

FIX-0815-5: месячный лимит Коворка догоняет тарифную сетку без ожидания продления

Было

/v1/cowork/state и /v1/cowork/me считали месячное окно платного места от объёма, зафиксированного в момент покупки. Когда администратор платформы увеличивал месячный лимит тарифа, новое значение доезжало до места только на следующем продлении: доля month.pctUsed не менялась, а место, упёршееся в exhausted, оставалось заблокированным до конца оплаченного периода, хотя лимит тарифа уже вырос. Недельное и пятичасовое окна при этом обновлялись сразу — три окна одного места отвечали по разным правилам.

Стало

Месячное окно платного места считается от большего из двух: действующего лимита тарифа и объёма, проданного на текущий период. Увеличение лимита применяется сразу — month.pctUsed падает без единого запроса, а month.exhausted сбрасывается в false, если новый лимит выше израсходованного. Уменьшение лимита оплаченный период не затрагивает: до его конца место считается по проданному объёму, новое значение вступает в силу со следующего периода. Бесплатное место, как и раньше, считается строго по действующему лимиту.

Абсолютных чисел в ответе по-прежнему нет — меняются только доли, признак exhausted и момент resetAt. Клиенту, кэширующему month.pctUsed, стоит перечитывать состояние перед решением о блокировке, а не полагаться на то, что доля растёт монотонно.

2026-08-14

NEW-0814-1: ещё тридцать одна рабочая операция появилась в машинной схеме

Тридцать одна рабочая операция, которая уже была доступна через V1 и описывалась в документации, теперь добавлена в GET /v1/openapi.json: поля смарт-процессов, ссылки реквизитов, настройки карточек CRM, настраиваемые дела, почта, учёт времени, управление значком и зависшим сервером, перевод бота и поиск узлов кадровой структуры, а также шаблоны приложений. Методы и их ответы не менялись — добавлено только описание для клиентов и агентов, строящих интеграции по машинной схеме.

BC-0814-2: закрепление порта отказывает вместо ложного подтверждения

Поддержка старого формата до: не предусмотрена

Было

У закреплённого сервера PATCH /v1/infra/servers/:id/port записывал запрошенный порт в настройки агента, не проверяя, слушает ли его на машине хоть один процесс. Публичный адрес после такой смены мог не отвечать вовсе, а вернуть машину в рабочее состояние получалось только неочевидным port: 0. Ответ verified: true означал при этом лишь, что агент вернулся на связь: агент, поднявшийся с автоматическим подбором порта, то есть без закрепления, выдавал ровно то же подтверждение. Два запроса подряд успевали увести агента на порт чужого запроса, и оба отвечали успехом.

Стало

Порт, которого на машине никто не слушает, отбивается кодом 409 PORT_NOT_APPLIED до того, как настройки будут переписаны; сообщение перечисляет порты, которые агент видит слушающими. Запись NEW-0812-3 утверждала, что у закреплённого сервера этот код не приходит никогда, — он приходит, ровно в этом случае и без поля agentError.

Подтверждение стало строже: verified: true теперь означает, что настройки агента несут запрошенный порт, агент действительно перезапустился и поднялся именно с закреплением, а не с автоматическим подбором. Ответ дополнительно несёт data.pinned — осталась ли машина закреплённой после вызова.

Пока смена порта не доведена до конца, второй запрос к тому же серверу получает 409 SERVER_BUSY. Сам эндпоинт получил ограничение частоты — 10 запросов в минуту.

Что делать интеграторам

Поднимайте процесс на целевом порту ДО смены порта: порядок «сначала закрепить порт, потом запустить на нём приложение» теперь отвечает отказом, а не успехом. Не отправляйте смену порта параллельно с выкладкой или с другой сменой порта того же сервера — дождитесь ответа предыдущего вызова. Уложитесь в 10 запросов в минуту.

FIX-0814-3: параметр dimensions для модели bitrix/embeddings

Было

Параметр dimensions был объявлен в схеме POST /v1/embeddings, но любой запрос с ним для модели bitrix/embeddings получал 400 ai_provider_rejected — независимо от значения, включая размерность, которую модель и так возвращает.

Стало

Для bitrix/embeddings параметр работает: допустимо целое от 32 до 4096, ответ содержит вектор указанной размерности, приведённый к единичной длине. Значение вне диапазона отклоняется с 400 invalid_request и полем param. Запросы без параметра не изменились: полная размерность 4096.

Меньшая размерность не уменьшает расход по входным токенам и не ускоряет обработку — экономия возникает на стороне интегратора, на размере индекса и скорости поиска. Векторы разной размерности нельзя смешивать в одном индексе близости. Подробнее — Создать эмбеддинги.

FIX-0814-4: служебные метки в начале ответа модели больше не попадают в content

Было

После вызова инструмента итоговый ответ POST /v1/chat/completions иногда начинался со служебных меток перед текстом. Запрос с response_format, в котором ответ состоял только из таких меток, возвращал 200 и непустой content.

Стало

Служебные метки в начале content снимаются. Если текста не осталось, content равен null — как у ответа без текста. Для запроса с response_format это уже описанный случай пустого content без tool_calls: HTTP 422 и код structured_output_truncated. Если после снятия меток остаётся текст, ответ 200 с очищенным content.

Влияние на интеграторов

В обычном случае менять клиент не нужно: ответ тот же, только без префикса, а собственное вырезание префикса по-прежнему безопасно. Изменение касается вас, если вы полагались на ответ 200 для запроса с response_format, в котором модель не дала текста: теперь такой ответ приходит с кодом 422 и structured_output_truncated, как и описано на странице метода. Метки снимаются и в потоковом режиме. Поле usage не пересчитывается.

FIX-0814-5: блокирующее пробуждение сервера реже отдаёт WAKE_TIMEOUT на холодном старте

Было

Блокирующее пробуждение — POST /v1/infra/servers/:id/wake с ?wait=true и автопробуждение спящего сервера при POST /v1/infra/servers/:id/deploy — отдавало 503 WAKE_TIMEOUT в двух случаях, которые не зависели от длины ожидания. Если команда запуска машины терялась на стороне облака, платформа больше её не повторяла и через отведённое время сообщала, что машина не поднялась. А если агент на машине успевал подключить туннель раньше, чем платформа обновляла статус сервера, готовность всё равно не признавалась — и уже поднятая машина возвращалась в сон.

Стало

Пока платформа ждёт готовности, она переспрашивает облако о состоянии машины и повторяет команду запуска, если та не доехала. Подключённый туннель теперь сам считается доказательством готовности: сервер переводится в running и ответ возвращается, не дожидаясь очередного обновления статуса. Коды ошибок, форма ответа и окно ожидания (~6.5 минуты, затем 503 WAKE_TIMEOUT и возврат сервера в sleeping) не изменились, поэтому клиентам менять ничего не нужно. Машина, которая действительно не поднялась, по-прежнему честно отдаёт WAKE_TIMEOUT.

FIX-0814-6: дробный или мусорный идентификатор в пути больше не возвращает ЧУЖУЮ запись

Было

Запрос вида GET /v1/tasks/1.5 отвечал 200 и возвращал запись с идентификатором 1: нецелое значение уходило в Битрикс24 как есть, тот отбрасывал дробную часть, и клиент получал ДРУГУЮ реальную запись вместо ошибки. То же на изменении и удалении — правка или удаление молча попадали не в ту запись. Затронуты сущности с целочисленным идентификатором; подмену записи удалось наблюдать вживую у двенадцати из них (задачи, рабочие группы, сотрудники, отделы, статусы, хранилища, сайты, страницы, папки, ленты событий, настройки открытых линий, шаблоны реквизитов), у остальных запрос отклонял сам Битрикс24. Часть сущностей (например сделки) уже отвечала 400, поэтому поведение внутри одного API различалось.

Стало

Нецелый идентификатор отклоняется с 400 INVALID_PARAMS до обращения к Битрикс24 — единообразно на чтении, изменении и удалении, и в одиночных запросах, и в пакетных (/v1/batch, /v1/<раздел>/batch). Правило действует для всех сущностей с числовым идентификатором. Целые значения работают как прежде. Сущности, у которых идентификатор не число (например статусы заказа N/P/F, валюты, коды бизнес-процессов), а также чаты, принимающие форму chat1, сохраняют прежнее поведение.

NEW-0814-7: чтение ленты уведомлений и счётчика непрочитанных

Появился GET /v1/notifications — чтение ленты уведомлений сотрудника и счётчика непрочитанных. Раньше API Вайбкод умел только отправлять уведомления, отмечать их прочитанными и удалять, поэтому приложению-инбоксу приходилось держать вторую, отдельную интеграцию с Битрикс24 ради чтения.

Ответ несёт список уведомлений, карточки их авторов, общий счётчик, счётчик непрочитанных и признак hasMore. Размер страницы задаёт limit (от 1 до 50, по умолчанию 50); значение вне диапазона приводится к границе, и применённый размер всегда виден в meta.appliedLimit. Постраничный обход идёт курсором Битрикс24: lastId и lastType передаются только вместе. Счётчик непрочитанных без выкачивания страницы: вызов с limit=1.

Лента принадлежит владельцу токена — своего параметра «чей инбокс» у операции нет, поэтому по личному ключу возвращается лента владельца ключа, а лента конкретного сотрудника доступна по ключу OAuth-приложения с заголовком Authorization: Bearer. Лента не отфильтрована по приложению: в ней будут уведомления других приложений и системные уведомления портала, отбирать свои нужно по notifyTag или notifyModule.

NEW-0814-8: История рабочего дня в V1 API

Появился эндпоинт GET /v1/workday/records — история рабочих дней одного сотрудника за период. Он отдаёт время начала и завершения дня, отработанные секунды, длительность перерывов и признак подтверждения записи, поэтому отчёты по опозданиям и переработкам собираются через API Вайбкод, без отдельной интеграции с учётом рабочего времени на портале.

Параметр userId обязателен: без него портал Битрикс24 отказывает в вызове. Период задаётся необязательными from и to в формате ISO-8601 с явным смещением часового пояса или Z; если период не передан, возвращается окно за последние 7 дней. Дата без времени отклоняется — граница рабочего дня зависит от часового пояса, и молчаливое приведение к UTC переклассифицировало бы те самые опоздания, ради которых эндпоинт и нужен.

За один запрос возвращается не больше 50 записей, глубина берётся через offset или page. Признак продолжения — meta.hasMore. Поле meta.total приходит только тогда, когда страница оказалась короче запрошенного limit: в этом случае размер выборки известен точно, а на полной странице он неизвестен, и выдуманное число туда не подставляется.

Скоуп прежний — timeman. Права на чтение чужого табеля определяет портал Битрикс24: их выдают администратору или прямому руководителю сотрудника.

FIX-0814-9: отказ INT_VIBE_PLUS_REQUIRED ведёт на страницу тарифа в аккаунте

Было

У кода INT_VIBE_PLUS_REQUIRED поля details.upgradeUrl и alternatives[0].url несли общую страницу цен Битрикс24. Тариф линейки Vibe+ подключается в самом аккаунте, поэтому общая страница не показывала, что именно нужно сделать.

Стало

Оба поля несут адрес аккаунта клиента, открывающий разбор нужного тарифа: https://<домен аккаунта>/online/?feature_promoter=limit_why_pay_tariff_vibe. Тот же адрес отдаёт слот servers.create в GET /v1/me — раньше он расходился с телом отказа.

Если домен аккаунта распознать не удалось, оба поля по-прежнему несут общую страницу цен: битого адреса наружу не уходит.

Влияние на интеграторов

Менять ничего не нужно. Форма ответа прежняя, оба поля остаются строкой с адресом. Клиент, который вёл человека по details.upgradeUrl, теперь приводит его к нужному тарифу вместо общего прайса. Прежний код отказа, состав полей и статус ответа не изменились.

BC-0814-10: расшифровка аудио принимает файл только в поле file, не обрезает его молча, а отказ распознавания возвращает как 400

Поддержка старого формата до: не предусмотрена

Было

POST /v1/audio/transcriptions брал первую файловую часть multipart независимо от имени поля, а расширение имени файла не проверял: неизвестное расширение подставлялось как audio/mpeg и уезжало к распознаванию. Файл сверх лимита 25 МБ не отклонялся, а молча обрезался по лимиту: ответ приходил 200 с расшифровкой только начала записи, и она тарифицировалась, — признака обрезки в ответе не было. Любой не-2xx от сервиса распознавания возвращался как 502 ai_provider_unavailable, включая отказ по содержимому запроса.

Стало

Файловая часть обязана называться file, иначе 400 no_file — независимо от размера присланного файла. Проверка срабатывает до вызова распознавания. Расширение в имени файла НЕ проверяется: контейнер определяет распознавание по содержимому, поэтому редкие форматы диктофонов, имя без расширения и часть без имени принимаются как раньше. Файл, который распознавание прочитать не смогло, возвращается как 400 ai_provider_rejected. Файл сверх 25 МБ отклоняется целиком — 413 request_too_large, без списания; обрезанной расшифровки больше не бывает. Отказ распознавания с HTTP 400 или 422 возвращается как 400 ai_provider_rejected с полем providerStatusCode; ограничение частоты с его стороны — как 429 rate_limit_exceeded с заголовком Retry-After; недоступность и ошибки авторизации остаются 502 ai_provider_unavailable.

Что делать интеграторам

Назовите файловую часть file — прежнее имя вроде audio больше не подходит. Записи длиннее 25 МБ режьте на части до отправки: теперь такой запрос отклоняется, а не расшифровывается частично. Если код различал ошибки по статусу, учтите, что отказ по содержимому теперь приходит как 400, а не 502, и повторять такой запрос бессмысленно. Расширения менять не нужно: список включает и те форматы, которые принимались молча. Старое поведение не возвращается.

NEW-0814-11: изображения товаров каталога

Добавлены GET /v1/catalog-products/:productId/images для снимка метаданных штатных изображений товара и GET /v1/catalog-products/:productId/images/:imageId для одного изображения. В снимок входят детальная картинка, картинка анонса и галерея MORE_PHOTO, но не файлы из других пользовательских свойств. Оба метода требуют скоуп catalog, возвращают недоверенный detailUrl и не раскрывают подписанный downloadUrl; серверно загружать URL можно только после платформенной SSRF-проверки. Документация.

NEW-0814-12: расшифровка аудио может списываться с квоты подписки Cowork/Code

Раньше вызов POST /v1/audio/transcriptions ключом со скоупом vibe:cowork не списывал ничего: расшифровка считалась по квоте аккаунта Битрикс24, а ключи подписки этот счётчик пропускают. Теперь администратор платформы может назначить модели расшифровки цену за минуту аудио, и такой вызов расходует квоту подписки — так же, как чат.

Пока цена не назначена, поведение прежнее: вызов бесплатный и лимитом подписки не ограничивается.

Когда цена назначена, а окно квоты исчерпано, ручка отвечает 402 с кодом cowork_quota_exhausted — тем же, что уже отдаёт чат, — и заголовком Retry-After с числом секунд до обновления окна. Тело несёт window (5h / week / month), resetAt и nextTier.

Форматы ответа без длительности (text, srt, vtt) тарифицируются по цене за вызов, потому что длительность аудио в них не приходит.

FIX-0814-13: расписание пробуждения больше не сокращает заданный авто-сон

Было

Если у сервера одновременно заданы авто-сон (sleepAfterMinutes — 30, 60 или 240 минут) и включённое окно расписания пробуждения, порог простоя молча заменялся на платформенный короткий порог в 15 минут. Сервер засыпал через 15 минут вместо заданных, а GET /v1/infra/servers/:id и карточка сервера продолжали показывать выбранное значение — расхождение нельзя было увидеть нигде.

Стало

Заданное значение действует как задано: расписание отвечает только за момент пробуждения. Короткий порог остаётся ровно там, для чего он и вводился, — у сервера без авто-сна (sleepAfterMinutes: null), чтобы он всё же засыпал в промежутках между окнами. Ничего менять в интеграции не нужно; серверы с заданным авто-сном и расписанием теперь держатся включёнными до своего порога.

NEW-0814-14: запись с повреждённой кодировкой возвращает предупреждение

Было

Если в названии или описании приходил текст, где не-ASCII символы уже заменены на вопросительные знаки, платформа молча сохраняла такое значение. Карточка приложения на портале показывала ????????? ????????, и понять это можно было только глазами.

Стало

Значение по-прежнему сохраняется, но ответ дополнительно несёт массив warnings — он называет поля, пришедшие без единого не-ASCII символа, и предлагает отправить текст в UTF-8. Так отвечают POST /v1/infra/servers и PATCH /v1/infra/servers/{id} (displayName, description), POST /v1/infra/servers/{id}/deploy (displayName, description), POST /v1/apps и PATCH /v1/apps/{id} (title), POST /v1/apps/{id}/publish (catalogTitle, catalogDescription). Предупреждение приходит только на поле, которое вызов действительно применил: повторная присылка того же значения и поле, отброшенное деплоем как уже заполненное, молчат. Поле необязательное: когда сообщать нечего, его в ответе нет, поэтому прежние клиенты работают без изменений.

2026-08-13

FIX-0813-1: отказ сборки нативного модуля называется своей причиной, а не «не найден Python»

Было

Приложение на Node с зависимостью, которая содержит машинный код (например better-sqlite3), разворачивалось через раз. Такие зависимости ставятся в два хода: сначала скачивается уже собранный файл, а если не вышло — модуль собирается из исходников на месте. Когда скачивание срывалось из-за внешней сети, начинался второй ход и падал — в образе приложения нет инструментов сборки. В ответе на деплой и на карточке приложения оказывалась последняя строка этого второго хода, Could not find any Python installation to use, и она уводила от настоящей причины: клиент искал ошибку в своём коде и в версии Python, хотя ни то, ни другое сломано не было. Категория отказа приходила общая — INSTALL_FAILED, с подсказкой про нехватку компилятора.

Стало

У такого отказа своя категория — NATIVE_PREBUILD_UNAVAILABLE, она приходит в поле category ответа 502 GALAXY_APP_BUILD_FAILED и в buildHint карточки приложения. Текст подсказки говорит то, что есть: готовую сборку не удалось скачать, внешний источник не ответил, это временно, повторите развёртывание через несколько минут. Категория ставится, только когда обе улики в логе сборки совпали — скачивание сорвалось по сети И собрать из исходников оказалось нечем; отказы, которые повтор не лечит (для этой платформы готовых сборок не публикуют, на хосте кончилось место), в неё не попадают и сохраняют прежний текст. Повторяет деплой клиент — платформа сборку сама не пересылает.

FIX-0813-2: Формы ответов методов API описаны в машинной схеме

Было

Машинная схема не показывала фактические формы ответов для POST /v1/duplicates/find и GET /v1/lists/{iblockId}/elements. Это затрудняло обработку результата поиска дублей и Bitrix24-native полей элементов списков.

Стало

Схема описывает для поиска дублей объект с ключами типов сущностей или пустой массив, а для элементов списков — массив в конверте с динамическими ключами свойств и счётчиком meta.total, не меняя фактический ответ API.

Влияние на интеграторов

Интеграции могут использовать машинную схему для выбора формы ответа. Для элементов списков учитывайте, что параметр limit игнорируется.

NEW-0813-3: настоящее состояние спящего galaxy-приложения в ответе GET сервера и в ответе логов

GET /v1/infra/servers/:id для galaxy-приложения теперь отдаёт блок reachability, который отвечает на вопрос «может ли приложение ответить прямо сейчас». Поле status не изменилось — это состояние учётной записи приложения, и во время пробуждения оно отстаёт от машины: запись остаётся в sleeping, пока платформа поднимает галактику, а на холодной машине это занимает минуты. Рядом с ним reachability несёт сводный effectiveStatus, состояние галактики-носителя hostStatus и hostTunnel, живо снятые container и forwarder, исход опроса probe и его время probedAt. Опрос идёт только по работающей галактике, спящую он не будит, поэтому у спящей галактики container и forwarder приходят как unknown. У серверов остальных типов блок равен null.

Ответ GET /v1/infra/servers/:id/logs для приложения на спящей галактике дополнен полем recovery и называет рабочий путь: вызов пробуждения, признак logsPreserved (пробуждение запускает тот же контейнер, поэтому строки, записанные до сна, остаются в журнале), бюджет подъёма холодной галактики в секундах, чем проверять готовность и доступно ли этому приложению окно пробуждения по расписанию. Поле hint осталось строкой и теперь тоже называет вызов пробуждения.

Разбор состояний и порядок действий — Сон и пробуждение Galaxy-приложения.

FIX-0813-4: скачивание файла объявляет требуемый скоуп в openapi.json

Было

У операции GET /v1/files/{fileId}/download в машинной спецификации не было поля x-required-scope, хотя рантайм отвечает 403 SCOPE_DENIED без скоупа. Клиент или агент, собиравший ключ по спецификации, видел «скоуп не нужен» и получал отказ на первом же вызове. Эта же операция попадала в любой срез openapi.json?scope=, включая срезы других модулей.

Стало

Операция объявляет x-required-scope: disk — основной скоуп. Обработчик принимает и crm: ключ только с ним скачивает файл, прикреплённый к пользовательскому полю карточки CRM. Второй скоуп объявлен новым необязательным полем x-alternative-scopes рядом с основным. В срезах операция остаётся при openapi.json?scope=disk и ?scope=crm, в остальных её больше нет.

Влияние на интеграторов

Клиент, читающий только x-required-scope, запросит disk и продолжит работать как раньше. Клиенту с ключом на одном crm теперь видно из спецификации и со страницы операции, что вызов ему доступен: раньше об этом сообщали только текст отказа и самоописание ключа в /v1/me.

FIX-0813-5: Понятный текст ошибки сборки вместо машинного вывода и внешних адресов

Было

Когда сборка приложения падала из-за недоступности хранилища базовых образов, в provisionError и в ответе 502 приходил дословный вывод сборщика: имя внешнего хранилища, путь запроса и публичный IP-адрес. Для остальных отказов сборки в тексте тоже могли оказаться внешние ссылки и публичные IP-адреса.

Стало

Недоступность хранилища образов описывается фиксированной фразой: причина и действие — повторить тот же деплой через несколько минут, слот и данные не тронуты. Категория отказа (provisionErrorCategory) и признак повторяемости не изменились. Во всех прочих отказах сборки причина по-прежнему передаётся как есть, но внешние адреса в ней заменяются на пометки — и ссылки целиком, и имя хранилища образов без схемы, когда строка сама говорит про образ, и адрес, названный вообще без пути в строке сетевого отказа (getaddrinfo ENOTFOUND …, «не удалось разрешить имя хоста»), и публичные IP-адреса. Адреса самой платформы, локальные и внутренние остаются видны, как и имя образа с версией, имена файлов и имена пакетов: пометка ставится там, где текст сам называет адрес адресом, — поэтому имя пакета вида socket.io остаётся читаемым, хотя по форме не отличается от имени хранилища. Одно исключение: числовой ряд из четырёх частей (11.0.16.1) неотличим от IP-адреса, поэтому версия такого вида тоже станет пометкой.

Влияние на интеграторов

Менять ничего не нужно: коды ошибок, категории и признак повторяемости те же. Клиент, который разбирал текст provisionError по подстрокам, для этого класса отказа получит стабильную фразу вместо изменчивого вывода сборщика, а полный лог сборки по-прежнему доступен в поле buildLog того же ответа.

NEW-0813-6: манифест бандла агента сообщает режим сверки рантайма

В ответ GET /v1/agent-bundles/:kind/manifest.json добавлены два необязательных поля.

runtime_integrity принимает значение "on" или "off" и говорит агенту, сверять ли файлы своего окружения с контрольными суммами, записанными при установке. Агент выполняет сверку только при значении "on"; отсутствие поля означает, что она выключена.

runtime_integrity_budget — целое число, которое платформа сейчас всегда присылает равным единице. Агент читает его как разрешение: больше нуля — восстановление допускается, ноль — запрещено. Частоту попыток ограничивает сам агент, не чаще одной в час, поэтому значения больше единицы поведение не меняют. Режимом восстановления управляет не это поле, а настройка платформы; поле нужно, чтобы агент без него по умолчанию ничего не восстанавливал.

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

NEW-0813-7: манифест бандла агента получил операцию точечного ремонта рантайма

В ответе GET /v1/agent-bundles/:kind/manifest.json в объекте operations появился ключ repair_runtime.

Операция переустанавливает движок агента на месте, когда файлы его окружения разошлись с контрольными суммами, записанными при установке. Раньше такое расхождение лечилось только пересозданием приложения целиком.

Поле available_to у неё содержит единственное значение admin: операция редкая и запускается вручную, поэтому вызов от имени регулярных заданий отклоняется с ошибкой OPERATION_FORBIDDEN. Успехом считается не только нулевой код возврата, но и подтверждение от агента, что окружение сошлось; иначе операция отвечает status: "failed" с причиной в поле error.

Прежние запросы не затронуты: остальные операции, поля манифеста и контрольная сумма архива не изменились. Клиентам, читающим манифест, действий не требуется.

FIX-0813-8: Отказ reauth больше не советует OAuth ключам без OAuth

Было

POST /v1/bots/{botId}/reauth на мёртвых кредах всегда отвечал 410 REAUTH_REQUIRED с одним и тем же советом — перезапустить авторизацию через POST /v1/oauth/authorize либо пересоздать личный ключ. Ключу, который ходит в Битрикс24 через входящий вебхук, этот совет выполнить нечем: ни потока авторизации, ни токена обновления у него нет. Владелец такого ключа читал инструкцию и упирался в тупик.

Стало

Текст отказа зависит от того, чем ключ авторизуется, а в error.details появилось поле credentialKind — oauth, webhook или unknown. Вебхучному ключу отказ называет реальную причину (вебхук мёртв, чаще всего удалён на стороне Битрикс24) и реальное лекарство — перевыпуск вебхука, при котором идентификатор и строка ключа сохраняются, а привязанные боты продолжают работать. Ключу с OAuth текст прежний.

Влияние на интеграторов

Менять ничего не нужно: код ответа и его статус не изменились. Клиент, который разбирал текст отказа строкой, увидит новую формулировку у вебхучных ключей — надёжнее ветвиться по error.details.credentialKind.

FIX-0813-9: удаление galaxy-приложения на непробуждаемом хосте отвечает терминальным 409

Было

DELETE /v1/infra/servers/{id} для приложения на galaxy-хосте, который нельзя разбудить (заморожен счёт либо стоит запрет пробуждения), отвечал 502 GALAXY_HOST_UNREACHABLE с объектом error.hint и советом повторить позже. Совет не срабатывал никогда: пробуждение было не провалено, а запрещено, поэтому клиент повторял запрос до собственного таймаута.

Стало

Такой отказ терминален и приходит как 409 GALAXY_HOST_WAKE_BLOCKED. В теле — поле error.reason с причиной: BILLING_FROZEN, ACCESS_EXPIRED, STOPPED или UNKNOWN. Объекта error.hint на этом пути больше нет — он описывал восстановление недостижимого хоста, а восстанавливать здесь нечего; для настоящей недостижимости 502 с hint сохраняется без изменений. Повторять запрос бессмысленно — нужно снять причину.

Заодно уточнён текст 409 GALAXY_HAS_APPS при удалении самого хоста: удаление галактики вместе с её приложениями доступно в кабинете, на публичном интерфейсе такой операции нет.

NEW-0813-10: пробный период Маркета включается с ключа десктопа Коворк/Код

Появился эндпоинт POST /v1/cowork/activate-market-trial: он включает одноразовый пробный период подписки Маркета для аккаунта Битрикс24, к которому привязан ключ десктопа Коворк/Код. Раньше программный путь был только у личных ключей и ключей приложений — ключ десктопа эндпоинт POST /v1/portals/{id}/activate-market-trial отклонял, и этот отказ остаётся в силе.

Параметра пути нет: аккаунт берётся из привязки ключа, поэтому клиенту не нужно знать внутренний идентификатор. Тело обязательное — {"acknowledgedOneTimeConsumption": true}, ровно литерал true. Им клиент подтверждает, что показал пользователю: включается одноразовый период, который нельзя отозвать, и назвал дату окончания. Подтверждение попадает в журнал действий аккаунта.

Ответ при успехе — {"success": true, "data": {"status": "activated", "trialEndsAt": "…"}}; вместо activated может прийти already_active (период или подписка уже действуют) либо pending — включение состоялось, подтверждения от Битрикс24 ещё нет, и повторять запрос в этом случае не нужно. Срок задаёт Битрикс24, поэтому показывайте пользователю дату из trialEndsAt, а не своё число дней.

Отказы: 400 DISCLOSURE_REQUIRED (нет подтверждения), 403 INSUFFICIENT_SCOPE (у ключа нет права vibe:cowork), 403 COWORK_DESKTOP_KEY_REQUIRED (ключ другого класса — например, ключ места агента с тем же правом), 403 WRITE_BLOCKED_READONLY_KEY, 409 ALREADY_ACTIVATED, 409 TRIAL_ACTIVATION_UNAVAILABLE, 503 TRIAL_ACTIVATION_RETRY. Предел частоты — 3 запроса в час на аккаунт.

Перед показом шага активации читайте activation.marketTrial.available в GET /v1/cowork/state: это предрасчёт, false окончателен, а true разрешает предлагать, но не обещает успех — отказ на самом включении обрабатывать всё равно нужно. В регионах с тарифной моделью доступа такого пробного периода нет как продукта: предрасчёт отвечает region_not_supported, а сам вызов — 409 TRIAL_ACTIVATION_UNAVAILABLE.

FIX-0813-11: временные ошибки больше не закрывают пробный период навсегда

Было

У каждого аккаунта был счётчик неудачных включений пробного периода, и он доходил до потолка от ЛЮБОЙ ошибки — в том числе от той, где Битрикс24 ничего не решал: запрос до него не дошёл, он ответил внутренней ошибкой или не сложилась наша собственная авторизация. Дойдя до потолка, аккаунт получал 409 TRIAL_ACTIVATION_UNAVAILABLE на любой следующий запрос, и вернуть его в рабочее состояние было нечем. Ответ 503 TRIAL_ACTIVATION_RETRY при этом предлагал повторить — то есть подсказка вела ровно в ту ловушку, где серия сетевых сбоев стоила аккаунту одноразового пробного периода.

Стало

Счётчик расходуют только решения Битрикс24 о самом аккаунте. Ошибка транспорта, внутренняя ошибка на стороне Битрикс24 и проблема с нашей авторизацией попытку не тратят — повтор после 503 TRIAL_ACTIVATION_RETRY теперь безопасен, как и написано в ответе.

Влияние на интеграторов

Менять ничего не нужно. Повторять запрос после 503 можно так же, как и раньше, — но теперь это действительно не приближает аккаунт к отказу. 409 TRIAL_ACTIVATION_UNAVAILABLE остаётся окончательным и означает то, что означал: решение Битрикс24 о самом аккаунте.

FIX-0813-12: активации пробного периода снова хватает на три попытки в час

Было

POST /v1/portals/{id}/activate-market-trial обещал в описании три запроса в час на аккаунт, а на деле отдавал одну попытку: заголовок x-ratelimit-limit приходил со значением 1, и второй запрос в течение часа получал 429 RATE_LIMITED с retry-after около часа. У пользователя, у которого включение сорвалось из-за временной ошибки, кнопка «Повторить» оставалась бесполезной до конца часа.

Стало

Аккаунт получает те самые три попытки в час, как и написано в описании. Значение x-ratelimit-limit совпадает с обещанным, 429 приходит с четвёртого запроса.

Влияние на интеграторов

Менять ничего не нужно. Если вы заложили у себя паузу в час после первого же 429, её можно вернуть к обычной обработке заголовка retry-after.

NEW-0813-13: новый код отказа доступа INT_VIBE_PLUS_REQUIRED

В справочнике кодов появился INT_VIBE_PLUS_REQUIRED (HTTP 402). На международном сегменте он приходит там же, где приходит INT_TARIFF_REQUIRED — на создании и пробуждении инфраструктуры и на выписке ключей — и означает, что аккаунту Битрикс24 нужен тариф Vibe+. Добавление аддитивное: прежние коды и форма ответа не изменились, поэтому обрабатывайте незнакомый код как отказ и ветвитесь по error.code, а не по тексту сообщения. Разбор кодов — в Ошибках.

NEW-0813-14: состояние Cowork/Code отдаёт оплаченный срок

Ответ GET /v1/cowork/state получил два поля в объекте subscription. termMonths — на сколько месяцев вперёд оплачено сиденье (1, 3, 6 или 12; у помесячного — 1). paidThroughAt — дата, до которой оплачено, в формате ISO 8601.

Это не то же самое, что currentPeriodEnd. Расчётный период — окно квоты, оно сдвигается каждые 30 дней независимо от того, на какой срок куплено сиденье. Поэтому у сиденья, оплаченного на год, currentPeriodEnd наступает через месяц, а paidThroughAt — через одиннадцать.

Заодно уточнено описание subscription.pendingTier: понижение тарифа применяется на paidThroughAt, а не на currentPeriodEnd. До появления сроков эти даты всегда совпадали, поэтому прежняя формулировка была верной; теперь совпадение сохраняется только у помесячных сидений. По той же дате заканчивается доступ, если подписка отменена: отмена прекращает автопродление, а оплаченный срок дослуживается целиком.

Прежние вызовы работают без изменений — оба поля добавлены, ни одно не переименовано и не удалено.

FIX-0813-15: переименование сервера теперь видно в кабинете

Было

PATCH /v1/infra/servers/:id меняет displayName и description сервера — документация этой ручки называет их двумя текстовыми полями, которые человек видит в личном кабинете и в карточке приложения в каталоге Битрикс24, и другой ручки для них нет. Но список и карточка приложения в кабинете показывали не сервер, а копию его имени и описания, снятую в момент создания приложения. После переименования GET /v1/infra/servers/:id и GET /v1/me/sources отдавали новое значение, а кабинет — прежнее, и вернуть его к правде было нечем.

Стало

У приложения со связанным сервером кабинет показывает displayName сервера. Описание кабинет берёт своё, если его там задавали, иначе — описание сервера, а правка описания через эту ручку теперь доходит до приложения и тогда, когда у него было своё описание: последняя правка выигрывает.

Имя стало одним значением с двумя дверями: переименование приложения в кабинете меняет displayName сервера, а переименование сервера обновляет карточку. Заголовок карточки в каталоге Битрикс24 после любой из этих правок перевыкладывается автоматически. Имя, введённое в кабинете при создании приложения, теперь попадает и в displayName нового сервера, а не только в карточку.

Действий со стороны клиента не требуется. Приложения, созданные до исправления, тоже следуют за сервером: имя видно в кабинете сразу, а описание — с первой же правки через эту ручку, до неё в кабинете остаётся прежний текст.

2026-08-12

NEW-0812-1: nextPollAfterMs — платформа может попросить опрашивать события реже

GET /v1/bots/{botId}/events отдаёт новое необязательное поле nextPollAfterMs — сколько миллисекунд стоит подождать перед следующим запросом. Поле приходит только боту, которому события доставляются вебхуком (eventMode равен webhook): очередь Event.get у такого бота пуста по устройству Битрикс24, поэтому частый опрос не приносит ничего. Боту в режиме fetch поле не приходит вовсе — опрашивайте с прежней частотой.

Отсутствие поля означает «опрашивайте как раньше», поэтому пустым или нулевым оно не приходит. Поле необязательное: клиент, который его игнорирует, продолжает работать без изменений. Если в ответе есть непрочитанный остаток (hasMore равен true), дочитайте очередь не дожидаясь присланной паузы — она относится к следующему пустому опросу.

FIX-0812-2: `touSavedPct` теперь доля месячного объёма подписки, а не доля расхода

Было

GET /v1/cowork/state считал touSavedPct как долю недисконтированного счёта — сэкономлено / (сэкономлено + израсходовано). У такой дроби числитель и знаменатель обязаны покрывать одно и то же окно, а счётчик экономии начал работать посреди уже идущего расчётного периода. Поэтому поле намеренно возвращало null до конца первого периода — на практике почти у всех подписок.

Стало

Знаменатель — месячный объём подписки, тот же, что у полосы месячной квоты. Значение читается с первого дня и не подавляется: null приходит, только когда за текущий период ещё ничего не сэкономлено. Верхняя граница — 100.

Две доли теперь читаются рядом: «израсходовано N% месячного объёма» и «выгодные часы вернули M% месячного объёма». Тип и диапазон поля не изменились, действий на стороне клиента не требуется.

NEW-0812-3: Порт приложения закрепляется за сервером и переживает пробуждение

Было GET /v1/infra/servers/:id отдавал только localPort, а он равен 3000 у всех серверов по умолчанию — понять по ответу, ходит агент строго на этот порт или выбирает его сам, было нельзя. Порт, заданный через PATCH /v1/infra/servers/:id/port, жил только в памяти агента: после пробуждения сервера или починки туннеля выбор шёл заново, и публичный адрес мог начать отдавать соседний процесс.

Стало Успешная выкладка, прошедшая проверку работоспособности, записывает порт в настройки агента на машине и помечает сервер закреплённым. Ответ GET /v1/infra/servers/:id несёт portPinned: при true агент проксирует строго на localPort и не переизбирает порт ни после пробуждения, ни после починки; при false порт по-прежнему определяется автоматически по слушающим сокетам.

У закреплённого сервера PATCH /v1/infra/servers/:id/port применяет порт перезаписью настроек агента и его перезапуском, а не командой на лету. Следствия для клиента: 409 PORT_NOT_APPLIED на таком сервере не приходит никогда; туннель на несколько секунд обрывается; verified: true означает, что настройки несут запрошенный порт и агент вернулся на связь, а verified: false — что порт записан, но возвращения агента дождаться не удалось (это не отказ, порт применится). port: 0 снимает закрепление. Добавлены два кода отказа: 502 AGENT_CONFIG_WRITE_FAILED — машина отклонила запись настроек, 502 GATEWAY_ERROR — команда не дошла до машины.

В data.steps[] ответа POST /v1/infra/servers/:id/deploy появился шаг port_pin — исход записи порта в настройки агента. Он не влияет на успех выкладки: warning на нём означает, что приложение выложено и работает, но порт не закрепился.

BC-0812-4: context в обращениях должен быть JSON-объектом

Поддержка старого формата до: не предусмотрена

Было

POST /v1/feedback принимал context любого JSON-типа, включая строку, массив, число, boolean и null.

Стало

context остался необязательным. Если поле передано, его верхний уровень должен быть JSON-объектом, включая {}. Строка, массив, число, boolean и null получают 400 VALIDATION_ERROR; обращение не создаётся.

Что делать интеграторам

Передавайте объект: {"context":{"endpoint":"/v1/feedback"}}. Отправители, которые передавали строку, массив, число, boolean или null, должны перейти на объект: старый формат перестаёт приниматься с этим релизом.

NEW-0812-5: публикация приложения принимает исходники с указанного сервера

POST /v1/apps/:id/publish принимает необязательное поле sourceServerId (или заголовок X-Source-Server) — идентификатор сервера, исходники которого публикуются. Это закрывает случай, когда автосейв деплоя отвечал autoSaved: true, а следующая публикация всё равно возвращала 409 SNAPSHOT_REQUIRED: если сервер принадлежит не OAuth-ключу публикуемого приложения (например, деплой шёл под личным ключом vibe_api_), снапшот сохраняется у сервера, а не у приложения, и проверка публикации его не видела. Теперь достаточно назвать тот же сервер, по которому вы вызывали деплой; повторно сохранять исходники через POST /v1/apps/:id/sources не нужно.

Сервер платформа не подбирает — идентификатор передаёте вы. Права на сервер проверяются отдельно от прав на приложение: подходит ключ-владелец сервера, личный ключ того же пользователя или администратор портала. Сервера нет, он из другого портала или удалён — 404 SERVER_NOT_FOUND; прав нет — 403 NOT_AUTHORIZED; заголовок X-Source-Server не в форме UUID или передан дважды — 400 VALIDATION_ERROR. Поле тела приоритетнее заголовка: когда sourceServerId пришёл в теле, заголовок не читается и не проверяется. Вместе с sourceServerId поле sourceVersionId означает версию этого сервера: нумерация версий у сервера своя.

Тег published и отметка о публикации ставятся только на версию самого приложения. Версию сервера публикация не помечает: те же поля у неё хранят историю деплоя, которую отдаёт GET /v1/infra/servers/:id/sources, а тег означает бессрочное хранение и запрет на удаление. Отсюда следствие, о котором стоит знать: у опубликованной версии сервера бессрочного хранения нет — она живёт по обычным правилам очистки и может быть удалена, пока приложение остаётся опубликованным. Поэтому в warnings приходит строка с готовым PATCH /v1/infra/servers/:id/sources/vN, которым версию можно пометить самому; если версия уже помечена, строки не будет. Если хранилище исходников для портала отключено, переданный sourceServerId не используется вовсе — публикация проходит, а в warnings приходит строка о том, что селектор не пригодился.

Без sourceServerId поведение прежнее — проверка ищет свежий снапшот приложения. У отказа 409 SNAPSHOT_REQUIRED появилось поле hint.reason с четырьмя значениями (app_snapshot_missing, app_snapshot_stale, server_snapshot_missing, server_snapshot_stale), а на пути без указанного сервера — блок hint.serverKeyedSources, который объясняет, как опубликовать исходники с сервера. Текст сообщения этого отказа переписан: раньше он начинался со слова «Deploy» и предлагал единственный выход через POST /v1/apps/:id/sources.

Подробности — Опубликовать приложение и Хранилище исходников.

NEW-0812-6: товарные позиции сделки: в ответе появился признак усечения

Было

GET /v1/{entity}/{id}/products (сделки, лиды, счёта, предложения, элементы смарт-процессов) отдавал только первую страницу товарных позиций — Bitrix24 отдаёт их порциями по 50 — и не сообщал об этом никак. Ответ состоял из success и data, поля meta не было вовсе, поэтому у элемента с 125 позициями и у элемента с 50 позициями ответы выглядели одинаково, и приложение теряло строки, не имея возможности это заметить. Параметры limit и offset на этом маршруте не действовали.

Стало

Ответ дополнен блоком meta: meta.total — сколько товарных позиций у элемента всего (по данным Bitrix24), meta.hasMore — есть ли строки за пределами отданной страницы. Поля success и data не изменились, поэтому клиент, который читает только их, продолжает работать как раньше.

Заодно на GET /v1/deals/:id/products и его аналогах у лидов, счетов, предложений и элементов смарт-процессов начали работать limit и offset. limit до 5000 отдаёт запрошенное число позиций за один вызов, значения выше 5000 приводятся к 5000. offset считается по строкам, а не по страницам: offset=7 начинает выдачу с восьмой позиции. meta.hasMore верна для любого запрошенного окна, а не только для первого. Вызов без limit и offset ведёт себя ровно как раньше. Просьба limit=0 размером страницы не считается: применяется значение по умолчанию, а в meta.warnings появляется запись с кодом LIMIT_ZERO_IGNORED — так же, как на списочных маршрутах. То же поведение описано в knownIssues ответа GET /v1/guide.

NEW-0812-7: машинная схема описывает работающие методы приложений, ИИ и телефонии

Восемнадцать операций, которые работали и были описаны на страницах документации, отсутствовали в машинной схеме GET /v1/openapi.json. Клиент, который сверялся со схемой, не находил метод и делал вывод, что метода не существует. Теперь схема их описывает: семейство приложений (GET /v1/apps, создание, чтение, изменение, удаление, публикация и снятие с публикации, а также перепривязка OAuth), расход и квота ИИ (GET /v1/ai/usage, GET /v1/ai/quota), расписание выгодных часов (GET /v1/off-peak), ИИ-итоги завершённых звонков (POST /v1/calls/followups/list, GET /v1/calls/followups/:callId) и список линий (GET /v1/voximplant-lines).

Сами методы не новые и не менялись — ни адреса, ни параметры, ни формы ответов. Новое только описание, поэтому менять в интеграции нечего.

Четыре метода ИИ, совместимых с OpenAI, отвечают по двум адресам сразу: /v1/models и /v1/ai/models, и так же для завершений чата, векторных представлений и расшифровки аудио. В схеме были только короткие адреса. Теперь описаны оба, и у формы с приставкой /v1/ai/ прямо указано, что она устаревшая: такие ответы несут заголовки Deprecation: true и X-Deprecated-Use с каноническим адресом. Вызовы по ним работают по-прежнему, но переходить стоит на короткий адрес. Исключение — GET /v1/ai/usage и GET /v1/ai/quota: у них короткой формы нет, они сами канонические и заголовков депрекации не несут.

Схема по-прежнему не описывает адреса-подсказки, которые существуют только чтобы вернуть ошибку «неверный путь» со списком настоящих адресов, и карточку одной модели: её идентификатор сам содержит косую черту, и параметр пути в OpenAPI такое значение описать не может.

FIX-0812-8: список планов бесплатного тарифа отдаётся публичным идентификатором

Было

На международной поверхности allowedPlans в теле 402 PLAN_NOT_ALLOWED_ON_TRIAL и в capabilities.servers.create.limits (GET /v1/me) нёс внутренний идентификатор плана — не тот, которым тот же план назван в каталоге GET /v1/infra/providers/:providerId/plans. Тот же идентификатор подставлялся в кавычках в человекочитаемый note, поэтому ИИ-ассистент показывал его пользователю дословно, а рядом стоящее requestedPlan могло вернуть план, который выбрала сама платформа (создание агента), а не клиент.

Стало

Оба поля и текст note несут тот же нейтральный идентификатор, который отдаёт каталог планов, поэтому ответ гейта и каталог наконец согласованы. Идентификаторы прежнего каталога по-прежнему принимаются на входе создания сервера, так что клиент, отправляющий ранее полученное значение, продолжает работать без изменений. В кабинете карточка только что созданного сервера больше не показывает идентификатор вместо названия плана.

FIX-0812-9: Исправлен отбор по пользовательским полям реквизитов

Было

GET /v1/requisites с фильтром filter[ufCrm_1698325419] передавал имя пользовательского поля без изменения. Bitrix24 игнорировал такой ключ, поэтому успешный ответ не сужал список.

Стало

Оба написания одного поля — filter[UF_CRM_1698325419] и filter[ufCrm_1698325419] — принимаются и отбирают одинаково. Написание, которого Битрикс24 не даёт (например filter[ufCrm_taxId]), по-прежнему не применяется. Ошибка UNKNOWN_FILTER_FIELD для пользовательских полей не появилась.

FIX-0812-10: неподтверждённая активация демо Маркетплейса отдаёт статус pending, а не activated

Было

POST /v1/portals/{id}/activate-market-trial присылал два успешных статуса: activated и already_active. Когда Битрикс24 включал демо, но подтверждения по нему не отдавал, вызов всё равно отвечал activated с полем trialEndsAt — то есть сообщал о завершённой активации, которую никто не подтвердил.

Стало

У неподтверждённого исхода появился свой статус — pending, без trialEndsAt. Он означает, что демо у Битрикс24 уже включено и повторно включить его нельзя, но подтверждения мы пока не получили. Статусы activated и already_active теперь приходят только на подтверждённые исходы.

Влияние на интеграторов

Проверьте ветку data.status === 'activated': часть удачных активаций приходит в неё больше не будет. Обработайте pending как выполненный вызов — перечитайте состояние аккаунта через минуту (Самоописание ключа) и не повторяйте запрос, он упрётся в ограничение частоты.

FIX-0812-11: выкладка на отдельную виртуальную машину сохраняет внутренние ссылки сборки и проверяет корень приложения

Было

Перед запуском платформа удаляла из распакованного дерева все символические ссылки. Готовая сборка с серверным рендерингом связывает собственные файлы относительными ссылками внутри своего же каталога, поэтому приложение теряло часть зависимостей и на корневом адресе отвечало ошибкой 500. Проверка работоспособности опрашивала только путь из поля healthPath, так что лёгкая ручка вроде /api/health продолжала отвечать 200 и POST /v1/infra/servers/{id}/deploy сообщал об успехе. В Битрикс24 приложение при этом не открывалось: ошибка приходила с собственным заголовком приложения X-Frame-Options, и вместо страницы ошибки пользователь видел сообщение браузера о невозможности соединения.

Стало

Ссылки внутри каталога выкладки сохраняются. Удаляются только небезопасные: абсолютные, ведущие в никуда и те, чей действительный путь выходит за пределы каталога выкладки.

Кроме того, когда healthPath отличается от /, выкладка после успешной проверки этого пути опрашивает ещё и корень приложения — адрес, который открывает Битрикс24. Ответ 5xx на корне останавливает выкладку ошибкой на шаге healthcheck. Ответ 4xx выкладку не останавливает и приходит отдельным шагом app_root со статусом warning и пояснением в stdout. С healthPath по умолчанию (/) не меняется ничего: дополнительный запрос не выполняется, шага app_root в ответе нет.

Разбор ссылок стоит дороже прежнего удаления, поэтому на очень большом дереве он может не уложиться в отведённое время. Раньше такой шаг всё равно отчитывался успехом — теперь шаги normalize_windows_paths и cleanup_metadata в этом случае приходят со статусом warning и объяснением в stdout, а выкладка продолжается.

Влияние на интеграторов

Менять вызовы не нужно. Приложение, отвечающее на корне, ведёт себя как раньше. Сервер без страницы на корне — например, только с API — по-прежнему выкладывается успешно: его 404 приходит предупреждением, а чтобы предупреждения не было, укажите приложению тот путь, на котором оно действительно отвечает, через PATCH /v1/apps/{id}. Выкладка приложения, чей корень отвечает ошибкой 5xx, теперь завершается ошибкой, а не мнимым успехом — прежний зелёный ответ в этом случае был неверным.

BC-0812-12: числовые поля учёта времени возвращаются числами, а не строками

Поддержка старого формата до: не предусмотрена

Было

GET /v1/task-time и GET /v1/tasks/:taskId/time возвращали id, taskId, userId, seconds и minutes строками JSON. Справочник полей при этом объявлял их числовыми, то есть описание и ответ расходились.

Стало

Эти пять полей возвращаются числами JSON. Поле source остаётся строкой. Схема GET /v1/task-time/fields приведена в соответствие тем же изменением.

Что делать интеграторам

Обновите строгие схемы на своей стороне: там, где ожидалась строка, теперь придёт число. Старый формат не возвращается — сравнение вида seconds === "900" перестанет совпадать, нужно сравнивать с числом.

FIX-0812-13: фильтр с зарезервированным именем поля отклоняется, а не отдаёт всю коллекцию

Было

Фильтр с зарезервированным именем поля — filter[__proto__], filter[constructor], filter[prototype] — принимался и молча отдавал ПОЛНУЮ, неотфильтрованную коллекцию (а на /{entity}/aggregate — счёт по всей коллекции). Клиент, собравший имя поля фильтра из пользовательского ввода, считал выборку отфильтрованной.

Стало

Такой фильтр отклоняется с 400 UNKNOWN_FILTER_FIELD (как и любое другое неизвестное поле), на всех сущностях и на /{entity}/aggregate.

FIX-0812-14: ключ, которому разрешена выкладка, может и поправить приложение на том же сервере

Было

Ключ, привязанный к серверу через приложение, успешно выкладывал код: POST /v1/infra/servers/:id/deploy отвечал успехом. Но команда, загрузка файла и чтение логов тем же ключом отвечали 403 WRONG_KEY, и программного способа получить это право не было — смена управляющего ключа выполняется только в личном кабинете. Мелкая правка рантайма на сервере, который принимает выкладку, оказывалась недостижимой. Отказ на команде приходил без объекта hint, поэтому подсказки о восстановлении в нём тоже не было.

Стало

Права на сервер делятся по тому, чего касается вызов. Операции над содержимым приложения — выкладка, команда, загрузка файла и чтение логов — принимают либо управляющий ключ сервера, либо ключ, приложение которого привязано к этому серверу. Управление самой машиной (запуск, остановка, пробуждение, перезагрузка, сон, удаление, починка, режим, политика доступа, порт, SSH), токены доступа и загрузка значка по-прежнему требуют управляющего ключа.

Отказ 403 WRONG_KEY теперь на всех четырёх маршрутах несёт объект hint и называет оба шага восстановления: перепривязать сервер в кабинете и сменить ключ, которым обращается клиент — перепривязка не меняет секрет, который вы уже отправляете. Код ответа и его смысл не изменились: где отказывали раньше, отказывают и теперь.

Влияние на интеграторов

Менять ничего не нужно. Вызовы, которые работали, продолжают работать; вызовы, которые отвечали 403 WRONG_KEY из-за связи через приложение, теперь проходят. Подробности — восстановление доступа к серверу.

FIX-0812-15: место в общей галактике считается по измерению машины, а не по фиксированному числу мест

Было

Заявка на новое приложение в общую галактику отклонялась кодом GALAXY_FULL, как только на машине набиралось фиксированное число жильцов. Это число выводилось из объёма памяти, заявленного тарифом машины, а не из того, сколько на ней занято в действительности, — поэтому отказ приходил и на почти простаивающей машине, а под заявку поднималась ещё одна галактика.

Стало

Ответ строится по двум измеренным условиям самой машины: свободному месту на диске и прогнозу занятости памяти. Прогноз берёт занятость, которую машина показала на последнем замере, и добавляет к ней цену тех приложений, которые в тот момент не работали, — против измеренного объёма памяти. Пока оба условия выполняются, галактика принимает новых жильцов, и GALAXY_FULL означает, что места действительно нет.

Машина, по которой измерений ещё нет или они устарели, отвечает как раньше — по числу мест.

Как это включается

Новое правило приезжает выключенным и включается по порталам. Пока оно выключено, ответы на запросы не меняются: решает прежнее число мест. Причина — измерения заводятся этим же обновлением и накапливаются постепенно, обходом бодрых машин, поэтому раньше времени правило просто не к чему применить.

NEW-0812-16: идентификатор операции выкладки и сверка исхода постфактум

Выкладка приложения на сервер теперь выдаёт идентификатор операции ДО начала работы, а не в конце. Он приходит заголовком ответа X-Vibe-Operation-Id в обоих режимах, в потоковом режиме дополнительно первым событием operation (браузерный EventSource заголовков не отдаёт), и дублируется полем operationId в конверте — и при успехе, и при отказе.

По этому идентификатору исход выкладки читается отдельным запросом: GET /v1/infra/operations/:operationId. Он отвечает статусом running, succeeded, failed либо unknown (процесс прервался между стартом и записью исхода), шагом, временами старта и финиша и кодом ошибки. Ручка только на чтение, побочных действий у неё нет.

Зачем это нужно: раньше исход выкладки существовал только внутри самого ответа, поэтому при обрыве соединения узнать, чем всё кончилось, было нечем — оставалось запускать выкладку заново, не зная, сработала ли первая. Теперь идентификатор выдаётся до начала долгой работы, и оборванный вызов можно сверить.

Идентификатор выдаётся только при выкладке на отдельную виртуальную машину (kind: "STANDALONE"). У galaxy-приложения (kind: "GALAXY_APP") его нет ни в заголовке, ни в кадре, ни в теле ответа, и сверка исхода постфактум для таких выкладок пока не поддержана.

Исходы хранятся 7 суток и доступны по идентификатору каждый в отдельности, включая предыдущие попытки на том же сервере. По истёкшему идентификатору ответ отличается от «не найдено» — приходит 410 с кодом OPERATION_OUTCOME_EXPIRED, то есть «операция была, исход больше не хранится». Списка операций пока нет.

Отсутствие заголовка НЕ означает, что выкладка не запускалась: если платформа не смогла завести запись, выкладка продолжается без идентификатора. Читать отсутствие заголовка как отказ и запускать вторую выкладку поверх первой нельзя.

NEW-0812-17: Провалившийся деплой сообщает исход машинно, а не прозой

Деплой отдельной виртуальной машины мог закончиться так: приложение выложено и работает, проверка работоспособности отвечает ошибкой, а платформа уже вернула приложение с ограниченных прав на полные. Отличить это от «деплой не состоялся» можно было только по английскому тексту внутри error.message, а машинный потребитель читает поля.

Теперь шаг healthcheck в data.steps[] несёт три факта о проверке: httpCode — код последней попытки (поле отсутствует, если кода не было; ноль или null вместо него не приходят), healthPath — путь, который фактически проверяли, и portOwner — service, foreign либо unknown. Это факты о попытке, а не вердикт: шаг со статусом error законно может нести httpCode: 200, потому что исход определяют success и статус шага.

Шаг hardening теперь приходит и при провальном деплое — раньше он был только при успешном. Он несёт поле hardeningRollback: completed — возврат завершён, приложение работает с правами администратора; incomplete — возврат начался и не завершился, приложение может по-прежнему работать с ограниченными правами. То же значение продублировано в конверте ошибки как error.hardeningRollback. Поле описывает возврат юнита и владения каталогом развёртывания, и только их; его отсутствие означает «возврат не запускался», а не «ограничение прав не применялось» — на второй вопрос отвечает шаг service_user.

Все поля необязательные и аддитивные: прежние вызовы работают без изменений.

2026-08-11

NEW-0811-1: тарифы серверов называют единицу цены

В ответе GET /v1/infra/providers/{providerId}/plans у каждого тарифа появилось поле currency со значением "Vibes" — единица, в которой считаются priceMonthly и sleepPriceMonthly. Раньше цены приходили безымянными числами, и единицу приходилось брать из текста документации; теперь это та же машинная пометка, что у стоимости поиска в GET /v1/me.

Поле аддитивное: прежние вызовы работают без изменений, числа и их смысл не поменялись. Каталожная цена по-прежнему может отличаться от фактического списания за конкретный портал.

NEW-0811-2: каталог данных приложения объявляется в теле деплоя

Приложение на отдельной виртуальной машине запускается под непривилегированной учётной записью, и платформа передавала ей во владение только каталог распаковки. Каталог состояния вне него — тот самый /opt/data, который наша документация советует для данных, переживающих выкат, — создаётся администраторскими шагами install и preStart, поэтому оставался за администратором, и первая же запись из приложения падала с ошибкой доступа. Обойти это можно было только ручной раздачей прав в preStart на каждом деплое.

Было

Приложение писало только в свой каталог распаковки. Для каталога состояния декларативного способа не было.

Стало

В теле POST /v1/infra/servers/{id}/deploy появились два необязательных поля. dataDirs — до восьми каталогов вне каталога распаковки, которые платформа создаёт и передаёт учётной записи приложения на каждом деплое, а при откате возвращает обратно. Путь — абсолютный, нормализованный, внутри /opt, /srv или /var/lib; сами корни отвергаются новым кодом INVALID_DATA_DIRS до того, как деплой займёт сервер. dataDirsRecursive дополнительно передаёт содержимое объявленных каталогов — нужно только для заранее развёрнутого дерева; для /opt/data не принимается, потому что там по нашему же рецепту восстановления базы лежит файл с паролем.

Передаётся сам каталог, а не его содержимое: файлы, положенные администратором раньше, владельца не меняют. Владелец каталога может удалять и заменять файлы внутри него, поэтому скрипты, запускаемые от администратора, и учётные данные держите в необъявленном каталоге.

Прежние вызовы работают как раньше: без этих полей ни один каталог не создаётся и владельца не меняет.

NEW-0811-3: выпуск ключа ровно с выбранными платформенными правами

Тело POST /v1/keys принимает необязательное поле exactScopes. С exactScopes: true ключ сохраняет ровно те права, что перечислены в scopes: четыре платформенных (vibe:infra, vibe:ai, vibe:search, vibe:storage) не дописываются при выпуске, и vibe:ai / vibe:search не добавляются к правам запроса на лету. Поэтому GET /v1/me возвращает ровно сохранённый набор, а ключ без vibe:infra отвечает 403 INFRA_SCOPE_REQUIRED на POST /v1/infra/servers.

Умолчание не изменилось: без поля к запрошенным правам по-прежнему добавляются четыре платформенных, поэтому уже написанные скрипты работают как раньше.

Ключи, выпущенные в кабинете, теперь тоже точные — снятая галочка платформенного права означает, что права у ключа нет. Раньше набор Вайбкод дописывался безусловно, и сузить права можно было только правкой ключа после выпуска.

NEW-0811-4: выгодные часы видны в подписке Cowork/Code и в отказе по исчерпанной квоте

В отдельные часы недели квота расходуется медленнее — тот же вызов забирает меньшую долю лимита. Раньше об этом можно было узнать только из расписания выгодных часов, теперь то же самое видно в ответах подписки Cowork/Code и в отказе по исчерпанной квоте, поэтому приложение может предложить перенести объёмную задачу на выгодный час, не собирая расписание само.

Ответ Что появилось
GET /v1/off-peak currentWindowEndsInHours — через сколько часов расход перестанет быть таким же выгодным. null, если в пределах недели вперёд дороже не станет
GET /v1/cowork/me Блок offPeak — действует ли скидка сейчас, какой множитель расхода и когда наступит ближайший выгодный час. Без сетки часов
GET /v1/cowork/state Тот же блок offPeak вместе с сеткой часов на неделю и поле touSavedPct — какую долю расхода за текущий расчётный период сняли выгодные часы
Отказ 402 cowork_quota_exhausted на POST /v1/chat/completions offPeakHint — через сколько часов снимется блокировка (inHours) и какой множитель расхода будет действовать в этот момент (multiplier). Приходит, только когда этот момент попадает в час со скидкой

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

Множитель — коэффициент расхода, а не размер скидки. Значение 0.5 означает, что вызов забирает половину той доли квоты, которую забрал бы без скидки.

Поля блока, примеры ответов и особенности — Выгодные часы в подписке Cowork/Code.

FIX-0811-5: ближайший выгодный час отсчитывается от границы часа, а не от минуты запроса

Было

Поле nextWindow.inHours в ответе GET /v1/off-peak отсчитывалось от момента запроса. Расписание почасовое, поэтому ответ «через 2 часа», полученный в 10:55, указывал на 12:55 — на середину выгодного часа, начавшегося в 12:00. Клиент, прибавивший это число к моменту запроса, попадал в дешёвый час, когда большая его часть была уже позади. Так же вёл себя тот же отсчёт в карточке выгодных часов в кабинете (GET /api/ai/tou).

Стало

Отсчёт идёт от границы текущего часа в часовом поясе расписания. То же «через 2 часа», полученное в любую минуту между 10:00 и 11:00, указывает на 12:00 — на начало выгодного часа. Поле стало тем, чем его описывает документация, — ближайшим часом, который дешевле текущего. Расхождение с прежним прочтением не превышает одного часа. То же исправление действует в карточке выгодных часов в кабинете.

Влияние на интеграторов

Менять ничего не нужно, форма ответа и тип поля прежние. Планировщик, который прибавляет inHours к моменту запроса, теперь стартует в начале выгодного часа, а не в его середине, и момент старта может сдвинуться не больше чем на час.

BC-0811-6: машина, у которой не загружается операционная система, отвечает отдельным кодом

Поддержка старого формата до: не предусмотрена

Было

Сервер, чья гостевая операционная система не стартует после прерванного обновления, выглядел как обычный «временно не на связи». Ремонт по нему запускался снова и снова и каждый раз возвращал сообщение про SSH-ключ, к которому поломка отношения не имеет, а POST /v1/infra/servers/:id/start отвечал 200 и поднимал машину, которая всё равно не загрузится. Отличить безнадёжную машину от временно недоступной по ответам API было нельзя.

Стало

Такая машина получает машинную метку provisionErrorCode = "GUEST_NOT_BOOTING" в ответах GET /v1/infra/servers/:id и списка серверов. POST /v1/infra/servers/:id/repair и POST /v1/infra/servers/:id/start отвечают 422 с кодом GUEST_NOT_BOOTING вместо заведомо бесполезной работы, в availableActions остаётся только delete, а GET /v1/me в блоке infra.unhealthyServers советует пересоздание вместо ремонта. Начисление за такую машину закрыто в момент вердикта.

Что делать интеграторам

Проверяйте provisionErrorCode перед запуском и ремонтом: значение GUEST_NOT_BOOTING терминально, повтор не поможет, сервер нужно пересоздать. Клиент, который вызывает start по расписанию, получит на такой машине 422 вместо прежнего 200 — обработайте этот код как «пересоздать», а не как временную ошибку. Прежнее поведение не сохраняется: 200 означал запуск машины, которая всё равно не загружается, и продолжал начисление за неё.

NEW-0811-7: у ответа «приложение не отвечает» появился отдельный код для сервера без выкладки

Было

Когда туннель до сервера открыт, а приложение не отвечает, машинному вызывающему всегда приходил один и тот же ответ 503 с кодом BH_APP_STARTING и заголовком Retry-After. Сервер, на который выкладки никогда не было, был неотличим от сервера, где приложение упало или слушает не тот порт, и повтор по таймеру выглядел осмысленным там, где ждать было нечего.

Стало

Если платформа не видела ни одной выкладки на этот сервер, ответ приходит с кодом BH_APP_NOT_DEPLOYED и без заголовка Retry-After — повторять по таймеру бессмысленно, нужна выкладка либо проверка адреса. Прежний BH_APP_STARTING с Retry-After остаётся для случая, когда выкладка была: там приложение действительно может подняться само. Статус ответа в обоих случаях 503, поэтому обработка, не разбирающая код, работает как раньше.

FIX-0811-8: создание шаблона документа объявляет требуемый скоуп в openapi.json

Было

У операции POST /v1/doc-templates в машинной спецификации не было поля x-required-scope, хотя рантайм отвечает 403 SCOPE_DENIED без скоупа documentgenerator. Клиент или агент, собиравший ключ по спецификации, видел «скоуп не нужен» и получал отказ на первом же вызове. Эта же операция попадала в любой срез openapi.json?scope=, включая срезы других модулей.

Стало

Операция объявляет x-required-scope: documentgenerator, как остальные операции модуля. В срезах она остаётся только при openapi.json?scope=documentgenerator и в полной спецификации.

Влияние на интеграторов

Ничего менять не нужно: поведение самого эндпоинта не изменилось. Клиенты, которые выводят набор скоупов из спецификации, теперь запросят documentgenerator сразу и не упрутся в 403.

FIX-0811-9: Пользовательские поля реквизитов в ответах

Было

crm.requisite.get не возвращал пользовательские поля реквизита в ответах V1.

Стало

После успешного crm.requisite.get сервис точечно добирает пользовательские поля реквизита и сохраняет успешный ответ, если этот дополнительный вызов недоступен.

NEW-0811-10: granted_scopes — фактические права выданного ключа в ответе обмена

POST /v1/connect/token отдаёт новое поле granted_scopes — набор прав, который реально несёт выданный API-ключ. Поле scopes не изменилось: там по-прежнему то, что приложение запросило при авторизации. Обычно наборы совпадают, но у приложений, которым права проставляет платформа, они расходятся — например при входе по коду устройства настольное приложение Cowork не просит ничего, поэтому scopes приходит пустым, а granted_scopes перечисляет весь набор ключа. Поле необязательное и может отсутствовать, если платформа не смогла прочитать права выданного ключа. Пустым оно не приходит, поэтому отсутствие означает «набор неизвестен», а не «прав нет». Прежние вызовы работают без изменений.

FIX-0811-11: OpenAPI: обязательные скоупы для bespoke-операций

Было Машинный OpenAPI-контракт не содержал x-required-scope для bespoke-операций, хотя их обработчики уже проверяли скоуп до вызова Bitrix24.

Стало Контракт публикует проверяемый обработчиком скоуп для операций CRM, bots, calls, chats, lists, note, notifications, posts, scrum, tasks, timelines, userfields, warehouses, workday и workflows. Прокси скачивания с двумя допустимыми скоупами оставлен без одиночной аннотации, потому что она была бы неточной.

FIX-0811-12: справочник /v1/guide отдаёт ссылку на документацию для сущности telephony-lines

Было

В ответе GET /v1/guide сущность telephony-lines приходила без поля docs. Страница с описанием линий существовала, но найти её по ответу справочника было нельзя.

Стало

Сущность telephony-lines содержит docs со ссылкой на раздел телефонии /docs/telephony/lines. Остальные поля ответа не изменились, действий на стороне клиента не требуется.

FIX-0811-13: запись с Content-Type: text/plain теперь отклоняется, а не создаёт пустую запись

Было

Запрос на создание или изменение (POST/PATCH) с заголовком Content-Type: text/plain и телом, не являющимся JSON, принимался: тело молча отбрасывалось, а запись всё равно выполнялась — POST создавал пустую сущность с кодом 201, PATCH тихо ничего не менял. Любой другой не-JSON тип уже отвечал 415.

Стало

Такой запрос отвечает 415 Unsupported Media Type, как и прочие не-JSON типы; пустая запись не создаётся. Отправляйте тело как application/json.

BC-0811-14: деплой не рапортует успех, когда порт остался за прежним процессом

Поддержка старого формата до: не предусмотрена

Было

Деплой на POST /v1/infra/servers/{id}/deploy мог ответить success: true и healthcheck: ok, хотя порт приложения остался за процессом, который занимал его ещё до начала деплоя. Проверка живости получала 200 от него, а не от нового приложения, поэтому по публичному адресу продолжала отдаваться прежняя версия. Шаг stop_existing при этом честно предупреждал, что владелец порта не связан с новым приложением, но деплой шёл дальше и завершался успехом.

Стало

Если проверка живости получает 200, а порт при этом занят процессом, который был на нём ещё до начала этого деплоя, шаг healthcheck завершается ошибкой. В тексте ошибки указан владелец порта. Ответ становится success: false, у шага — status: "error", в потоковом режиме поток останавливается на том же шаге.

Деплои, где порт публикует процесс, запущенный самим этим деплоем, по-прежнему завершаются успехом — в том числе когда его поднимает шаг install или preStart, то есть до старта сервиса. Не меняется и случай, когда владельца порта определить не удалось: такой деплой, как и раньше, считается успешным.

Что делать интеграторам

Проверьте, не держит ли порт приложения процесс, который переживает деплои: обратный прокси, менеджер процессов, а также контейнер или systemd-юнит, поднятый один раз из шага install или preStart и не пересоздаваемый при следующих деплоях. Такая схема раньше отвечала success: true, а теперь будет отвечать success: false: платформа не может подтвердить, что по этому адресу отвечает именно новая версия. Отдайте порт приложения самому приложению, а свой процесс переведите на другой порт. Всем остальным менять ничего не нужно: форма ответа прежняя, а сценарий, который раньше получал success: true при неподнявшейся новой версии, теперь получит success: false с причиной.

NEW-0811-15: предрасчёт стоимости смены тарифа Cowork/Code

GET /v1/cowork/subscription/preview?tier=<FREE|PRO|MAX|ULTRA> возвращает сумму, которая будет списана прямо сейчас при переходе на указанный тариф: полную цену операции, возврат за неиспользованный остаток оплаченного месяца и итог к списанию. Скоуп vibe:cowork, ответ не кэшируется, ограничение — 30 запросов в минуту на пару «аккаунт и пользователь».

Экран подтверждения стоит строить на этом ответе, а не на цене тарифа из tiers[].feeVibes в GET /v1/cowork/state: цена там — ценник, и сумма списания уже сегодня отличается от него в четырёх случаях. Повторный выбор тарифа, который уже стоит, списывает ноль; понижение, поставленное в очередь на конец оплаченного периода, сейчас не списывает ничего; повторный запрос про такое понижение — тоже ноль; повышение с зачётом остатка месяца списывает цену за вычетом возврата. Пара netVibes: "0" и scheduled: true прямо отвечает на вопрос «спишется ли сейчас», поэтому эти правила не нужно повторять в клиенте.

Вайбы приходят десятичной строкой в основных единицах — так же, как баланс и движения по счёту. Рядом идут currency (код валюты пополнения по ISO 4217 либо null) и topUpAvailable; это про пополнение кошелька, а не про цену тарифа — денежной цены тарифа и курса вайба к деньгам ответ не содержит.

FIX-0811-16: сообщение об отказе установки приложения называет ответ портала

Было

Когда Битрикс24 отказывал в установке приложения по ключу разработчика и причина не попадала ни в один распознаваемый код, ответ нёс DEVKEY_MINT_FAILED и общий текст Failed to install app via developer key. По нему нельзя было отличить отказ по правам от недоступности REST или сетевого сбоя.

Стало

К тому же тексту дописывается наблюдаемое: Failed to install app via developer key (Bitrix24 answered HTTP 403 BITRIX_REST_V3_EXCEPTION_ACCESSDENIEDEXCEPTION). Код ошибки (error.code), статус ответа и поле userMessage не изменились, так что разбор ответа по коду продолжает работать без правок. У отказов с распознанной причиной — подписка, тариф, устаревший ключ — текст прежний. Тот же уточнённый текст видит человек в мастере создания приложения: предупреждение о том, что ключ авторизации не выпущен, теперь называет ответ портала.

Ответ портала дописывается только когда он был: на сетевом сбое, когда портал не ответил вовсе, текст остаётся прежним. Код отказа дописывается в машинном виде и не длиннее 64 символов.

FIX-0811-17: чтение с ключом только для чтения не отклоняется как запись

Было

Ключ только для чтения мог получить 403 WRITE_BLOCKED_READONLY_KEY при запросах, которые читают данные через методы Битрикс24 timeman.status, timeman.settings, calendar.event.getbyid, bizproc.workflow.instances, lists.get.iblock.type.id, lists.element.get.file.url, crm.type.getByEntityTypeId и crm.activity.call.getTranscript.

Стало

Ключ только для чтения пропускает эти операции чтения. Неизвестные методы по-прежнему считаются записывающими и блокируются.

BC-0811-18: отметка «прочитано» требует ключа с правом записи

Поддержка старого формата до: не предусмотрена

Было

Ключ в режиме «только чтение» мог отметить уведомления и сообщения прочитанными: POST /v1/notifications/read, POST /v1/chats/:dialogId/read и POST /v1/bots/:botId/chats/:dialogId/read выполнялись и меняли состояние портала — счётчик непрочитанного, статусы уведомлений. Проходили они потому, что право на запись определяется по имени вызываемого метода Битрикс24, а эти имена оканчиваются словом «read» и читались как чтение.

Стало

Все три операции под ключом «только чтение» отвечают 403 WRITE_BLOCKED_READONLY_KEY. Код добавлен в справочник ошибок каждой из трёх страниц. Ключ с правом записи работает как раньше.

Что делать интеграторам

Если сценарий отмечает прочитанным — выпишите или переключите ключ в режим с правом записи в разделе ключей кабинета. Чтение уведомлений и сообщений под ключом «только чтение» не изменилось. Подписка на события чатов (POST /v1/chats/events/subscribe и POST /v1/chats/events/unsubscribe) ключу «только чтение» по-прежнему доступна — это осознанное исключение, без которого агент в режиме чтения не смог бы следить за событиями.

FIX-0811-19: ещё пять читающих операций перестали отклоняться под ключом только для чтения

Было

Запись FIX-0811-17 сняла отказ у восьми читающих методов, но того же класса осталось ещё пять операций: GET /v1/tasks/:taskId/chat/messages, GET /v1/humanresources/nodes/:id/children, GET /v1/humanresources/employees/:id/subordinates, GET /v1/mail/messages/:id/thread и GET /v1/mail/mailboxes/:id/senders. Ключ в режиме «только чтение» отвечал на них 403 WRITE_BLOCKED_READONLY_KEY: право на запись определяется по имени вызываемого метода Битрикс24, а у этих операций оно оканчивается общим словом, и незнакомое имя трактовалось как запись. Ни одно обращение по ним не приходило — расхождение нашлось сверкой всего списка методов с их адресами.

Стало

Все пять отвечают ключу «только чтение» как обычное чтение. Отказ 403 WRITE_BLOCKED_READONLY_KEY остался на операциях записи в тех же разделах: отправка письма, правка оргструктуры, отправка сообщения в чат задачи.

Влияние на интеграторов

Менять ничего не нужно: запросы, которые раньше отклонялись, теперь выполняются. Если ради этих операций вы выписывали ключ с правом записи — верните ему режим «только чтение».

NEW-0811-20: состояние Cowork/Code рассказывает, как аккаунту открыть работу с Битрикс24

В ответе GET /v1/cowork/state появился блок activation. Он говорит, по какой модели работает регион аккаунта (model: subscription или tariff), даёт ссылку на условия тарифов Битрикс24 (tariffInfoUrl — только при model: tariff и только для облачного аккаунта) и заранее отвечает, стоит ли предлагать включение пробного периода Маркета: marketTrial.available и marketTrial.unavailableReason (trial_already_activated, subscription_active, demo_used, region_not_supported, not_cloud, portal_state, not_supported), плюс журнал наших попыток — status, endsAt, activatedAt.

Признак считается до попытки, поэтому шаг включения можно не показывать там, где он заведомо не сработает. Значение false окончательно, значение true означает «предлагать можно», но не гарантирует успех: состояние аккаунта может измениться между запросом и нажатием, поэтому обработку отказа на самом включении оставьте. Список причин может пополняться — неизвестное значение читайте как «активацию не предлагать». Самого блока activation в ответе может не быть вовсе: так отвечает платформа, которая про него ещё не знает, и это штатное состояние — проверяйте наличие блока, а внутри него читайте значение поля.

FIX-0811-21: у состояния Cowork/Code появился предел частоты опроса

Было

GET /v1/cowork/state принимал запросы без ограничения частоты, хотя документация рекомендует опрашивать его раз в 15–30 секунд.

Стало

Предел есть: он общий на аккаунт и владельца ключа, то есть все устройства одного человека делят его. Сверх предела эндпоинт отвечает 429 RATE_LIMITED.

Влияние на интеграторов

Клиент, соблюдающий рекомендованный интервал, предела не заметит: он расходует около двух запросов в минуту, а запас до потолка кратный. Если ваш опрос чаще, разредите его или обработайте 429.

FIX-0811-22: ссылка на повышение в ответе о возможностях больше не ведёт в никуда

Было

В GET /v1/me при отказе на создание сервера поле capabilities.servers.create.alternatives[].url (и тот же адрес в тексте userMessage) вело на страницу лицензии внутри самого аккаунта. Этот адрес различается между редакциями и версиями Битрикс24 и на части аккаунтов отдавал 404 — платформа отказалась от него в остальных ответах ещё в апреле, а здесь он остался.

Стало

Приходит устойчивый адрес: касса аккаунта там, где доступ открывается подпиской, и общая страница условий тарифов Битрикс24 там, где доступ тарифный. Заодно текст «оформите подписку» больше не показывается регионам, где подписки нет как продукта, — им остаётся общая формулировка и ссылка на условия тарифов.

Влияние на интеграторов

Ничего менять не нужно: поле осталось на месте и по-прежнему может отсутствовать. Не сохраняйте значение у себя и не разбирайте его домен — это адрес платформы, а не вашего аккаунта.

FIX-0811-23: пауза перед повтором в отказе ai_congested зависит от настроенной базы и ограничена сверху

Было

Когда платформа ограничивала поток AI-запросов, POST /v1/chat/completions, POST /v1/embeddings и POST /v1/audio/transcriptions отвечали 429 с кодом ai_congested, заголовком Retry-After и полем retryAfter в теле. Названная пауза почти не разбрасывалась — соседние отказы приходили с близкими значениями, — и верхнего предела у неё не было.

Стало

Пауза разбрасывается шире и может прийти длиннее прежней; сверху она ограничена — не больше часа. Насколько именно — зависит от того, как платформа ограничивает поток в этот момент, поэтому единственное верное поведение прежнее: ждать ровно столько, сколько названо в ответе.

Заголовок Retry-After и поле retryAfter в теле по-прежнему несут одно и то же число.

Влияние на интеграторов

Менять ничего не нужно: форма ответа, код ai_congested и набор полей не изменились. Если ваш клиент предполагал, что пауза укладывается в базу плюс две секунды, — снимите это предположение и ждите столько, сколько названо в Retry-After. Это относится и к вызовам через устаревшие адреса /v1/ai/*, которые ведут к тем же обработчикам.

NEW-0811-24: у прерванного деплоя в галактике появилась машинная причина и признак повтора

Ответ 502 GALAXY_DEPLOY_INTERRUPTED теперь несёт два новых необязательных поля. Прежние клиенты продолжают работать без изменений: код ошибки, retryable и error.hint остались на месте.

error.subcause называет, что именно произошло, — раньше все прерывания выглядели одинаково и единственным советом было «повторите»:

  • http_window_exhausted — окно запроса кончилось раньше, чем платформа успела проверить хоть раз. Сборка вполне могла завершиться на хосте, и повторный запрос подтвердит это за секунды.
  • exec_channel_busy — канал команд хоста был занят до конца отведённого ожидания.
  • no_this_deploy_container — хост ответил, но здорового контейнера именно этого деплоя не нашлось.
  • tail_unreached — связь оборвалась, и чистого ответа до дедлайна не пришло.
  • source_fetch_interrupted — добывание архива с исходниками прервалось до начала сборки, приложение при этом не тронуто.

error.repeated становится true, когда одно и то же приложение прерывалось несколько раз за короткое окно. В этом случае error.hint перестаёт советовать простой повтор и предлагает сначала проверить, стартует ли приложение, а если оно заведомо рабочее — обратиться в поддержку с идентификатором сервера.

Заодно текст ошибки перестал утверждать, что хост доступен, в том случае, когда платформа его ни разу не спросила.

Затронутые эндпоинты: POST /v1/infra/servers/:id/deploy

FIX-0811-25: создание galaxy-приложения сообщает, что присланные provider, plan и region не применены

Приложение в галактике живёт контейнером на общем хосте: своей машины у него нет, поэтому провайдера, тариф и регион оно наследует от хоста. Так было и раньше, но ответ об этом молчал — присланные значения подменялись на хостовые без единого признака, и опечатка в идентификаторе тарифа выглядела как принятый запрос.

Было

POST /v1/infra/servers на портале, который размещает приложения в галактиках, отвечал 201, а в data.provider, data.plan и data.region возвращал значения хоста. Отличить «платформа взяла моё» от «платформа взяла чужое» по ответу было нельзя.

Стало

Тот же запрос по-прежнему отвечает 201 и по-прежнему наследует значения хоста — поведение не изменилось. Изменился ответ: рядом с data появляется запись в warnings[], если присланное значение расходится с действующим. Она называет разошедшиеся поля, показывает оба значения, предупреждает, что повторная отправка ничего не изменит, и указывает на placement со значением dedicated — способ получить машину с выбранными вами характеристиками. Совпавшие поля не упоминаются, поэтому корректный вызов остаётся без предупреждения. Слишком длинное или не похожее на идентификатор каталога значение описывается словами, а не возвращается обратно.

BC-0811-26: загрузка файла в приложение галактики отклоняется вместо записи на общий сервер

Поддержка старого формата до: 01.02.2027

Было

POST /v1/infra/servers/:id/upload принимал id приложения галактики (kind: GALAXY_APP) и записывал файл — но не в контейнер приложения, а на файловую систему общего сервера, где живут остальные приложения аккаунта. Ответ был 200, поэтому промах выглядел как успех.

Стало

Тот же вызов отвечает 400 GALAXY_APP_USE_GALAXY_ROUTE — как это уже делают exec, logs и deploy для приложений галактики. Ответ называет причину: запись шла на общий сервер, а не в контейнер.

Что делать интеграторам

Файлы в приложение галактики доставляются пересборкой образа из исходников — source в теле POST /v1/infra/servers/:id/deploy. Загрузка в отдельный сервер (kind: STANDALONE) не изменилась.

FIX-0811-27: приложение из каталога Битрикс24 открывается сотруднику без учётной записи Вайбкод

Было

Приложение, открытое из раздела «Битрикс24.Вайбкод», запускалось только у тех сотрудников, у кого уже была личная учётная запись Вайбкод. Остальные получали экран «войдите в VibeCode и подключите этот портал» — даже когда приложение было выдано всему порталу. Из-за этого администратор, у которого учётная запись есть, видел рабочее приложение, а рядовые сотрудники — нет.

Стало

Учётная запись больше не является условием доступа. Приложение с политикой «весь портал» (а также «любой авторизованный» и «публичный доступ») открывается всем сотрудникам портала: членство подтверждает подписанная порталом форма, а платформа дополнительно спрашивает у Битрикс24, что это активный сотрудник, а не уволенный и не внешний гость. Именные политики (только владелец, конкретные пользователи, отделы) этим путём по-прежнему не открываются — они решают по конкретному человеку, поэтому там остаётся прежний экран со входом.

Что учесть при разработке приложения: у сотрудника без учётной записи заголовок X-Vibe-User-Role всегда приходит MEMBER, даже если на портале он администратор, а X-Vibe-User-Name — Unknown. Не завязывайте на эти заголовки необратимые решения; права проверяйте вызовом к Битрикс24.

BC-0811-28: у AI-запроса появился срок обслуживания: вместо зависания приходит отказ 429

Поддержка старого формата до: не предусмотрена

Было

Запрос к POST /v1/chat/completions, POST /v1/embeddings и их псевдонимам /v1/ai/* не имел объявленного верхнего срока. Обычный ответ упирался во внутренний предел ожидания и приходил обрывом соединения без тела, а у потокового ответа общего ограничения по времени не было вовсе: если кластер переставал присылать фрагменты, соединение висело до таймаута самого клиента. Отличить «ещё работает» от «уже не ответит» было нечем.

Стало

У запроса есть срок обслуживания. Не уложились — приходит 429 с телом {"error":{"code":"ai_deadline_exceeded","type":"rate_limit_exceeded","retryAfter":<секунды>}} и заголовками Retry-After и X-AI-Deadline-Ms (фактический бюджет запроса в миллисекундах). В потоковом ответе, где статус уже отправлен, то же тело приходит отдельным событием потока, за которым следует data: [DONE].

Клиент может попросить себе более короткий бюджет заголовком запроса X-AI-Deadline-Ms — значение в миллисекундах. Оно только сокращает срок: больше платформенного не выдаётся, и там, где срок отключён, заголовок его не включает. Нечисловое или неположительное значение считается неуказанным и вызов не отклоняет.

Срок применяется и к повторной попытке на резервной модели: отсчёт идёт от прихода запроса, а не от начала попытки.

Что делать интеграторам

Обрабатывайте 429 с кодом ai_deadline_exceeded как приглашение повторить: подождите указанное в Retry-After число секунд и отправьте запрос заново. Для потоковых вызовов добавьте разбор события с полем error — раньше поток такого события по этой причине не присылал. Если у вашего клиента свой предел ожидания, передавайте его в X-AI-Deadline-Ms: тогда отказ придёт от нас с объяснением, а не оборвётся у вас по таймауту.

FIX-0811-29: каталог регионов сервера на международной версии больше не отдаёт зоны недоступных регионов

Было

GET /v1/infra/providers/{providerId}/regions на международной версии платформы возвращал в списке региональные зоны, которые в этом сегменте недоступны для размещения, — они попадали в ответ вместе с доступными.

Стало

Список фильтруется по сегменту: на международной версии в ответе остаются только зоны, доступные для размещения в этом сегменте. Клиенту менять ничего не нужно — ответ стал корректным. На основной версии платформы список не изменился.

2026-08-10

NEW-0810-1: выдача ссылки-приглашения отмечается в журнале доступа портала

Ответ POST /v1/infra/servers/:id/access-tokens не изменился — меняется то, что происходит на стороне портала.

Было

Выдача ссылки с mode=share-url не оставляла следа в надзорном слое портала: администратор не видел, кто и когда открыл приложение ссылкой.

Стало

После успешной выдачи ссылки платформа записывает смену открытости в журнал портала, а если ссылка не требует входа в Битрикс24 (identityBound=false) и приложение до этого не было открыто наружу — отправляет администраторам портала сообщение в чат-бот со ссылкой на список открытых приложений.

Влияние на интеграторов

Формат запроса, ответа и коды ошибок не изменились — менять клиент не нужно. Учитывайте, что каждая выдача открытой ссылки теперь видима администратору портала.

BC-0810-2: ошибка фильтра в POST /v1/{entity}/batch стала ошибкой подвызова, а не всего запроса

Поддержка старого формата до: 04.02.2027

Было

Один неверный ключ фильтра в любом подвызове POST /v1/{entity}/batch отменял весь запрос: ответ 400, код в error.code, результаты остальных подвызовов отбрасывались. Глобальный POST /v1/batch вёл себя иначе — клал ошибку рядом с конкретным подвызовом и выполнял остальные.

Стало

Обе поверхности ведут себя одинаково. Ответ — 200, код отказа лежит в data[i].error.code для того подвызова, который его вызвал; остальные подвызовы выполняются и возвращают данные. Сам набор отказов и их коды не изменились — изменился только радиус: UNKNOWN_FILTER_FIELD, INVALID_FILTER_OPERATOR, INVALID_FILTER_FIELD, UNSUPPORTED_FILTER.

Прежний текст ошибки начинался с Call at index N: — теперь позиция подвызова видна по его месту в массиве data, и префикс убран.

Что делать интеграторам

Если код читает отказ фильтра как HTTP 400 с error.code, добавьте разбор 200 с data[i].error.code — иначе подвызов, у которого фильтр не принят, будет прочитан как успешный. Проверяйте наличие error у каждого элемента data, как это уже делается для глобального POST /v1/batch.

BC-0810-3: чтение через батч больше не обходит выключенную операцию сущности

Поддержка старого формата до: 04.02.2027

Было

Сущность может выключать отдельную операцию — обычно потому, что универсальный обработчик для неё неверен: настоящий список живёт на собственном маршруте с другим конвертом, либо метод Битрикс24 не понимает фильтр и отдаёт таблицу целиком. Одиночные маршруты это учитывали, запись через батч — тоже, а чтение через батч — нет. Поэтому POST /v1/{entity}/batch и POST /v1/batch с action list, get, search или fields отвечали 200 там, где та же операция на своём маршруте отвечает 404 — и отдавали ровно тот результат, ради отказа от которого операцию и выключили.

Отдельно: устаревший GET /v1/{entity}/aggregate вообще не спрашивал, есть ли у сущности агрегация. У сущности, где все числовые поля — идентификаторы, POST /v1/{entity}/aggregate отвечает 404, а этот адрес отвечал 200.

Стало

Оба батча отвечают на выключенную операцию 400 с кодом ACTION_NOT_SUPPORTED до вызова Битрикс24: в per-entity батче это ответ всего запроса, в глобальном — ошибка конкретного подвызова (остальные подвызовы выполняются). Устаревший GET /v1/{entity}/aggregate регистрируется по тому же признаку, что и POST-версия, поэтому у сущности без агрегации теперь 404 на обоих адресах.

Влияние на интеграторов

Затронуты только те сущности и операции, где ответ и раньше был неверным. Проверить стоит все четыре чтения — list, get, search, fields: у шести сущностей выключен именно search при работающем list, поэтому пачка, которая раньше проходила целиком, теперь вернёт ACTION_NOT_SUPPORTED на этом подвызове. Полный набор действий сущности — в operations.batch ответа GET /v1/guide и в описании OpenAPI; data.batch в GET /v1/{entity}/fields перечисляет только действия ЗАПИСИ и на вопрос про чтения не отвечает. У сущности, где выключено всё, маршрут остаётся на месте, но любое действие отвечает ACTION_NOT_SUPPORTED — отказ называет действие, чего 404 на адресе сказать не может.

BC-0810-4: `filter`, переданный не объектом, отклоняется вместо тихой потери

Поддержка старого формата до: 04.02.2027

Как и в соседней записи про неизвестное имя поля, «старый формат» здесь означает неверный ответ, а не рабочий.

Было

filter — объект условий, но передать вместо него строку, число, булево значение или массив никто не мешал. Строка и массив уходили в Битрикс24 как есть, число и булево превращались в пустой отбор — и во всех случаях запрос отвечал 200 со ВСЕЙ коллекцией:

POST /v1/tasks/search   { "filter": [{ "responsibleId": 1 }] }   →  200, все задачи портала

Стало

Такой запрос отклоняется до вызова Битрикс24 — 400 с кодом INVALID_FILTER_SHAPE; в сообщении сказано, что именно пришло вместо объекта, и показана правильная форма.

Действует на всех сущностях и на всех поверхностях, где filter приходит в теле: поиск, агрегация и оба батча.

Что делать интеграторам

Передавайте filter объектом. В строке запроса это отдельная история: там условия пишутся скобочной формой (?filter[responsibleId]=1), а filter, закодированный в JSON одной строкой, с этой же поставки не отклоняется, а разбирается и применяется — раньше он молча терялся и возвращал всю коллекцию.

BC-0810-5: неизвестное имя поля в фильтре отклоняется ещё на пятнадцати сущностях

Поддержка старого формата до: 04.02.2027

До сих пор такой фильтр отвечал 200 и всей коллекцией — то есть «старый формат» здесь означает неверный ответ, а не рабочий.

Было

Фильтр по имени, которого у сущности нет, уходил в Битрикс24. Битрикс24 такой ключ не отклоняет — он молча его выбрасывает и отвечает 200 со ВСЕЙ коллекцией. Опечатка в имени поля поэтому выглядела как успешный запрос с неправдоподобно большим результатом:

GET /v1/tasks?filter[responsable]=1     →  200, все задачи портала

Так вели себя задачи, пользователи, рабочие группы, реквизиты, банковские реквизиты, шаблоны реквизитов, адреса, сайты, страницы, комментарии таймлайна, шаблоны бизнес-процессов, настройки открытых линий и элементы универсальных списков.

Стало

Такой запрос отклоняется до вызова Битрикс24 — 400 с кодом UNKNOWN_FILTER_FIELD и списком доступных имён в сообщении — этот список и есть точный ответ на вопрос «по чему можно фильтровать».

Проверяется только имя: операторы, диапазоны, $in/$nin и логика И работают как раньше. Кроме объявленных полей принимаются пользовательские поля (UF_*, ufCrm*) и — у сущностей, где он объявлен, — ключ id.

Отдельно: у действий и роботов бизнес-процессов метод Битрикс24 не принимает фильтр ни в каком виде, поэтому там отклоняется любой ключ фильтра — код UNSUPPORTED_FILTER.

Полное описание — Фильтрация и поиск.

Что делать интеграторам

Сверьте имена полей в своих фильтрах со списком из сообщения об ошибке. Запрос, который раньше «работал», но возвращал больше записей, чем должен был, теперь ответит 400 с точным указанием, какое имя не найдено — это и есть его исходная ошибка. Остальные фильтры не затронуты.

FIX-0810-6: нехватка места на общем хосте — отдельный повторяемый отказ деплоя, а не поломка приложения

Было

Когда на общем хосте galaxy-приложения кончалось свободное место, сборка падала, и POST /v1/infra/servers/:id/deploy отвечал 502 GALAXY_APP_BUILD_FAILED — тем же кодом, что и ошибка в исходниках самого приложения. Приложение при этом помечалось сломанным, хотя работать не переставало: отказ происходил на сборке, до подмены контейнера, поэтому предыдущая версия продолжала отвечать на запросы. Отличить нехватку места от настоящей ошибки сборки можно было только по хвосту лога в buildLog, а повтор того же деплоя давал тот же результат.

Стало

Тот же случай возвращает 502 GALAXY_LOW_DISK с признаком retryable: true и структурированным полем error.hint: причина, что сделать и предупреждение не удалять слот. Приложение не помечается сломанным — слот, контейнер и его том с данными целы, а уже работающая версия обслуживает трафик дальше. Повторять деплой имеет смысл после того, как на хосте освободили место: само оно не освобождается, поэтому немедленный повтор упирается в тот же отказ. Новый код перечислен в машинном описании контракта деплоя, которое отдают GET /v1/me и GET /v1/openapi.json.

Влияние на интеграторов

Менять ничего не нужно: успешные деплои не затронуты. Ветку на GALAXY_APP_BUILD_FAILED оставьте — она по-прежнему приходит на настоящих ошибках сборки, а у нехватки места теперь есть отдельный код, по которому видно, что дело не в исходниках. Автоматический повтор бывает только там, где платформа сама пересобирает уже существующее приложение: там попытки идут без участия клиента, пока не истечёт отведённый на ожидание бюджет. При создании приложения сразу с исходниками отказ приходит сразу и не повторяется — решение о повторе остаётся за вами.

FIX-0810-7: Открытие из каталога Битрикс24 предупреждает, что приложение не авторизовано

Было

Приложение, открытое из каталога Битрикс24 пользователем, который ещё не выдал ему доступ, запускалось как обычно, но шлюз не проставлял заголовок X-Vibe-Authorization. Вызовы к API отвечали 401, и приложение показывало текст, который наша же документация предписывала для 401, — «откройте приложение из меню Битрикс24». Совет был тупиковым: пользователь именно так и открыл, а открытие из каталога доступ не выдаёт.

Стало

Такой запуск платформа перехватывает и показывает экран с кнопкой «Авторизовать приложение»; второй путь — один раз открыть приложение через встройку в меню Битрикс24. Там же есть неприметная ссылка «открыть без авторизации» на тот же адрес запуска — приложение, которому сессия не нужна, открывается как прежде. Экран показывается только там, где кнопке есть куда вести: облачный портал, ключ приложения и зарегистрированное OAuth-приложение. Во всех остальных случаях запуск идёт как раньше.

Влияние на интеграторов

Менять код не нужно, и ни один запуск не становится недоступным. Стоит поправить только текст своей ошибки на 401: у неё две причины — истёкшая сессия и не выданный доступ, — поэтому «откройте из меню» как единственная формулировка вводит пользователя в заблуждение. Рекомендация обновлена в разделе Среда выполнения приложения.

NEW-0810-8: список документов CRM появился в машинной схеме OpenAPI

Эндпоинт GET /v1/crm-documents теперь описан в схеме OpenAPI, которую отдаёт /v1/openapi.json. Сам вызов работал и раньше, но клиенты и ИИ-агенты, которые строят интеграцию по машинному описанию, считали его несуществующим. В описании указаны обязательный параметр entityTypeId, необязательные entityId, select, order и start, требуемый доступ crm, форма ответа с массивом документов и блоком meta с полями total, start и next, а также коды отказа MISSING_PARAMS, INVALID_ENTITY_ID, INVALID_START, TOKEN_MISSING и SCOPE_DENIED. Параметр размера страницы эндпоинт не принимает и в описании его нет: за следующей страницей идут со значением meta.next в start. Поведение самого эндпоинта не изменилось.

FIX-0810-9: при входе по прямой ссылке приложение получает имя и токен посетителя

Было

Приложение, открытое по прямой ссылке на свой адрес (а не плиткой из Битрикс24), получало имя посетителя из его учётной записи на платформе Вайбкод, а не из карточки сотрудника: участник нескольких Битрикс24 видел имя, под которым он записан в другом из них. Заголовок X-Vibe-Authorization на этом пути не приходил вовсе — приложение не могло обратиться в Битрикс24 от лица посетителя.

Стало

X-Vibe-User-Name и X-Vibe-User-Name-Encoded несут имя из карточки сотрудника того Битрикс24, которому принадлежит приложение — как и при открытии плиткой. Токен доступа X-Vibe-Authorization резолвится по номеру сотрудника, поэтому приходит и здесь.

Влияние на интеграторов

Формат заголовков не изменился, менять клиент не нужно. Если карточку сотрудника прочитать не удалось, имя остаётся прежним, из учётной записи на платформе Вайбкод: пустым заголовок не приходит.

FIX-0810-10: пользовательские поля счёта

Было

Пользовательские поля счёта нельзя было прочитать или создать ни одним путём. GET /v1/userfields/invoices отвечал ошибкой UNKNOWN_ENTITY, а GET /v1/items/31/userfields — ошибкой доступа от Битрикс24, потому что счёт адресовался так же, как обычный смарт-процесс.

Стало

Оба пути работают и дают одинаковый результат: шесть операций (список, справочник типов, чтение, создание, изменение, удаление) над пользовательскими полями счёта. Путь /v1/userfields/invoices добавлен для тех, кому удобнее обращаться по имени сущности, а не по числовому идентификатору типа.

Влияние на интеграторов

Менять ничего не нужно. Речь идёт о счетах в текущем виде; счета старого образца через API по-прежнему недоступны.

BC-0810-11: Справочник полей товарных позиций описывает то, что реально приходит в ответах

Поддержка старого формата до: не предусмотрена

Было

GET /v1/deals/{id}/products/fields отдавал набор полей Битрикс24 как есть, и он расходился с ответами самих товарных позиций сразу в трёх местах. Сумма скидки называлась в справочнике discountSum, а в данных и при записи — discount. Внешний код позиции и цена в валюте отчёта приходили в каждой строке, но в справочнике отсутствовали. Владелец строки, тип владельца и склад, наоборот, были в справочнике, но из ответов вырезались.

Хуже того, клиент, сгенерировавший запись по этому справочнику, отправлял discountSum — обёртка это имя не распознавала, отвечала 201 Создано и молча выбрасывала скидку. То же происходило с любым другим неизвестным полем: ответ об успехе, данные не записаны.

Стало

Справочник собирается из тех же таблиц, по которым формируются сами ответы, поэтому разъехаться они больше не могут. Скидка называется discount — как в данных, при записи и в документации. Прежнее имя discountSum при этом никуда не делось: оно осталось устаревшим псевдонимом, по-прежнему приходит в справочнике и по-прежнему принимается при записи, поэтому код, написанный по старому списку полей, продолжает работать — с той разницей, что скидка теперь действительно записывается, а не теряется молча. В самих товарных позициях приходит только discount. Добавлены priceAccount и xmlId, которые Битрикс24 возвращает в строках, но в своём справочнике не описывает. Поля ownerId, ownerType и storeId теперь приходят в ответах списка и одной позиции; storeId равен null, если складской учёт выключен.

Признаки isReadOnly и isRequired описывают контракт этого API, а не контракт Битрикс24: ownerId, ownerType, customized и measureName помечены только для чтения (записать их через обёртку нельзя), а у ownerId и ownerType снят признак обязательности — они берутся из адреса запроса. У id появилось пояснение: как атрибут он только для чтения, а в элементах PUT /products он принимается. Уточнение 18.08.2026: живая проверка показала, что возврат id идентичность строки не сохраняет — позиция, поля которой не изменились, сохраняет свой id и без id в теле, а изменённая приходит с новым id. Правка позиции с сохранением идентификатора идёт через PATCH /v1/deals/:id/products/:rowId.

Запись с неизвестным именем поля больше не отвечает успехом: POST, PUT и PATCH возвращают 400 INVALID_PARAMS и перечисляют записываемые поля. Поля только для чтения по-прежнему принимаются и игнорируются, поэтому объект, прочитанный через GET, можно отправить обратно без чистки.

Поле id в теле принимается только в элементах PUT /products. При создании и правке оно отбрасывается: строку там задаёт адрес запроса, а тело с id уже существующей строки — ровно то, что получается, если отправить обратно объект, прочитанный через GET.

Признак taxIncluded при записи снова принимается булевым: true и false уходят в Битрикс24 как Y и N. Раньше булево значение отправлялось как есть, и налог по строке фактически оставался незаданным — то есть строка, прочитанная через GET и отправленная обратно, теряла этот признак. Кто обходил это, посылая "Y" и "N" строками, ничего не заметит: такая форма принимается по-прежнему.

Если Битрикс24 отдал неполные метаданные, справочник всё равно приходит полным, но ответ дополняется предупреждением meta.warnings[].code = "fields_partial" — раньше такой ответ был неотличим от нормального. Текст предупреждения различает два случая: метаданных не пришло вовсе или не описана только часть полей (тогда он их перечисляет).

Что делать интеграторам

Ломается запись с посторонним ключом в теле. Проверьте, что при создании и правке товарной позиции вы не отправляете ничего сверх записываемых полей: своих служебных пометок, остатков внутренней модели, имён Битрикс24 в верхнем регистре (PRICE_ACCOUNT, XML_ID). Раньше такой запрос отвечал 201 Создано и молча выбрасывал значение — теперь он отвечает 400 INVALID_PARAMS и перечисляет, что принимается. Окна поддержки у прежнего поведения нет намеренно: оно и было дефектом, из-за которого заявку завели, а держать его параллельно значит держать тихую потерю данных. Полный список записываемых полей приходит в ответе на отказ и в справочнике полей.

Остальное менять не нужно. discountSum продолжает и приходить, и приниматься — перейти на discount можно без спешки, это имя приходит в самих товарных позициях, тогда как псевдоним живёт только в справочнике. Ветвитесь на isReadOnly или isRequired — сверьтесь с новыми значениями у ownerId, ownerType, customized и measureName. Заменяете строки целиком — учтите, что идентификаторы изменённых позиций меняются: перечитайте набор через GET после записи.

FIX-0810-12: подбор сотрудников по серверу больше не пустует из-за свежего ключа без доступа к Bitrix24

Было

Когда управляющий ключ сервера не давал доступа к Bitrix24, GET /v1/infra/servers/{id}/b24-users и подбор сотрудников в интерфейсе переходили к личным ключам владельца сервера, но проверяли только один — самый свежий подходящий. Если у него не было ни вебхука, ни токена установленного приложения, ответ приходил пустым (data: [] с подсказкой), хотя у владельца был другой активный ключ с нужными правами. Ключи, которые платформа выпускает сама под задачи без обращений к Bitrix24, всегда оказываются самыми свежими, поэтому подбор мог не работать постоянно.

Стало

Проверяются все подходящие личные ключи владельца, от свежего к старому, и используется первый, для которого действительно есть доступ к Bitrix24. Ключи без доступа пропускаются, к Bitrix24 по-прежнему уходит один запрос. Требования к ключу не смягчены: как и раньше, годится только активный неистёкший личный ключ владельца сервера в том же портале, не привязанный к приложению.

Заодно исправлена подсказка в поле hint: раньше она называла только неавторизованное приложение и отозванный ключ, из-за чего активный ключ выглядел отозванным. Теперь третьей причиной названо отсутствие доступа к Bitrix24 у ключей и подсказано действие — выпустить личный ключ с нужными правами. Форма ответа не изменилась.

FIX-0810-13: привязка места встраивания называет отказ в правах установки вместо общей ошибки шлюза

Было

Битрикс24 отказывал в установке встройки, и проверить состояние подписки в этот момент не удавалось — POST /v1/placements/bind отвечал 502 BITRIX_UNAVAILABLE и складывал код Битрикс24 в details. Названного кода в ответе не было, а вместе с ним и подсказки, что делать дальше. Отказ приходил и на аккаунт с оформленной подпиской.

Стало

Когда проверку выполнить не удалось, а аккаунт уже числится подписанным в Вайбкод, тот же отказ приходит как 403 B24_EMBEDDING_INSTALL_DENIED с details.remedy равным install-rights. Ссылка на оформление не передаётся: подписка есть, не хватает права ставить локальные приложения. Если действующей подписки нет ни по одному из источников, ответ остаётся 502 BITRIX_UNAVAILABLE.

Влияние на интеграторов

Менять ничего не нужно. Ветка обработки 502 BITRIX_UNAVAILABLE продолжает работать для остальных отказов, а этот сценарий уходит из неё в 403. Повторять запрос в нём бессмысленно — нужно, чтобы ключ разработчика принадлежал пользователю с правом ставить локальные приложения и с доступом к приложению.

BC-0810-14: команды, выкладка и загрузка файлов по id машины-галактики больше не выполняются

Поддержка старого формата до: 07.08.2026

Было

Галактика — это одна машина, на которой в контейнерах живут приложения нескольких ключей одного аккаунта Битрикс24. Список серверов отдаёт как сами приложения (kind=GALAXY_APP), так и несущую их машину (kind=GALAXY), и вызов POST /v1/infra/servers/:id/exec, POST /v1/infra/servers/:id/deploy или POST /v1/infra/servers/:id/upload с id несущей машины выполнялся на ней самой — то есть за пределами контейнера вызывающего.

Стало

Тот же вызов с id машины вида GALAXY отвечает отказом: команды — 403 GALAXY_HOST_EXEC_FORBIDDEN, выкладка и загрузка файлов — 400 GALAXY_HOST_NOT_A_DEPLOY_TARGET. Текст отказа называет замену: работать с приложением по его собственному id (kind=GALAXY_APP). Для машин вида STANDALONE и для приложений галактики ничего не изменилось.

Что делать интеграторам

Возьмите из списка серверов строку своего приложения (kind=GALAXY_APP) и обращайтесь по её id. Если сценарий опирался на доступ к самой машине ради свободного места, эта величина теперь доступна как данные, а не как результат команды.

Окна поддержки прежнего поведения нет: отказ действует с момента выката. Причина в том, что прежнее поведение открывало доступ к машине, на которой стоят контейнеры других ключей, — держать такой доступ полгода ради совместимости нельзя.

NEW-0810-15: занятость диска машины-галактики видна в списке серверов

Строка машины-галактики (kind=GALAXY) в GET /v1/infra/servers и в карточке сервера теперь несёт четыре поля: diskTotalMb и diskFreeMb — размер и свободное место в мебибайтах, diskState — оценка (ok, warning, critical или unknown, если замера ещё не было), diskProbedAt — время замера в ISO-8601.

Обе границы оценки платформа держит с запасом: warning означает «место кончается», critical — что свободного места меньше, чем платформа считает безопасным; обе зажигаются раньше, чем выкладке действительно перестанет хватать места. Замер обновляется сам, пока машина не спит; у спящей показывается последнее известное значение вместе с его временем.

Для машин вида STANDALONE и для приложений галактики (kind=GALAXY_APP) все четыре поля равны null: у первых диск не измеряется, у вторых своего диска нет.

NEW-0810-16: агентская модель bitrix/bitrixgpt-5.6-agent

GET /v1/models отдаёт новую агентскую модель bitrix/bitrixgpt-5.6-agent с контекстом 1 048 576 токенов. Модель поддерживает потоковую выдачу, вызов инструментов (tools) и структурированный ответ по схеме — response_format с type: "json_schema". Публичный идентификатор модели входит в список ai.structuredOutputs.models в ответе GET /v1/me.

Модель добавлена в каталог и ничего не заменяет: прежние вызовы работают без изменений. Она включена в программу квоты, поэтому доступна и по партнёрскому токену Битрикс24.

Затронутые эндпоинты: GET /v1/models, POST /v1/chat/completions, GET /v1/me

NEW-0810-17: bitrix/bitrixgpt-5.5-agent помечена устаревшей

Ответы POST /v1/chat/completions на модели bitrix/bitrixgpt-5.5-agent теперь несут заголовки Deprecation: true, X-Model-Replacement: bitrix/bitrixgpt-5.6-agent и Link со ссылкой на преемника (rel="successor-version").

Модель продолжает работать без ограничений и остаётся в выдаче GET /v1/models. Дата отключения не назначена — заголовок Sunset не отдаётся, и менять в интеграции ничего не требуется. Преемник для новых интеграций — bitrix/bitrixgpt-5.6-agent.

FIX-0810-18: временный сбой транзакции базы больше не отдаёт 500 с внутренним кодом движка

Было

Если транзакция в базе закрывалась или истекала до конца операции, запрос отвечал 500 и клал в тело внутренний код движка — {"error":{"code":"P2028"}}. Кода нет ни на одной странице документации, заголовка Retry-After в ответе не было, и по ответу нельзя было понять, что запрос стоит повторить. Чаще всего это видели на DELETE /v1/apps/{id}: приложение оставалось на месте, а повтор выглядел бессмысленным.

Стало

Тот же класс отказа отвечает 503 с кодом DB_TRANSIENT, полем error.retryAfter и заголовком Retry-After — та же посадка на повтор, что у POOL_EXHAUSTED. Изменение при таком отказе не применяется ни частично, ни полностью, поэтому прямой повтор через несколько секунд безопасен. На маршрутах AI (/v1/ai/*, /v1/chat/*, /v1/audio/*, /v1/models) код приходит в нижнем регистре — db_transient — в конверте, совместимом с OpenAI. Внутренний код движка в теле ответа больше не встречается.

Влияние на интеграторов

Менять ничего не нужно. Если ваш обработчик считал 500 окончательным отказом — теперь этот случай приходит как 503 с указанным сроком и попадает в вашу ветку повторов. Отдельная обработка кода DB_TRANSIENT не требуется: достаточно уважать Retry-After на любом 503.

FIX-0810-19: preserveEnv не теряет .env, если выкладка упала после очистки каталога

Было

При cleanDeploy: true вместе с preserveEnv: true существующий .env читался до очистки каталога, но записывался обратно только на шаге env — уже после установки рантайма и зависимостей. Если выкладка обрывалась раньше, POST /v1/infra/servers/:id/deploy отвечал DEPLOY_FAILED, а каталог оставался без .env совсем. Настройки приложения приходилось заливать заново.

Стало

Сохранённая копия возвращается на диск при любом обрыве выкладки после очистки — на установке зависимостей, на рантайме, на загрузке архива, на самой очистке, а также при разрыве связи с сервером. Восстановление идёт по мере возможности и не добавляет отдельного шага в ответ. Правило приоритета не изменилось: переданный в этом же запросе env по-прежнему побеждает, а явный пустой env: {} означает «очистить» и сохранённую копию не возвращает. Успешная выкладка работает как раньше, включая подстановку часового пояса для расписаний пробуждения. Флаг по-прежнему только для отдельной виртуальной машины (kind: "STANDALONE").

Влияние на интеграторов

Менять на своей стороне ничего не нужно. Форма ответа DEPLOY_FAILED и набор шагов в data.steps не изменились. Если вы обходили этот дефект — заливали .env вручную после каждой неудачной выкладки — обходной путь больше не нужен.

2026-08-09

BC-0809-1: meta.total в списках больше не приходит по умолчанию

Поддержка старого формата до: 09.02.2027

Было

GET /v1/{entity} и POST /v1/{entity}/search присылали meta.total — количество записей под фильтр — если запрос не отказался от подсчёта явно. Отказаться можно было параметром withTotal=false или настройкой totalDefault на ключе, но умолчание платформы означало «считать», поэтому интеграция, которая про подсчёт ничего не знала, получала число всегда.

Стало

Платформенное умолчание сменилось на «не считать»: подсчёт заказывается явно. Попросить его можно тремя способами, они перекрывают друг друга в этом порядке: параметр запроса withTotal=true (у POST /v1/{entity}/search — поле тела "withTotal": true), настройка totalDefault на API-ключе, платформенное умолчание.

Если количество не попросили, наличие meta.total определяется формой вызова:

Вызов meta.total
limit не больше 50, offset 0, страница короче запрошенного приходит, точное число — в том числе 0
limit не больше 50, страница полная либо offset больше нуля не приходит
limit больше 50 приходит

Короткая страница доказывает количество сама, поэтому точное число приходит бесплатно и подсчёт не заказывается. На вызове с limit больше 50 подсчёт нужен платформе, чтобы спланировать обход, поэтому число приходит как раньше — передавать туда withTotal=false ради экономии незачем: параметр уберёт число, а не стоимость.

Явный withTotal=false убирает ключ на любом из этих вызовов: за этим параметром обещание «поля не будет» остаётся безусловным. Настройка totalDefault на ключе и платформенное умолчание точное число из короткой страницы не запрещают, поэтому два одинаковых запроса от двух разных ключей могут вернуть ответы разной формы.

Всё это относится к вызовам, на которых подсчёт можно пропустить. Там, где пропустить его нельзя, withTotal=false игнорируется и meta.total приходит как раньше. Проверяйте наличие поля в конкретном ответе, а не выводите его из настроек ключа.

Новое умолчание действует и на списочных вызовах внутри POST /v1/batch: там количество приходит в data.totals и meta по идентификатору вызова и отсутствует по тем же правилам.

Остальной ответ не изменился: data — те же записи в том же порядке, meta.hasMore на месте и по-прежнему говорит, есть ли ещё страницы.

Что делать интеграторам

Если код читает meta.total, выберите одно из двух.

Разово и на всю интеграцию — включите на API-ключе настройку totalDefault: страница ключей в кабинете или PATCH /v1/keys/:id с телом {"totalDefault": true} (управляющий ключ vibe_live_). Код менять не нужно.

Точечно — добавьте withTotal=true тем вызовам, которым количество действительно нужно: GET /v1/deals?withTotal=true, в теле поиска "withTotal": true.

Что действует на ваш ключ сейчас, показывает блок totalDefault в GET /v1/me: key — настройка ключа, platform — платформенное умолчание, effective — что получится, если запрос не передаст withTotal.

Отдельно: если meta.total использовался как граница цикла листания, переключитесь на meta.hasMore — так надёжнее в любом случае. А если нужна была именно цифра, спросите её прямо: POST /v1/{entity}/aggregate с функцией count отдаёт количество одним вызовом. Обходить коллекцию постранично ради счётчика не надо — это десятки вызовов вместо одного.

2026-08-07

NEW-0807-1: импорт записей CRM: автор и даты из внешней системы, без запуска автоматизации

Перенести уже существующую базу в CRM теперь можно одним запросом на сущность: POST /v1/leads/import и такие же маршруты у сделок, контактов, компаний, предложений, счетов и элементов смарт-процессов. До ста записей за раз, в теле — массив items, поля записи те же, что у обычного создания.

Импорт отличается от создания тремя вещами, и все три — свойства самой операции в Битрикс24, а не наши параметры. Он проверяет отдельное право «импорт», которое администратор портала выдаёт явно. Он не запускает роботов, триггеры и бизнес-процессы, настроенные на создание элемента. И он принимает служебные поля, которые обычное создание молча игнорирует: кто создал (createdBy), кто изменил (updatedBy), кто перевёл на стадию (movedBy) и соответствующие даты. Проставить их может только администратор портала — обычному пользователю Битрикс24 ответит отказом по такой записи, остальные записи пакета при этом создадутся. Набор доступных служебных полей у объектов разный; точный перечень отдаёт GET /v1/leads/fields — там они помечены importable.

У даты создания есть окно, которое задаёт Битрикс24: не позже текущего момента и не раньше, чем у самой свежей уже существующей записи этого объекта. То есть историю целиком получится перенести в пустую CRM или в такую, где всё старше переносимого; «подложить» записи задним числом в наполненную CRM Битрикс24 не даст. Если история не нужна, даты можно не передавать — автор проставляется и без них.

Ответ приходит со статусом 200 даже когда часть записей не прошла: импорт не транзакционен, поэтому исход читается по каждой записи в results[], а сводка лежит в summary. Всегда проверяйте summary.failed — статус говорит «запрос обработан», а не «всё создано». Повторный импорт создаёт дубли; если повторы возможны, записывайте идентификатор из внешней системы в originatorId и originId.

Маршрут ограничен по темпу на портал, чтобы массовая заливка не блокировала другие интеграции аккаунта. Импортируйте в один поток: внутри запроса порядок гарантирован, между параллельными запросами — нет, а Битрикс24 требует неубывающих дат создания.

Подробности, коды ошибок и примеры — на странице Импорт записей CRM.

FIX-0807-2: версия исходников выкладывается по номеру на своём сервере

Было

Версия, сохранённая через POST /v1/infra/servers/:id/sources, не выкладывалась на том же сервере: POST /v1/infra/servers/:id/deploy с телом { "source": { "versionId": "v1" } } отвечал 400 SOURCE_VERSION_REQUIRES_APP, если ключ-владелец сервера не привязан к приложению — то есть на личном ключе vibe_api_*. Обойти можно было только вручную: скачать архив по ссылке и выложить его формой { "source": { "url": … } }.

Стало

Если сервер принадлежит тому же ключу, которым идёт вызов, версия ищется в контексте этого сервера, и выкладка по versionId работает, включая личный ключ. Не нашлась на сервере, а ключ-владелец привязан к приложению — поиск повторяется в контексте приложения, как раньше. Не нашлась нигде — 404 SOURCE_VERSION_NOT_FOUND; текст ошибки теперь называет сервер и путь сохранения вместо приложения.

Код SOURCE_VERSION_REQUIRES_APP не удалён, а сузился: он приходит только там, где сервер принадлежит НЕ вызывающему ключу — менеджмент-ключ и доступ через карточку приложения. Если вы ветвитесь на этот код, ветку оставьте.

Влияние на интеграторов

Менять ничего не нужно. Вызов, который раньше отбивался, теперь проходит; поле sha256 в ответе сохранено.

Одно исключение, редкое: если на сервере и на его приложении лежат версии с ОДНИМ номером — так бывает, когда версия сохранена до перепривязки сервера на другой ключ, — теперь берётся версия сервера, а не приложения. Проверить, какая именно версия уехала, можно по полю sha256 в ответе.

FIX-0807-3: чтение и правка несуществующего дела отвечают 404, а не 422

Было

GET /v1/activities/{id} и PATCH /v1/activities/{id} с идентификатором, которого на портале нет, отвечали 422 с кодом BITRIX_ERROR и текстом вида Bitrix24 API error: 400. Причина внешняя: на этих двух методах Битрикс24 отдаёт отказ, в котором и код ошибки, и описание пустые, — распознать «записи нет» было не по чему. При этом DELETE /v1/activities/{id} на тот же идентификатор уже отвечал 404, потому что там портал текст ошибки присылает. Один и тот же несуществующий идентификатор давал два разных ответа в зависимости от глагола, и оба раздела документации — get и update — обещали 404.

Стало

Оба метода отвечают 404 с кодом ENTITY_NOT_FOUND и сообщением Activity is not found. — тем же, что и DELETE. Правило привязано к этим двум методам и срабатывает только когда портал не прислал ни кода, ни текста: отказ с любым кодом или сообщением (в том числе ошибка валидации при обновлении) остаётся как был. Список ошибок в документации не менялся — изменился ответ, который теперь ему соответствует.

FIX-0807-4: запрос без тела доходит до обработчика, а не падает на разборе

Часть HTTP-клиентов (axios, PowerShell Invoke-RestMethod, некоторые обёртки над fetch) подставляет Content-Type: application/x-www-form-urlencoded в каждый POST, PATCH и DELETE — даже когда тело не отправляется вовсе. Ещё часть не ставит Content-Type совсем. Обе формы до этой правки не доходили до обработчика.

Было

POST /v1/deals без тела и без заголовка Content-Type отвечал 500 INTERNAL_ERROR — обработчик падал на пустом теле раньше, чем успевал проверить, что создавать нечего. Тот же вызов с пустым телом и заголовком application/x-www-form-urlencoded отвечал 415 Unsupported Media Type ещё до проверки ключа. POST /v1/chats/events/subscribe — тело которому не нужно вовсе — отвечал 415 с формовым заголовком и 400 с пустым телом под application/json.

Стало

Пустое тело принимается независимо от заголовка: POST /v1/deals без тела отвечает 400 EMPTY_CREATE_BODY — тем же ответом, что и POST /v1/deals с телом {}; POST /v1/chats/events/subscribe без тела отрабатывает штатно.

Заголовок Content-Type с пустым телом теперь не мешает нигде на сущностях (/v1/deals, /v1/contacts, /v1/tasks и остальные генерируемые маршруты, включая пакетные и агрегирующие), в чатах (/v1/chats/*), в пользовательских полях (/v1/userfields/*, /v1/items/:entityTypeId/userfields), в базе знаний (/v1/note/*) и в ключах (/v1/portals, /v1/keys). Отдельный случай «заголовка нет совсем» — это другая поломка, и она закрыта на сущностях: там запрос без тела и без заголовка теперь получает обычный ответ проверки полей вместо 500.

Влияние на интеграторов

Менять ничего не нужно: запрос, который работал, работает так же. Непустое тело под незнакомым Content-Type по-прежнему отклоняется с 415 — тем же кодом, что и раньше; 413 приходит только если тело действительно больше допустимого размера. Тело с битым JSON под application/json теперь отвечает 400 INVALID_JSON_BODY на всех перечисленных маршрутах (раньше на чатах приходил код разборщика Fastify).

BC-0807-5: дополнение своего обращения больше не выглядит ответом платформы

Поддержка старого формата до: 07.08.2026

Было

Автором обращения считался конкретный ключ, которым оно создано. Комментарий, отправленный другим ключом того же владельца (или обращение, заведённое из кабинета и дополняемое ключом), уходил в платформенную ветку: записывался как authorType: PLATFORM, переводил обращение в AWAITING_USER и перезаписывал Feedback.resolution своим телом. Тем ключом, которым обращение создано, дополнить решённое обращение было нельзя — POST /v1/feedback/:id/comments отвечал 409 FEEDBACK_CLOSED. Единственным обходом было сменить статус через PATCH /v1/feedback/:id.

Кроме того, Feedback.resolution работал зеркалом последней реплики команды: любой комментарий со скоупом vibe:feedback перезаписывал текст решения, и вернуть прежнее значение было нечем. Лимита темпа у операции комментирования не было вовсе.

Стало

Автором считается владелец ключа. Обращение, созданное другим вашим личным ключом или заведённое из кабинета, для вас своё: комментарий записывается как authorType: USER, поле resolution не переписывается, а переданный status игнорируется. Одно условие: такому ключу нужен скоуп vibe:feedback — без скоупа своим считается только тот ключ, которым обращение и создано. Правило не распространяется на ключи приложений и управляющие ключи — там владелец ключа и тот, кто пишет, разные лица.

Комментарий автора к обращению в статусе RESOLVED возвращает его в работу (NEEDS_REVIEW) и снимает resolvedAt / resolvedBy; текст решения при этом сохраняется. Статусы ARCHIVED и WITHDRAWN остаются закрытыми и по-прежнему отвечают 409 FEEDBACK_CLOSED.

Поле resolution заполняет только комментарий, закрывающий обращение (целевой статус RESOLVED или ARCHIVED). При любом другом статусе поле не меняется, а текст комментария по-прежнему доходит до автора письмом и виден в ленте.

У операции комментирования появился лимит темпа — 20 комментариев в минуту, как у той же операции в кабинете. Счётчик общий на ВЛАДЕЛЬЦА ключа: несколько своих ключей делят один бюджет. Превышение — 429 RATE_LIMITED с заголовком Retry-After.

Что делать интеграторам

Правок требуют пять мест, и найти их в своём коде стоит до обновления.

  1. Обработчик 409 FEEDBACK_CLOSED. На решённом обращении теперь приходит 201, и обращение возвращается в работу. Если по этому коду вы решали «обращение закрыто, дальше не пишем», проверку надо перевести на статус из ответа: закрытыми остались только ARCHIVED и WITHDRAWN.
  2. Ветвление по authorType. Для второго ключа того же владельца значение сменилось с PLATFORM на USER. Код, который по PLATFORM рисует реплику как «ответ поддержки», начнёт показывать её как сообщение пользователя — и это верно, но если у вас на этой ветке висела логика, её надо пересмотреть.
  3. Чтение resolution. Как «последняя реплика команды» поле больше не работает: там лежит вердикт последнего закрытия, а на обращении, которое ни разу не закрывали, поле пустое. За последним ответом команды идите в ленту комментариев — последний элемент с authorType: PLATFORM.
  4. Закрытие своего обращения комментарием. Если вы закрывали своё обращение через POST /comments с полем status, этот способ больше не работает: у автора status игнорируется молча, ответ приходит 201, а статус остаётся прежним. Отзыв обращения — через PATCH /v1/feedback/:id с status: WITHDRAWN.
  5. Обработка 429 на комментариях. Операция получила лимит темпа, которого у неё не было. Если ваш код шлёт комментарии пачкой или в цикле — добавьте обработку 429 RATE_LIMITED с паузой по заголовку Retry-After. Бюджет общий на владельца ключа, поэтому выпуском второго ключа его не расширить.

Параллельная поддержка прежнего поведения не предусмотрена: прежнее заполнение resolution и было тем дефектом, ради которого правка сделана, — сохранять было бы нечего.

Затронутые эндпоинты: POST /v1/feedback/:id/comments, GET /v1/feedback/:id, GET /v1/feedback

BC-0807-6: сервер AI-агента больше не принимает выкладку приложения

Поддержка старого формата до: 07.09.2026

Было

POST /v1/infra/servers/{id}/deploy принимал архив на сервер, принадлежащий AI-агенту. Выкладка проходила, код агента затирался чужим приложением, и агент переставал отвечать. Той же машиной агент числился рабочим — ни в ответе, ни в интерфейсе следов не оставалось. По этой же причине сервер агента мог быть переиспользован под приложение при создании нового сервера с тем же именем и при повторной привязке приложения.

Стало

Выкладка в такой сервер отбивается 403 с кодом AGENT_SLOT_DEPLOY_FORBIDDEN; текст ошибки называет рабочую альтернативу — кнопку «Повторить» на карточке агента или создание отдельного сервера под приложение. Пути повторного использования сервер агента больше не выбирают: вместо перезаписи создаётся новый. Управляемые ботов это не касается — они выкладывают свой код через тот же вызов штатно.

Выписка ключа обслуживания на агента, чей сервер снесён, теперь отвечает 409 с кодом AGENT_SERVER_GONE вместо выдачи ключа, которым некуда идти. Чтение состояния ключа осталось 200, в теле появилось поле reason со значением SERVER_GONE.

NEW-0807-7: `GET /v1/cowork/state` сообщает о запланированном понижении тарифа

Было

Переход на более низкий тариф применялся сразу и обнулял оплаченный месяц, поэтому сообщать было не о чем: тариф в ответе менялся в тот же момент.

Стало

Понижение планируется на конец оплаченного периода, и объект subscription получил поле pendingTier — код тарифа, на который сиденье перейдёт при следующем списании, либо null. Дата перехода — уже имеющееся поле currentPeriodEnd.

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

FIX-0807-8: архив исходников галактического приложения добывает сама машина

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

Коды отказа и поля ответа прежние; добавился UPLOAD_NO_SPACE для нехватки места на диске. Неизвестный формат архива и ссылки, присланные в теле запроса, идут прежним путём. Затронуто POST /v1/infra/servers/:id/deploy.

Отдельно: extractTo теперь проверяется на стороне платформы, а не только на машине. Набор допустимых путей не изменился ни на POST /v1/infra/servers/:id/deploy, ни на POST /v1/infra/servers/:id/upload, где своей проверки не было вовсе — отвергается то же, что и раньше, но сразу и с внятным кодом INVALID_EXTRACT_TO.

FIX-0807-9: деплой больше не падает на остановке медленного приложения

Было

Повторный POST /v1/infra/servers/:id/deploy поверх работающего приложения, которое не завершается сразу по SIGTERM, падал на шаге stop_existing через ~45 секунд: DEPLOY_TIMEOUT, Deploy step timed out: GATEWAY_TIMEOUT: no response within 45s. Новая версия не выкатывалась. Платформа отводила остановке 15 секунд, а операционная система на сервере — до 90, поэтому приложение, которому на завершение нужно от 45 до 90 секунд, роняло обновление гарантированно. Подсказка при этом советовала ремонтировать туннель — ложный след.

Стало

Шаг stop_existing ждёт остановку столько, сколько ей отведено на сервере (лимит шага — 105 секунд), и больше не прерывает деплой: если остановку не удалось подтвердить — результат не пришёл ЛИБО сервер ответил, что остановить не смог, — шаг возвращает warning с честным текстом «исход неизвестен» и деплой идёт дальше. Раньше второй случай не показывался вообще: шаг отдавал ok, и о том, что остановка не удалась, узнать было негде. Сюда же попал соседний шаг очистки каталога: он тоже мог отдать ok, ничего не удалив, если сервер команду не выполнял, — и деплой падал двумя шагами позже с сообщением, которое причины не называло. Теперь такой случай останавливает деплой сразу и говорит причину. Когда одновременно занят порт, в предупреждении остаются оба факта. Ответ по-прежнему success: true, статус шага виден в data.steps[].

FIX-0807-10: переименование поля шаблона реквизитов проверяется до записи

Было

PATCH /v1/requisite-presets/:presetId/fields/:id с полем fieldName, которого нет среди доступных, отвечал 200 и {"updated": true}, а несуществующее имя реально сохранялось в строке шаблона. Метод обновления в Битрикс24, в отличие от метода добавления, не проверяет имя и принимает любую строку — поэтому строка шаблона оставалась с именем, за которым нет поля, и переставала отображать данные.

Стало

Если fieldName в теле запроса отличается от имени, которое строка уже несёт, имя сверяется со списком GET /v1/requisite-presets/:presetId/fields/available до записи. Имя вне списка — 400 с кодом INVALID_FIELD_NAME, обновление не выполняется; ни одно поле строки не меняется. Имя, занятое другой строкой того же шаблона, в списке доступных отсутствует и тоже отклоняется. Переименование в свободное имя проходит как раньше, а регистр имени приводится к написанию, которое вернул Битрикс24.

Влияние на интеграторов

Изменились три ответа. Мусорное имя вместо 200 даёт 400: прежний 200 записывал имя, за которым нет поля, поэтому терялись и остальные поля того же запроса — просто молча. fieldName, переданный не строкой, тоже даёт 400 INVALID_FIELD_NAME: раньше такое значение уходило в Битрикс24 и оседало в строке словом Array. Запрос с fieldName на несуществующую строку отвечает 404 до записи, а не ответом Битрикс24.

Запрос без fieldName работает как раньше. Запрос с тем же именем, что уже стоит в строке, тоже проходит, но стоит на один вызов к Битрикс24 дороже: чтобы понять, что имя не меняется, платформа сперва читает строку.

NEW-0807-11: справочник полей учёта времени задач

Добавлен GET /v1/task-time/fields — программный состав полей записи учёта времени. Для каждого из десяти полей отдаются тип, признак «только чтение», подпись и описание. Раньше состав полей был описан только текстом в документации, и клиент не мог получить его вызовом.

Схема одинакова для всех задач, поэтому путь плоский. Вложенный GET /v1/tasks/:taskId/time/fields по-прежнему возвращает 400 WRONG_PATH, но теперь называет в тексте ошибки правильный путь. Запрос требует скоуп task и не обращается к Битрикс24.

Числовые по смыслу поля — id, taskId, userId, seconds, minutes, source — объявлены строками, потому что именно строками они и приходят в ответах. Поле userId помечено createOnly: оно принимается при создании и отклоняется при обновлении.

Затронутые эндпоинты: GET /v1/task-time, GET /v1/task-time/fields.

FIX-0807-12: подписи и описания полей daysBeforeClose, fm и FILES в ответе /fields

Было

Три поля, которые Битрикс24 отдаёт живьём, приходили без пояснения, а два из них — ещё и с неудобной подписью. GET /v1/smart-processes/fields отдавал daysBeforeClose с подписью длиной в предложение вместо краткого названия. GET /v1/leads/fields отдавал fm с технической подписью «FM». GET /v1/timelines/fields отдавал FILES с подписью, но без описания, поэтому формат вложений приходилось искать на странице создания комментария.

Стало

У всех трёх полей есть description. У daysBeforeClose подпись сокращена до краткого названия, а прежний длинный текст перенесён в описание. У fm подпись заменена на понятную человеку, а описание отправляет к плоским полям phone и email, через которые те же данные удобнее читать и писать. У FILES подпись осталась той, которую прислал Битрикс24, — она зависит от языка портала, — а описание называет формат вложений на запись и на чтение.

Влияние на интеграторов

Менять ничего не нужно: тип поля (type) и признак «только для чтения» (readonly) по-прежнему приходят от Битрикс24 и не изменились. Если ваш код показывает пользователю значение label этих полей, текст станет другим — он берётся из ответа, а не хранится у вас.

FIX-0807-13: список обращений понимает фильтр в скобочной форме

Было

GET /v1/feedback?filter[status]=RESOLVED возвращал 200 и весь доступный список: скобочная форма фильтра разбиралась, но не читалась, поэтому и записи, и total приходили без фильтра. То же самое с filter[category]. Работала только плоская форма — ?status=RESOLVED.

Стало

Обе формы работают одинаково. GET /v1/feedback применяет filter[status] и filter[category] с той же проверкой, что и плоские параметры: регистр не важен, неизвестное значение отдаёт 400 INVALID_FILTER_VALUE вместо тихой выдачи всего списка. Если присланы обе формы, побеждает плоская — ответы на запросы, которые работали раньше, не меняются. Значение, которое не является одиночным (filter[status][]=NEW), тоже отклоняется с 400 INVALID_FILTER_VALUE.

FIX-0807-14: обновление заказа больше не теряет сумму, пометку и её причину молча

Было

PATCH /v1/orders/:id принимал price, marked и reasonMarked и отвечал 200, но Битрикс24 эти поля на обновлении не сохраняет. У marked и reasonMarked значение просто пропадало. У price было хуже: сумма пересчитывается из позиций корзины, поэтому запрос с ручной суммой её не менял, а при пустой корзине сохранённая сумма становилась 0 — то есть обновление, посланное с любым другим полем, обнуляло цену заказа. Ответ об этом не сообщал.

Стало

Все три поля отклоняются на обновлении с 400 READONLY_FIELD до обращения к Битрикс24 — на всех трёх поверхностях записи: одиночный PATCH, POST /v1/orders/batch с action: "update" и POST /v1/batch с action: "update". Создание не изменилось: POST /v1/orders и оба батч-создания по-прежнему принимают эти поля и передают их значения. В ответе GET /v1/orders/fields такое поле помечено readonlyOnUpdate: true, чтобы отличать его от readonly (нельзя и при создании) и от createOnly (значение неизменно после создания — про сумму заказа это неверно, её пересчитывает Битрикс24).

Влияние на интеграторов

Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что значение молча терялось. Если запрос на обновление посылал эти поля — уберите их из тела, иначе он начнёт отвечать 400. Чаще всего так делают клиенты, читающие заказ целиком и отправляющие объект обратно: из такого тела нужно убрать price, marked и reasonMarked. Чтобы изменить сумму заказа, меняйте позиции корзины.

NEW-0807-15: деплой сообщает, что не применил displayName или description

Поля displayName и description деплой заполняет, но не переименовывает: displayName записывается, только пока он ещё равен техническому идентификатору сервера, description — только пока оно пусто. Раньше присланное значение, конфликтующее с уже заданным, отбрасывалось молча — ответ приходил со статусом 200 и без единого признака, что поле не записано.

Теперь такой ответ несёт дополнительную запись в warnings[]: она называет отброшенные поля, подтверждает, что сам деплой прошёл, и предупреждает, что повтор ничего не изменит. Там же — готовое тело запроса к PATCH /v1/infra/servers/{id} с уже подставленным текущим именем: у этой ручки displayName обязателен, поэтому образец избавляет от случайной перезаписи имени при правке одного описания. Запись добавляется в конец массива, поведение записи в базу не изменилось.

Заодно операция переименования появилась в машинном описании API (GET /v1/openapi.json) — раньше схема утверждала, что переименования в этом API нет.

Затронутые эндпоинты: POST /v1/infra/servers/{id}/deploy, PATCH /v1/infra/servers/{id}

FIX-0807-16: переоткрытие обращения больше не стирает текст резолюции

Было

PATCH /v1/feedback/:id с одним только статусом — например {"status":"REVIEWING"} — при возврате обращения из RESOLVED, WITHDRAWN или ARCHIVED обнулял поле resolution, хотя в теле запроса его не было. Ответ приходил 200, о потере в нём ничего не говорилось. Если текст ответа команды не был продублирован комментарием, восстановить его было нечем.

Стало

Возврат в активный статус обнуляет только resolvedAt и resolvedBy. Поле resolution не меняется, если вы его не передали: у обращения, возвращённого из ARCHIVED, сохраняется и причина архивации. Чтобы заменить текст — передайте resolution в том же запросе, чтобы очистить поле — передайте "resolution": null. Путь с комментарием (POST /v1/feedback/:id/comments) resolution не трогал и раньше — теперь обе поверхности ведут себя одинаково.

BC-0807-17: агрегации дел нужен сужающий фильтр

Поддержка старого формата до: 07.02.2027

Было

POST /v1/activities/aggregate принимал запрос без фильтра. На небольшом аккаунте он отвечал за секунду, на большом — не отвечал никогда: первое же обращение к Битрикс24 (подсчёт всех дел аккаунта) не укладывалось в отведённое на вызов время, и клиент получал 503 BITRIX_TIMEOUT с заголовком Retry-After и подсказкой «чтения можно повторять». Повтор давал тот же результат, потому что причина не была временной. Документация при этом прямо предлагала {} как «самый быстрый запрос».

meta.truncated означало ровно одно: «под фильтр попало больше 5000 записей». Если часть страниц записей до нас не дошла, ответ приходил с truncated: false — то есть числовые агрегации и группы были посчитаны по части записей, а ответ этого не сообщал.

Стало

Агрегация дел требует одного сужения из трёх: пара ownerTypeId + ownerId, либо responsibleId, либо граница по дате на createdAt / updatedAt / deadline. Требование включается платформой отдельно на каждый аккаунт. Пока оно выключено, поведение прежнее; после включения запрос без сужения получает 400 MISSING_REQUIRED_FILTER — в message перечислены допустимые сужения и готовый пример тела, обращения к Битрикс24 не происходит.

Независимо от этого переключателя запрос без сужения, на который Битрикс24 не ответил за отведённое время, теперь возвращает 422 AGGREGATION_LIMIT_EXCEEDED вместо 503: отказ терминальный, заголовка Retry-After нет, в тексте — что сделать вместо повтора. Запрос с сужением по-прежнему получает на таймауте 503 с Retry-After: там повтор — честный совет, потому что причину замедления мы не знаем.

Оба ответа приходят и на устаревший GET /v1/activities/aggregate — правило нельзя обойти, вызвав его.

meta.truncated теперь означает «часть записей до нас не дошла» и в прежнем случае (выборка шире 5000), и в новом (обработано меньше записей, чем всего под фильтр). Во втором случае рядом приходит meta.recordsShortfall — сколько записей не хватило. count и meta.totalRecords при этом остаются полными: неполны только data.groups и числовые агрегации. ⚠️ Эта половина изменения касается агрегации ЛЮБОЙ сущности, а не только дел: раньше в таком ответе приходило truncated: false, то есть неполнота не сообщалась нигде.

Список допустимых сужений и то, включено ли требование на аккаунте прямо сейчас, приходят в data.aggregateFilterRequirement ответа GET /v1/activities/fields: поле anchors — сужения, поле enforcement — enforced либо advisory. Статические сужения без состояния аккаунта есть и в GET /v1/guide.

Что делать интеграторам

Добавить в агрегацию дел одно из сужений — этого достаточно и до, и после включения требования. Если сегодня в коде есть ветка на 503 для этого эндпоинта, добавить ветку на 422 и не повторять запрос по ней. Если код читает meta.truncated, учесть, что теперь он поднимается и при потере записей, и посмотреть на meta.recordsShortfall.

FIX-0807-18: файл в поле CRM больше не упирается в 1 МБ, а код отказа стал понятным

Было

Значение пользовательского поля типа «Файл» едет в теле запроса как base64, а тело было ограничено 1 МБ на всех методах. Практический потолок одного файла — около 750 КБ: PATCH /v1/deals/{id} с файлом крупнее отвечал 413 и кодом FST_ERR_CTP_BODY_TOO_LARGE, которого нет ни в одной странице документации. Тот же потолок бил по загрузке файлов ботам и в чаты.

Стало

Тело ограничено 40 МиБ на создании и обновлении записей (POST /v1/{entity}, PATCH /v1/{entity}/{id}, включая POST /v1/items/{entityTypeId}), а также на POST /v1/bots/{botId}/files и POST /v1/chats/{chatId}/files — это чуть меньше 30 МиБ исходного файла после base64. Остальные методы сохраняют прежний потолок 1 МБ: поиск (POST /v1/{entity}/search), пакетные вызовы, служебные методы.

Код отказа 413 теперь PAYLOAD_TOO_LARGE на всех методах /v1/, кроме маршрутов AI (/v1/ai/*, /v1/chat/*, /v1/audio/*, /v1/models) — там сохраняется конверт ошибки, совместимый с OpenAI. Это тот же код, что уже возвращает пограничный слой на своём пороге. Замена затрагивает и потоковую загрузку исходников приложений (POST /v1/apps/{id}/sources) и серверов (POST /v1/infra/servers/{id}/sources), и отказ 413 на неверном Content-Type у публикации приложения, привязки мест встраивания и создания шаблона документа. Прежний внутренний код в ответах больше не встречается.

Порядок проверок изменился в пользу безопасности: на записи сущностей и на загрузке файлов ботам и в чаты ключ проверяется до чтения тела. Запрос без ключа или с неверным ключом теперь получает 401 там, где раньше мог получить 400 о неразобранном JSON или 413 о размере.

Появился новый отказ 429 с кодом LARGE_BODY_BACKEND_BUSY и заголовком Retry-After: 5: одновременно обрабатываемых тел крупнее 1 МБ ограниченное число. Он защищает память сервера — поднятый потолок сам по себе ничем не ограничен, а каждый ожидающий очереди вызов удерживает своё тело. Обычный клиент его не увидит; массовая заливка в несколько потоков — увидит, и правильная реакция на него та же, что на любой 429: подождать и повторить.

Учтите время: вызов в Битрикс24 ограничен 15 секундами и не повторяется, поэтому файл у самой границы на медленном аккаунте может отказать с BITRIX_TIMEOUT. Оставляйте запас или переносите крупные файлы в поле «Файл (диск)» через POST /v1/files/upload.

2026-08-06

NEW-0806-1: 402 при исчерпанной квоте Cowork/Code несёт заголовок Retry-After

Ответ 402 с кодом cowork_quota_exhausted на POST /v1/chat/completions теперь несёт заголовок Retry-After — число секунд до сброса исчерпанного окна квоты (5h, week или month). Раньше момент сброса был виден только в поле resetAt тела ответа; заголовок понимают и обычные HTTP-клиенты без разбора тела. Ответ 402 с кодом insufficient_balance заголовок не несёт — у пустого баланса нет времени сброса.

FIX-0806-2: Веб-поиск: статус ответа различает отказ ключа провайдером и сбой провайдера

Было

Любая ошибка поискового провайдера в POST /v1/search приходила как 502 UPSTREAM_ERROR — и отказ провайдера принять ключ (401/403), и его троттлинг (429), и настоящий сбой. Клиенты повторяли запросы, которые не могли пройти.

Стало

Для BYOK-ключа ответы провайдера 401 и 403 сохраняют свой статус — провайдер отверг ваш ключ, замените его. Ответ провайдера 429 сохраняет статус для любого ключа и несёт заголовок Retry-After. Код ошибки во всех случаях остаётся UPSTREAM_ERROR, а тело дополнительно несёт поле upstream_status с исходным статусом провайдера. Остальные ошибки провайдера, включая отказ ключа платформенного движка, по-прежнему приходят как 502. Дополнительно ограничена длина поля message в ответе 400 INVALID_REQUEST — присланное значение больше не отражается целиком.

Влияние на интеграторов

Обработчик, который повторял запрос на любой 5xx, продолжает работать. Если вы ветвились на 502 как на «любую ошибку провайдера», добавьте ветки для 401/403 (замените BYOK-ключ) и 429 (повторите по Retry-After); надёжный признак «это ошибка провайдера, а не авторизации» — поле upstream_status в теле.

FIX-0806-3: реестр операций в GET /v1/guide перечисляет то, что действительно работает

Было

Список операций сущности в GET /v1/guide расходился с набором работающих эндпоинтов сразу в двух направлениях.

Он молчал о рабочих операциях. Ни одна сущность не объявляла fields, хотя GET /v1/{entity}/fields отвечает у 46 из 49 сущностей. У бронирований отсутствовали list и search, хотя GET /v1/bookings и POST /v1/bookings/search обслуживаются отдельными обработчиками, — робот читал сущность как «только на запись» и не мог получить идентификатор. У конфигураций открытых линий отсутствовали list, search, create, update и delete — в реестре оставались только getById, aggregate и batch. Тем же механизмом были скрыты create шаблонов документов, list и create адресов, search узлов оргструктуры и delete пользователя, а у поиска адресов реестр печатал описание общего оконного поиска, которого этот обработчик не выполняет.

И он обещал то, чего у сущности нет: пример пакетного вызова у восьми сущностей называл действие create, которое для них возвращает 400 ACTION_NOT_SUPPORTED.

Стало

Операция объявлена ровно тогда, когда её маршрут действительно зарегистрирован. Появились fields у сущностей, где этот маршрут есть, list и search у бронирований (с обязательными параметрами dateFrom и dateTo в описании), полный набор list / search / create / update / delete у конфигураций открытых линий, create у шаблонов документов, list / search / create у адресов, search у узлов оргструктуры и delete у пользователя. Описания этих операций перечисляют параметры, которые читает их собственный обработчик, а не общий контракт поиска: оконного поиска (autoWindow, windowCount) у них нет.

Пример пакетного вызова называет действие, которое сущность принимает, и ключ, который читает это действие: ids для удаления, items для остальных записей, calls для чтения. У сущности без доступных операций записи пример читающий.

Заметка у такой сущности больше не перечисляет набор чтения одной строкой-константой «принимаются list, get, fields»: она называет действия, которые у ЭТОЙ сущности действительно отвечают данными, и отдельно — что конверт делает с остальными. Причин слепоты три: fields у сущности без метода схемы полей отвечает пустым объектом (и заметка ведёт к GET /v1/{entity}/fields, если этот маршрут есть); get у сущности без адресного метода чтения отдаёт первую запись коллекции, а не запрошенную; у сущности на REST 3.0 пакетный подвызов вообще не доходит до метода и возвращает пер-вызовную ошибку внутри 200; а list у конфигураций открытых линий уходит в Битрикс24 без конверта, которого требует imopenlines.config.list.get, и отвечает 200 со всей коллекцией, молча выбросив фильтр.

То же утверждение исправлено ещё в двух местах, где клиент его видит: тело отказа 400 ACTION_NOT_SUPPORTED больше не заканчивается фразой «Supported batch actions: list, get, fields» (теперь там тот же вычисленный набор — прежде клиент, прочитавший честный реестр и споткнувшийся об отказ, получал вводящий в заблуждение список назад), и описание пакетного чтения конфигураций открытых линий в схеме OpenAPI: список принимаемых действий там сохранён, но сказано, какие из них отвечают данными.

Там же исправлено описание select у списка конфигураций открытых линий: перечисление через запятую (?select=id,name) читается, не читается только форма с индексом (?select[0]=id).

Сущность, у которой операции нет, её и не получает: fields не появился у комментариев к задачам, у разделов календаря и у почтовых ящиков. Осталось необъявленным и то, что объявлять не следует: маршруты, существующие только чтобы отбить вызов с указанием верного пути, и aggregate у сущности, где работает лишь подсчёт, — про него по-прежнему сообщает описание поиска.

Влияние на интеграторов

Изменение аддитивное: прежние поля operations не переименованы и не удалены. Клиент, который строил список доступных вызовов по этому реестру, теперь видит операции, которые раньше приходилось угадывать или искать в документации. Клиент, который копировал пример пакетного вызова как есть, перестанет получать 400 ACTION_NOT_SUPPORTED на сущностях только для чтения.

FIX-0806-4: служебные поля задачи принимаются на записи — так же, как их принимает сам Битрикс24

У задачи семь служебных полей: постановщик (createdBy), кто изменил (changedBy), кто закрыл (closedBy), кто изменил статус (statusChangedBy), а также даты создания, изменения и закрытия (createdDate, changedDate, closedDate). Битрикс24 принимает и сохраняет их все — и при создании задачи, и при обновлении. Вайбкод отклонял шесть из семи, то есть был строже платформы на ровном месте.

Было

POST /v1/tasks и PATCH /v1/tasks/:id отвечали 400 READONLY_FIELD на changedBy, closedBy, statusChangedBy, createdDate, changedDate, closedDate и до Битрикс24 не доходили. Так же отклонялись написания верхним регистром — CHANGED_BY и остальные. Отказ приходил и в подзапросе POST /v1/batch. Седьмое поле, createdBy, при создании работало, а при обновлении отклонялось.

Стало

Все семь принимаются на обеих операциях и на всех трёх поверхностях записи — одиночный маршрут, пакетный запрос сущности и общий пакетный запрос. Принимаются оба написания, createdBy и CREATED_BY. Значение применяется в пределах прав вызывающего пользователя: если Битрикс24 отказывает в правке задачи, отказ приходит как есть — 422 с его собственным текстом, без подмены на нашу ошибку и без ложного успеха.

Смену постановщика Битрикс24 пишет в журнал изменений задачи, и там остаётся настоящий вызывающий пользователь. Для остальных шести полей записи в журнале не предусмотрено. Ещё одна тонкость — у трёх дат значение без часового пояса в написании createdDate получает смещение из заголовка X-Vibe-Timezone, а в написании CREATED_DATE уходит как есть. Обе тонкости разобраны на странице PATCH /v1/tasks/:id.

Что НЕ изменилось: id по-прежнему отклоняется — Битрикс24 присваивает идентификатор сам и переданное значение игнорирует, поэтому явный отказ честнее молчаливой потери. dateStart, activityDate и realStatus тоже остаются закрытыми, но по другой причине: их поведение на записи мы не проверяли, а объявлять поле открытым без проверки не станем.

Передавайте только существующего сотрудника — во всех четырёх полях с идентификатором пользователя. Битрикс24 не проверяет значение на существование и запишет любое число, а задача с несуществующим постановщиком перестаёт управляться через API: отказ приходит и на дальнейшее обновление, и на удаление, причём даже ключу администратора и напрямую в Битрикс24, минуя нас. Предупреждение добавлено на страницу обновления задачи.

Влияние на интеграторов

Ничего менять не нужно: запросы, которые раньше отклонялись, теперь выполняются. Если ваш код полагался на 400 READONLY_FIELD как на защиту авторства и истории — эту роль он не играл: те же значения принимает сам Битрикс24 по своему интерфейсу, в обход нашего слоя. Ограничить подмену служебных полей может только модель прав Битрикс24 — это отдельная доработка на стороне платформы. У лидов и сделок поле автора закрыто по-прежнему, и это не изменилось: там Битрикс24 значение молча игнорирует, поэтому явный отказ остаётся честным ответом.

FIX-0806-5: карточка приложения в каталоге открывает подпуть, а не корень сервера

Было

Карточка приложения в каталоге Битрикс24 всегда открывала корень Black Hole-сервера. Приложение, которое отдаёт интерфейс из подкаталога, из каталога открыть было нельзя: переход отвечал HTTP 200 и показывал то, что живёт в корне того же сервера. Адрес приложения (appUrl) на это никак не влиял, а его правка через PATCH /v1/apps/:id до карточки не доходила.

Стало

Карточка ведёт по полному адресу связанного приложения вместе с подпутём, query и fragment, если этот адрес указывает на тот же поддомен Black Hole, что и сервер карточки. Во всех остальных случаях — прежний корень сервера: другой поддомен, свой домен, другая схема, пустой или неразбираемый адрес.

Правка appUrl через PATCH /v1/apps/:id теперь ставит карточку в очередь на обновление, поэтому новый адрес доезжает до Битрикс24 сам. Разовый проход по уже опубликованным карточкам выполняется на стороне платформы — от интегратора действий не требуется.

Влияние на интеграторов

Ничего менять не нужно. Приложение, отдающее интерфейс из корня, работает как прежде. Приложение в подкаталоге больше не требует ручного обхода: достаточно, чтобы appUrl содержал нужный подпуть.

FIX-0806-6: распознавание речи сообщает о временной паузе провайдера

Было

При временной недоступности кластера POST /v1/audio/transcriptions мог отвечать 502 ai_provider_unavailable, не сообщая клиенту, сколько ждать перед повтором.

Стало

В этом состоянии метод отвечает 429 ai_provider_cooldown с заголовком Retry-After в секундах. Запрос не выполняется и не списывает квоту или деньги. Дождитесь указанного интервала и повторите тот же запрос.

FIX-0806-7: распознавание речи действительно ждёт ответа заявленные 15 минут

Было

Для длинной записи POST /v1/audio/transcriptions мог ответить 503 ai_provider_timeout примерно через 5 минут, хотя в описании метода заявлено ожидание до 15 минут. В тексте ошибки при этом говорилось о сетевом таймауте.

Стало

Метод ждёт ответа весь заявленный срок — до 15 минут — и отвечает 503 ai_provider_timeout с заголовком Retry-After только по его истечении. Ограничение на длительность записи не изменилось: для файлов длиннее ~30 минут по-прежнему разбивайте запись на части.

FIX-0806-8: exec для galaxy-приложения обращается к контейнеру по его настоящему имени

Было

POST /v1/infra/servers/{id}/exec для приложения в галактике всегда обращался к контейнеру по имени из subdomain. Приложение, восстановленное из клона, работает под другим именем, поэтому команда уходила к контейнеру, которого нет: вызов завершался ошибкой, а в неудачном случае мог попасть в оставшийся от прежнего развёртывания контейнер. Остальной жизненный цикл галактики (развёртывание, переезд, остановка) уже учитывал переименование — расходился только exec.

Стало

Имя контейнера резолвится единым правилом для всех операций: используется имя на хосте, если приложение переименовано, иначе subdomain. Имя дополнительно проверяется перед подстановкой в команду; при непригодном имени возвращается 409 GALAXY_APP_NOT_READY с текстом invalid on-host name вместо запуска команды. Для приложений, которые не восстанавливали из клона, поведение не меняется.

BC-0806-9: V1: статус сервера в JSON всегда строчными буквами

Поддержка старого формата до: 06.09.2026

Было

GET /v1/infra/servers и GET /v1/infra/servers/:id отдавали status строчными (running, sleeping), а POST /v1/infra/servers/:id/wake, POST /v1/infra/servers/:id/refresh, поле currentState.status в ответах 422 у POST /v1/infra/servers/:id/start, POST /v1/infra/servers/:id/stop, POST /v1/infra/servers/:id/reboot и infra.unhealthyServers[].status в GET /v1/me — значение перечисления как в базе, ЗАГЛАВНЫМИ (RUNNING, SLEEPING, PROVISIONING). Клиент, выучивший status === 'running' по документации и GET, ломался на ответах пробуждения и обновления состояния.

Стало

Во всех перечисленных полях публичного V1 JSON значение статуса сервера — строчные литералы: provisioning, running, stopped, sleeping, error, deleted. Поле data у обновления состояния по-прежнему строка, а не объект: сравнивайте data === 'running', не data.status. Поле blackholeStatus не изменилось — оно остаётся ЗАГЛАВНЫМИ (CONNECTED, DISCONNECTED, NONE).

Влияние на интеграторов

Замените сравнения с 'RUNNING' / 'SLEEPING' / 'PROVISIONING' и остальными заглавными значениями на строчные либо сравнивайте без учёта регистра. Читайте статус из структурных полей (data, currentState.status), а не из текста message / userMessage — там статус по-прежнему может встречаться заглавными.

NEW-0806-10: деплой galaxy-приложения по ссылке и по сохранённой версии

Раньше galaxy-приложение принимало только встроенный архив: source.url и source.versionId отклонялись с 400 GALAXY_DEPLOY_CONTENT_ONLY. Теперь POST /v1/infra/servers/:id/deploy принимает обе формы там, где платформа включила выкладку по ссылке для вашего аккаунта Битрикс24; где не включила — код GALAXY_DEPLOY_CONTENT_ONLY возвращается как прежде, и встроенный source.content продолжает работать всегда.

Ссылку скачивает сам хост, поэтому архив не проезжает через тело запроса: потолок на встроенный архив (413 GALAXY_UPLOAD_TOO_LARGE) на этот путь не распространяется, и слот одновременных «толстых» запросов (429 DEPLOY_BACKEND_BUSY) он не занимает. source.versionId выкладывает версию, уже лежащую в хранилище исходников: платформа сама минтит подписанную ссылку и связывает версию с этим деплоем, поэтому в истории видно, что именно уехало в прод.

Ссылка у galaxy-приложения ведёт только в хранилище исходников — подойдёт подписанная ссылка сохранённой версии. Сторонний адрес отклоняется кодом 400 GALAXY_SOURCE_URL_NOT_ALLOWED, поэтому крупный архив сначала сохраняют версией, а выкладывают по source.versionId. У отдельной виртуальной машины такого ограничения нет.

Создание сервера с источником (POST /v1/infra/servers) принимает source.url на тех же условиях. source.versionId там не принимается: сервера, чьё хранилище задавало бы область поиска версии, в момент создания ещё нет — выкладывайте версию вторым шагом, через /deploy.

Кабинетные маршруты остаются на встроенном архиве.

FIX-0806-11: справочник полей элементов смарт-процессов больше не показывает поле contacts

Было

GET /v1/items/:entityTypeId/fields показывал поле contacts с типом crm_contact. Значения по нему не приходило ни в списке, ни в карточке элемента, а записать его было нельзя: Битрикс24 принимал только пустой массив, а любое непустое значение отклонял ошибкой своего внутреннего слоя данных. Поле попадало в справочник сквозным пробросом схемы Битрикс24, а не объявлялось платформой.

Стало

Поле убрано из справочника. Привязанные контакты читаются и пишутся через contactId и contactIds — они не изменились. Фильтр и сортировка по contacts как и раньше отклоняются с кодом UNKNOWN_FILTER_FIELD и UNKNOWN_SORT_FIELD.

Влияние на интеграторов

Действий не требуется: значения по полю не существовало, поэтому клиент, читавший его, всегда получал пустоту. Если вы строили модель данных по справочнику — уберите contacts из неё и опирайтесь на contactIds.

FIX-0806-12: справочник полей страниц сайта сообщает, какие поля могут быть пустыми

Было

Ответ GET /v1/pages/fields не позволял отличить поле, у которого значение есть всегда, от поля, приходящего null. Вдобавок два описания обещали не то, что приходит: datePublic описывался как «приходит пустым объектом», а dateCreate, dateModify и datePublic — как дата фиксированного шаблона. Клиент, написавший разбор по этим описаниям, спотыкался на пустом значении, а фильтр по дате в чужом формате возвращал пустой список с кодом 200.

Стало

Девять полей, которые Битрикс24 заполняет не всегда, помечены признаком nullable: description, xmlId, tplId, tplCode, folderId, searchContent, initiatorAppCode, rule, datePublic. Набор получен замером по всей коллекции страниц живого портала, а не выведен из описания метода Битрикс24.

datePublic описан честно: обёртка отдаёт null, и это обычное значение даже для опубликованной страницы, поэтому факт публикации читайте из active или public. Описания dateCreate, dateModify и datePublic больше не обещают фиксированный шаблон: это строка в формате локали портала, одинаковая в списке и в карточке. Тот же формат нужен и в фильтре: значение в формате другой локали или в ISO Битрикс24 не распознаёт и возвращает пустой список с кодом 200.

Тем же признаком помечены поля в генерируемой схеме OpenAPI: там тип теперь записан как ["string", "null"], поэтому клиент, который валидирует ответ по схеме, больше не падает на пустом значении. Контракт записи не затронут.

Влияние на интеграторов

Действий не требуется: состав полей, типы и значения не изменились — добавился только признак nullable и уточнились описания. Если вы определяли публикацию страницы по наличию datePublic, переключитесь на active или public.

NEW-0806-13: отказ по лимиту ключей называет числа

POST /v1/apps при отказе KEY_LIMIT_REACHED (409) теперь кладёт в error.details состояние квоты: limit — сколько ключей на человека разрешил администратор портала, used — сколько занято сейчас.

Было

JSON
{
  "success": false,
  "error": { "code": "KEY_LIMIT_REACHED", "message": "Maximum number of API keys reached" }
}

Стало

JSON
{
  "success": false,
  "error": {
    "code": "KEY_LIMIT_REACHED",
    "message": "Maximum number of API keys reached",
    "details": { "limit": 10, "used": 10 }
  }
}

Поле аддитивное — клиенты, читающие только code, ничего не заметят. В used входят и ключи, выписанные платформой (приложения, агенты, боты), поэтому число может превышать длину списка из GET /v1/keys.

NEW-0806-14: справочники полей сайтов и сотрудников отдают подписи, описания и перечни значений

GET /v1/sites/fields теперь отдаёт подпись label и описание description у всех 22 полей — раньше они были только у поля type, а остальные 21 приходили с одним типом и признаком «только для чтения». Описания сообщают то, чего по типу не видно: что active через API не устанавливается, что code хранится в форме, обрамлённой слешами, что landingIdIndex/landingId404/landingId503 задаются только при обновлении, а dateCreate и dateModify приходят строкой в формате локали портала, а не в ISO 8601.

GET /v1/users/fields получил перечень допустимых значений enum у пола (personalGender: M, F) и у типа учётной записи (userType: employee, extranet, email) — каждое значение с английской подписью label и русской labelRu. Кроме этого подписи и описания появились у десяти полей рабочих сведений, у которых их не было в Битрикс24 вовсе и вместо подписи приходило само имя поля: WORK_FAX, WORK_PAGER, WORK_STREET, WORK_MAILBOX, WORK_STATE, WORK_ZIP, WORK_COUNTRY, WORK_PROFILE, WORK_LOGO, WORK_NOTES. Если портал такое поле подписал сам, его подпись сохраняется без изменений.

У пола появился ещё и признак nullable: true, а в схеме OpenAPI тип этого свойства объявлен как ["string", "null"]. Незаполненный пол приходит пустым (null) — на замеренном портале так отвечали 48 сотрудников из 50, — а схема без этого признака обещала строку и ничего кроме строки, поэтому клиент, проверяющий ответ по нашей же опубликованной схеме, получал ошибку почти на каждой записи. Признак описывает только чтение: в схеме тела запроса тип поля остался строкой.

Перечни значений приходят и в двух других машиночитаемых поверхностях — GET /v1/guide (блок fieldsDetailed сущности) и схема OpenAPI (x-enumValues у свойства). Подписи и описания объявленных полей схемы отдаёт только OpenAPI (title и description у свойства), поэтому клиент, сгенерированный по схеме, получает их без дополнительных вызовов; путеводитель подписи не несёт намеренно. Подписи и описания десяти полей рабочих сведений приходят только в самом справочнике полей.

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

FIX-0806-15: разделы товаров: поле sort помечено «только для чтения» и «не возвращается»

Было

GET /v1/product-sections/fields объявлял sort записываемым, POST /v1/product-sections и PATCH /v1/product-sections/:id принимали его без ошибки, а Битрикс24 значение не сохранял. При этом ни один ответ на чтение — карточка, список, поиск, отклик создания — поле не содержал, даже если запросить его явно в select. Клиент получал «успех» и продолжал считать, что порядок задан.

Стало

Поле помечено только для чтения и notReturned: true. Запись sort в теле создания или обновления отклоняется с 400 READONLY_FIELD до вызова Битрикс24; в справочнике полей поле осталось видимым вместе с описанием причины, чтобы её можно было прочитать на месте. Упорядочивание по нему работает как раньше: ?sort=sort&order=asc и order=desc дают разный порядок. Фильтрация по sort по-прежнему отклоняется с 400 UNSUPPORTED_FILTER.

Влияние на интеграторов

Уберите sort из тела запросов создания и обновления разделов товаров — иначе запрос целиком получит 400 READONLY_FIELD вместо прежнего «успеха». Если код читал sort из ответа, его там никогда и не было: значение приходило undefined. Менять порядок разделов можно в интерфейсе Битрикс24, читать порядок — упорядочиванием списка по этому полю. Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что значение молча терялось, — сохранять было бы нечего.

FIX-0806-16: у сайтов поле active стало только для чтения — включение идёт через публикацию

Было

GET /v1/sites/fields описывал active как обычное записываемое поле, и запрос с ним проходил: POST /v1/sites и PATCH /v1/sites/:id возвращали успех. Значение при этом терялось. Битрикс24 не принимает ACTIVE ни в landing.site.add, ни в landing.site.update — их контракт этого поля не объявляет, а новый сайт всегда создаётся неактивным. Живая проверка обоих вызовов подтвердила потерю: и создание с active: true, и обновление на active: true отвечали успехом, а признак оставался выключенным.

Стало

active помечено readonly. Передача его в теле создания или обновления отклоняется с 400 READONLY_FIELD до вызова Битрикс24. В ответе list/get и в справочнике /fields поле остаётся — читается оно по-прежнему, в том числе фильтрацией и группировкой по нему.

Активность сайта включается публикацией в интерфейсе портала Битрикс24.

Влияние на интеграторов

Если код передавал active в теле создания или обновления сайта — уберите его. Значение всё равно никогда не сохранялось, но теперь запрос отклоняется целиком, поэтому вместе с ним не применятся и остальные поля тела: название, символьный код, домен, описание. Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что значение молча терялось, — сохранять было бы нечего.

FIX-0806-17: недоступный склад образов на сборке galaxy-приложения — временная ошибка, а не отказ

Было

Если публичный склад образов был недоступен в момент сборки, POST /v1/infra/servers/:id/deploy отдавал 502 с сырым текстом Docker, а приложение помечалось как сломанное — повтор приходилось запускать вручную, и подсказки в ответе не было.

Стало

Ответ несёт код GALAXY_BASE_IMAGE_UNAVAILABLE, признак retryable: true и поле hint с инструкцией повторить тот же запрос через 2-3 минуты. Слот не помечается сломанным, поэтому повтор ложится на него же. Для агентов платформа повторяет сама. Одношаговое создание с исходниками (POST /v1/infra/servers с source) HTTP-ответа уже не держит, поэтому там приложение по-прежнему помечается сломанным, но текст ошибки называет причину и просит повторить развёртывание.

FIX-0806-18: отказ выписки при живой подписке больше не выдаёт «демо уже использована»

Было

Если Битрикс24 отказывал в выписке ключа или установке приложения по линии подписки, ответ строился по признаку «демо когда-то активировали». Признак остаётся поднятым всё время действия демо, поэтому портал с ДЕЙСТВУЮЩЕЙ демо-подпиской получал 403 B24_MARKET_TRIAL_USED и текст «демо-подписка уже использована, оформите платную» — при том, что подписка работала и покупать было нечего.

Стало

Пока подписка или демо действуют, отказ выписки отдаётся как 502 CONNECTOR_REST_UNAVAILABLE с текстом «повторите попытку / обратитесь в поддержку» и без предложения оформить подписку. В ответе есть error.details.reason (исходная причина отказа) и error.details.retryable: true. Если демо действительно израсходовано, ответ прежний — 403 B24_MARKET_TRIAL_USED; если подписки не было ни разу — 403 B24_MARKET_SUBSCRIPTION_REQUIRED. Затронуты POST /v1/apps и парные кабинетные маршруты выписки ключей и создания приложений. Дополнительно: отказ вида «REST недоступен» повторяется автоматически один раз — это снимает гонку сразу после активации демо.

FIX-0806-19: платформа сообщает приложению его порт в переменной PORT

Было

Платформа согласовывала порт приложения на трёх уровнях — проброс публичного трафика, EXPOSE в образе и проверка работоспособности, — но самому приложению его не сообщала. Приложение, написанное по общей конвенции облачных платформ (listen(process.env.PORT)), получало пустое значение, занимало случайный свободный порт, и на нужном порту никто не слушал: шлюз отдавал «Приложение не обнаружено», хотя POST /v1/infra/servers/:id/deploy рапортовал успех.

Стало

Платформа передаёт номер порта в переменной окружения PORT — всегда равной полю port запроса (по умолчанию 3000). На отдельной виртуальной машине это отдельный платформенный файл .vibe-platform.env, который systemd-юнит подключает после вашего .env; ваш .env не читается и не перезаписывается. В приложении галактики PORT приходит в окружение контейнера при запуске. В ответе появился шаг platform_env.

Ключ PORT теперь зарезервирован платформой: если прислать свой env.PORT, отличный от поля port, платформа его перекроет и скажет об этом строкой в warnings[] ответа. Прислать совпадающее значение можно — предупреждения не будет.

Влияние на интеграторов

В обычном случае менять ничего не нужно: приложение, слушающее process.env.PORT, теперь работает без явного env.PORT, а уже работающие приложения получат PORT при следующем деплое. Есть одно исключение, и его стоит проверить: если вы держали в env.PORT не порт своего приложения, а что-то другое (порт базы, внешнего сервиса), — переименуйте эту переменную, потому что теперь PORT принадлежит платформе и ваше значение до приложения не дойдёт. В ответе на деплой в таком случае приходит предупреждение в warnings[]. Если вы читаете файл .env напрямую, а не переменные окружения процесса, — читайте process.env.PORT: платформенное значение живёт в отдельном файле. Со своим systemd-юнитом (systemd: false) платформенный файл создаётся, но подключаете его вы — пока не подключили, побеждает ваш env.PORT, и предупреждение в ответе говорит именно об этом; как подключить, описано в POST /v1/infra/servers/:id/deploy.

BC-0806-20: расход квоты отдаётся только в процентах

Поддержка старого формата до: 06.02.2027

Было

GET /v1/ai/quota возвращал в data.byModel[] абсолютные счётчики расхода — tokensIn, tokensOut и audioSeconds — рядом с долей лимита pctOfLimit.

JSON
{ "modelId": "bitrix/bitrixgpt-5.5", "calls": 1240, "tokensIn": 800000, "tokensOut": 350000, "audioSeconds": 0, "pctOfLimit": 1.2 }

Стало

Три поля убраны. Расход квоты — как и сам лимит — раскрывается только относительной величиной: pctOfLimit (доля месячного лимита, израсходованная моделью) и calls (количество вызовов).

JSON
{ "modelId": "bitrix/bitrixgpt-5.5", "calls": 1240, "pctOfLimit": 1.2 }

Что делать интеграторам

Авторитетная цифра по аккаунту одна — data.pctUsed: именно она считается по леджеру списаний и учитывает скидку за время суток. Разбивка byModel[].pctOfLimit показывает, КУДА ушла квота, и считается пересчётом журнала вызовов по действующим сейчас ценам, поэтому суммировать доли по моделям и сравнивать сумму с pctUsed не нужно — величины разойдутся. Если вам нужны токены для собственного учёта, берите их из GET /v1/ai/usage или снимайте на вызове модели: ответ POST /v1/chat/completions по-прежнему содержит блок usage с prompt_tokens и completion_tokens.

FIX-0806-21: исчерпанная квота Cowork/Code больше не обслуживается подменной моделью

Было

При исчерпании квоты Cowork/Code запрос от ключа десктопа обслуживался резервной моделью, причём инструменты (tools) и системный промпт запроса передавались ей без изменений. Модель отвечала связно и могла сообщить о выполненной работе, которой не было. Ответ приходил с кодом 200 и заголовком X-Cowork-Fallback: true.

Стало

Ответ один для всех ключей: tools, tool_choice и response_format снимаются, модель сообщает об исчерпанном лимите и о том, когда он сбросится. Код ответа 200 при настроенной резервной модели, иначе 402 cowork_quota_exhausted — как раньше. Заголовок X-Cowork-Fallback: true и предупреждение COWORK_QUOTA_FALLBACK остаются, но означают теперь «лимит объявлен», а не «запрос обслужен другой моделью».

Влияние на интеграторов

Не ожидайте tool_calls и структурированный ответ при исчерпанной квоте: response_format снимается, поэтому вернётся текст, а не JSON. Признак состояния — заголовок X-Cowork-Fallback, предупреждение COWORK_QUOTA_FALLBACK или код 402. Отдельно исправлена дата обновления месячного лимита на бесплатном тарифе. Раньше resetAt.month (GET /v1/cowork/me), windows.month.resetAt и subscription.currentPeriodEnd (GET /v1/cowork/state) отдавали дату из строки подписки, а у бесплатного места она не сдвигалась после окончания периода — то есть приходила дата в прошлом, и отсчёт до обновления показывал «меньше минуты» бесконечно. Теперь все три поля отдают период, в котором место находится фактически: месячный счётчик обнуляется при первом обращении после окончания периода. В теле отказа 402 cowork_quota_exhausted поле resetAt для месячного окна больше не приходит равным 1970-01-01.

Влияние на интеграторов

Если вы кэшировали currentPeriodEnd бесплатного места как неизменную дату — перечитайте её: на просроченном месте она сдвинется вперёд.

FIX-0806-22: починка сервера сообщает причину отказа и больше не оставляет агента выключенным

Было

GET /v1/infra/servers/:id/repair-status при неудаче возвращал error без причины — SSH install failed (exit 255); serial fallback: Serial console install failed. По этому тексту нельзя было отличить закрытый порт от недоступной машины или от сорвавшегося скачивания. Кроме того, установка агента останавливала работающий сервис ДО того, как скачивала новую версию: если скачивание не удавалось (нет выхода в интернет, недоступен адрес раздачи), агент оставался выключенным, а следующая попытка починки повторяла то же самое.

Стало

error несёт причину: для обычного входа — сообщение SSH (Connection refused, Connection timed out и т.п.), для установки через аварийную консоль — короткий отрывок вывода консоли (например curl: (6) Could not resolve host: …). Отрывок очищен от секретов и ограничен по длине. Установка теперь сначала скачивает новую версию агента и только потом останавливает сервис, а при отказе любого последующего шага возвращает агента в работу.

FIX-0806-23: выписка ключа проверяет доступ к платформе

Было

POST /v1/keys и POST /v1/apps выписывали новый ключ любому порталу, прошедшему авторизацию, — даже если доступ к платформе у портала закрыт. Ключ при этом работал: выписка проверку доступа не спрашивала.

Стало

Перед выпиской проверяется доступ портала. Порталу без доступа приходит тот же код, который он уже получает на других поверхностях, — MARKETPLACE_REQUIRED, KZ_PAID_ONLY, UZ_PAID_ONLY или INT_TARIFF_REQUIRED, в зависимости от региона лицензии.

Уже выданные ключи продолжают работать. Проворот ключа, автоматическое восстановление и передача владения не затронуты: клиент с закончившимся доступом должен уметь закрыть свои дела.

Влияние на интеграторов

Обработайте отказ на выписке так же, как на остальных поверхностях: оформите доступ и повторите запрос. Ранее выписанные ключи менять не нужно.

NEW-0806-24: понятный отказ, когда личному ключу оставили только placement или entity

Личный ключ работает через входящий вебхук Битрикс24, а тот не хранит права placement и entity — они требуют контекста приложения. Раньше такой запрос падал невнятно: при создании ключа приходил 502 DEVKEY_MINT_FAILED с советом обратиться к администратору портала, при правке прав — 502 DEVKEY_SCOPE_SYNC_FAILED.

Теперь оба случая отвечают 400 PERSONAL_KEY_WEBHOOK_SCOPES_INVALID с текстом, который говорит, что делать: добавить хотя бы одно обычное право (например crm или user_brief) либо создать OAuth-приложение, если нужны встраивания.

Отказ приходит, только когда после отбрасывания этих двух прав не остаётся ни одного, которое Битрикс24 может привязать к вебхуку. Смешанный набор (placement + crm) по-прежнему проходит.

Затронутые эндпоинты: POST /v1/keys, PATCH /v1/keys/{id}, POST /v1/keys/{id}/rotate

Смежное изменение ответа: у личного ключа в поле scopes ответа на создание и правку больше не возвращаются placement и entity — вебхук их всё равно не несёт, и раньше ответ обещал право, которого у ключа нет. Ключи приложений и системные ключи не затронуты.

FIX-0806-25: сборка Python-приложения в галактике больше не падает на существующей версии пакета

Было

Деплой с runtime: python311* и requirements.txt падал с No matching distribution found for <пакет> — на версии, которая существует и ставится. Причина не в версии: контейнер собирался в изоляции и не знал про каталог пакетов, доступный из нашего облака. Рядом с этим текстом платформа не показывала ничего, и ошибка читалась как ваша.

Стало

Платформа задаёт образу каталог пакетов по умолчанию. Ваш собственный --index-url в requirements.txt или в команде установки по-прежнему сильнее нашего.

Если каталог всё же не ответит, error.category теперь INSTALL_REGISTRY_UNAVAILABLE (было GENERIC), а error.buildHint и одноимённое поле в GET /v1/infra/servers/:id несут понятную причину вместо пустоты. Значение аддитивное: прежние коды не менялись. Отказ терминальный — автоматически платформа его не повторяет.

2026-08-05

FIX-0805-1: перевыпуск ключа больше не осиротит зарегистрированного им бота

Было

Бот, зарегистрированный через POST /v1/bots, запоминает ключ, которым его завели. После POST /v1/keys/:id/rotate эта привязка оставалась на старом ключе: новый ключ получал 403 BOT_ACCESS_DENIED на любой вызов по этому боту, а когда старый ключ истекал по окончании льготного периода, бот замолкал совсем — входящие события копились в очереди, но забрать их (GET /v1/bots/:id/events) было уже некому. Вернуть бота можно было только через POST /v1/bots/:botId/transfer.

Стало

При перевыпуске бот переезжает на новый ключ вместе с приложением: вызовы по боту и опрос событий новым ключом продолжают работать без вмешательства.

Что по-прежнему не так, как хотелось бы

Боты, которыми управляет ИИ-агент или managed-бот, этой перепривязкой не затрагиваются — у них свой путь смены ключа, и попытка перевести их отсюда была бы рассинхроном с их собственными полями. Для них поведение не изменилось.

Влияние на интеграторов

Менять ничего не нужно. Ручной POST /v1/bots/:botId/transfer после перевыпуска ключа больше не требуется — он остаётся только для передачи бота между разными ключами.

FIX-0805-2: после перевыпуска ключа контейнер на общем хосте больше не теряется

Было

Перепривязка при POST /v1/keys/:id/rotate обходила стороной серверы на общем хосте (kind=GALAXY_APP): такой контейнер оставался за старым ключом, и после окончания льготного периода деплой, выполнение команд, загрузка файлов и просмотр логов новым ключом переставали его находить.

Стало

Контейнер на общем хосте переключается на новый ключ вместе с остальными серверами. Исключение осталось ровно одно и узкое: контейнер не переводится на ключ авторизации приложения (vibe_app_) — такая привязка необратима и ломает выкладку.

Что по-прежнему не так, как хотелось бы

Ключ, зашитый в переменные окружения самого контейнера, платформа не подменяет: его значения задаются при запуске контейнера. Обновите переменную и разверните приложение заново — на это есть льготный период ключа.

Влияние на интеграторов

Менять ничего не нужно. Вызовы новым ключом к контейнеру на общем хосте теперь продолжают работать после окончания льготного периода.

FIX-0805-3: перевыпуск ключа больше не отрывает от него сервер и приложение

Было

После POST /v1/keys/:id/rotate сервер и приложение, созданные с помощью этого ключа, продолжали внутренне числиться за старым ключом. Когда старый ключ истекал по окончании льготного периода, вызовы деплоя, выполнения команд, загрузки файлов и просмотра логов такого сервера с новым ключом переставали находить сервер.

Стало

После перевыпуска сервер, приложение и его живые токены доступа (api-bearer, минтятся через POST /v1/infra/servers/:id/access-tokens) переключаются на новый ключ вместе с ним — вызовы деплоя/exec/upload/logs новым ключом продолжают находить сервер, а обновление такого токена (POST .../access-tokens/:tokenId/refresh) больше не отказывает из-за несовпадения ключей.

Что по-прежнему не так, как хотелось бы

Старый ключ теряет доступ к серверу и приложению НЕМЕДЛЕННО, в момент перевыпуска — а не по истечении льготного периода. Формально ключ ещё активен эти часы (KEY_GRACE_PERIOD_HOURS), но сервер и приложение уже переехали на новый, поэтому вызовы старым ключом к этому серверу перестают находить его сразу.

Влияние на интеграторов

Менять ничего не нужно. Клиент, ловивший пропажу сервера после ротации как постоянную проблему, теперь видит непрерывный доступ — кроме самого старого ключа, который перестаёт видеть сервер раньше, чем истекает формально.

BC-0805-4: описание полей задачи совпало с её ответом, числа стали числами

Поддержка старого формата до: 04.02.2027

Было

GET /v1/tasks/fields описывал 92 поля, из которых 66 приходили именами вида MARK, NOT_VIEWED, STAGE_ID, CHAT_ID — в ответах GET /v1/tasks и GET /v1/tasks/:id таких ключей не бывает никогда. При этом бо́льшая часть ключей самого ответа в описании отсутствовала. Значения тоже расходились с объявленным типом: id, status, priority, groupId, chatId, responsibleId, createdBy, changedBy, closedBy, statusChangedBy, timeEstimate, timeSpentInLogs объявлены числами, а приходили строками ("289", "2"). Признаки «да/нет» приходили строками "Y" и "N", а "N" в любом языке истинна. Пустые tags, group, accomplicesData, auditorsData приходили пустым массивом при объявленном объекте. Поля, реально приходящие пустыми, не были помечены как допускающие пустое значение. chatId дополнительно менял тип между поверхностями: строка в списке, число в карточке.

Клиент, сгенерированный по такому описанию, не работал.

Стало

Описание и ответ называют одни и те же поля. Объявлено 69 полей; сырые имена верхним регистром из описания убраны (остаются только пользовательские поля портала и CHECKLIST — он доступен через эндпоинты чек-листа). Объявленные числами поля приходят числами, признаки «да/нет» — значениями true и false, пустые tags, group, accomplicesData, auditorsData — пустым объектом. 27 полей помечены как допускающие пустое значение. chatId — число на обеих поверхностях.

Что делать интеграторам. Проверьте в своём коде: сравнения значений со строками (status === "2", id === "289"), проверки признаков на непустую строку и обращения к пустым tags / group / accomplicesData / auditorsData как к массиву.

Поля subStatus (только в списке) и action, checklist, checkListTree, checkListCanAdd (только в карточке) приходят по-прежнему и в описание не включены — списки и карточки задач различаются составом ключей на стороне Битрикс24. Поле realStatus служит только для отбора и сортировки и в ответе не приходит — теперь это видно и машине, по признаку notReturned в описании.

FIX-0805-5: пауза при недоступной модели удлиняется, пока кластер не восстановится

Было

Пауза после отказа 429 ai_provider_cooldown всегда длилась около минуты. По её истечении платформа снова пускала весь поток в кластер моделей, и если тот ещё не восстановился, всё повторялось: минута ожидания — залп повторов — снова отказы. Значение Retry-After при этом всегда было одним и тем же, поэтому клиент, зашивший минуту константой, вёл себя так же, как читающий заголовок.

Стало

Первая пауза по-прежнему около минуты, но если по её истечении кластер всё ещё отвечает ошибками, следующая пауза удваивается — до четырёх минут максимум. Как только вызов проходит успешно, счёт сбрасывается и следующая пауза снова начинается с минуты. Заголовок Retry-After (и поле retryAfter в терминальном кадре потока) несёт актуальный остаток, поэтому берите время ожидания из ответа, а не из константы в своём коде.

NEW-0805-6: деплой предупреждает, когда проверенный путь приложения расходится с адресом для Bitrix24

Было

Деплой с healthPath, отличным от /, проверял приложение на подпути и возвращал 200, ничего не сообщая про адрес, по которому приложение открывает Bitrix24. Если приложение отвечало только на подпути, а appUrl оставался голым адресом сервера, плейсмент-фрейм открывал корень: деплой зелёный, приложение внутри Bitrix24 не открывается, и в ответе про это ни строчки.

Стало

POST /v1/infra/servers/{id}/deploy в таком сочетании добавляет строку в необязательный массив warnings (обычный JSON-ответ и событие done в SSE — так же, как уже устроены подсказки про displayName/description и про changelog). Подсказка называет обе половины расхождения — проверенный путь и открываемый адрес — и два выхода: раздавать сборку с / либо перенести подпуть в appUrl приложения через PATCH /v1/apps/{id}. Подпуть в appUrl поддерживается, запрета нет.

Подсказка не появляется, когда healthPath не задан или равен /, когда appUrl уже несёт путь, когда адрес приложения не на домене платформы и когда приложение ещё не связано с сервером. Форма ответа не меняется: warnings как был необязательным, так и остался.

FIX-0805-7: Привязка места встраивания называет причину отказа

Привязка места встраивания через ключ приложения больше не отвечает безымянным 502 BITRIX_UNAVAILABLE, когда Битрикс24 отклоняет регистрацию.

Было

POST /v1/placements/bind на любую причину отказа со стороны Битрикс24 отдавал один и тот же ответ — 502 BITRIX_UNAVAILABLE с текстом «Failed to register placement on Bitrix24 via dev key». Отличить «у приложения нет нужного права» от «не хватает обязательной настройки места» было нельзя: аккаунт возвращает оба случая одним непрозрачным кодом. Для чат-виджетов IM_SIDEBAR, IM_NAVIGATION, IM_TEXTAREA вызов без options.iconName попадал в тот же безымянный отказ.

Стало

Причина отказа называется:

  • 403 PLACEMENT_APP_GRANT_MISSING — место недоступно приложению Битрикс24. В details приходят требуемое право requiredScope, места, которые приложению доступны (availablePlacements, availablePlacementsTotal), и путь расширения прав в remediation.
  • 400 PLACEMENT_OPTIONS_REQUIRED — не хватает обязательной настройки места, которую платформа не смогла подставить (missing в details).
  • 400 PLACEMENT_NOT_REST_BINDABLE — код в принципе не привязывается через API.
  • 502 BITRIX_UNAVAILABLE остаётся для остальных случаев и теперь несёт в details признак placementInAppList, а при недоступной диагностике — diagnostics ("placement_list_skipped" или "placement_list_empty").

Значок чат-виджета больше не обязателен: если options.iconName не передан, платформа подставляет его сама и сообщает об этом в успешном ответе полем optionsDefaulted. Своё значение всегда важнее подставленного.

Справочник GET /v1/placements/available отдаёт по каждому коду три новых поля — requiredScope, requiresIconName, restBindable — и показывает десять кодов, которые привязывались, но в справочнике не значились, включая вкладки и панели задач. Блок placements.bindPrerequisite в данных ключа описывает требование права заранее.

Влияние на интеграторов

Менять вызовы не нужно. Если вы разбираете отказы привязки по коду — добавьте три новых кода; если полагались на обязательность options.iconName — поле стало необязательным, поведение с переданным значением не изменилось.

FIX-0805-8: туннель переживает перезапуск приложения, а автоопределение порта больше не уводит цель

Было

Агент в режиме автоопределения порта сбрасывал найденную цель по одному неудачному наблюдению. Перезапуск приложения (1-3 с) или ответ медленнее 1,5 с — и туннель отдавал страницу-заглушку ещё около 5 секунд после того, как приложение снова отвечало. Отдельно: цель пересчитывалась на каждом успешном скане, поэтому служебный процесс, поднявшийся на меньшем порту, забирал туннель у полностью здорового приложения — ответ HTTP 200 с чужим содержимым, без единой ошибки.

data.warning у PATCH /v1/infra/servers/:id/port и шаг tunnel_routing у POST /v1/infra/servers/:id/deploy обещали, что автоопределение сойдётся «за ~30 с».

Стало

Агент различает два сигнала. Пока порт приложения присутствует среди слушающих, цель удерживается; освобождается она только после нескольких подряд идущих наблюдений, что порт исчез (~15 с), либо — если порт слушает, но не отвечает — примерно через 2 минуты. Отвечающая цель больше не пересчитывается, кроме случая, когда текущая цель — 80/443, а ответил настоящий порт приложения.

Тексты data.warning и шага tunnel_routing переписаны честно. Автоопределение подтверждает новый порт примерно за минуту, если прежний порт освободился; если на прежнем порту остался живой отвечающий процесс, сканер намеренно удерживает его и сам не переключится — задайте порт явно через PATCH /v1/infra/servers/:id/port либо остановите тот процесс. POST /v1/infra/servers/:id/repair для этого случая не подходит: ремонт переустанавливает агента в режиме автоопределения, и выбор порта начнётся заново — с тем же результатом, если прежний процесс всё ещё отвечает.

Эффект приезжает на сервер вместе с обновлением агента до 1.3.7.

FIX-0805-9: подсказка MISSING_FIELDS у привязок реквизитов называет имена полей, а не отправляет за ними на другой эндпоинт

Было

Отказ 400 MISSING_FIELDS у POST /v1/requisite-links сообщал, что вместо camelCase принимаются и «сырые» имена в UPPER_SNAKE, и предлагал взять их из GET /v1/requisite-links/fields. По этому адресу их нет: ответ /fields отдаёт имена в camelCase. Читатель отказа шёл за списком туда, где список в другой нотации, и возвращался ни с чем.

Стало

Сообщение перечисляет шесть имён прямо в тексте: ENTITY_TYPE_ID, ENTITY_ID, REQUISITE_ID, BANK_DETAIL_ID, MC_REQUISITE_ID, MC_BANK_DETAIL_ID. Обе нотации по-прежнему принимаются на запись, код отказа и условие не изменились.

FIX-0805-10: схема API описывает обмен сессии и слоты встраивания, и честно говорит о своём покрытии

Было

Схема GET /v1/openapi.json описывалась как полная, а GET /v1/guide советовал нарезать её по областям, чтобы она поместилась в контекст ИИ-агента. Часть живых методов при этом в схему не попадала: агент, который добросовестно так и делал, приходил к выводу, что метода нет. Конкретный случай — обмен контекста встраивания на сессию: метод работал и был описан в документации, но в схеме под /v1/oauth/ находились только начало авторизации, обратный вызов, обмен кода и отзыв, поэтому к нам пришла просьба добавить то, что уже давно работало.

Стало

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

Главное: схема больше не обещает полноты, которой у неё нет. Пути сущностей строятся из живого реестра и полны, а рукописные разделы ещё дополняются — поэтому и в описании схемы, и в GET /v1/guide теперь прямо сказано: отсутствие пути не означает отсутствия метода, и как проверить наличие метода за один вызов (действительно отсутствующий путь отвечает ROUTE_NOT_FOUND, а живой — ошибкой проверки данных). Там же названы разделы, которых в схеме не будет никогда: входящие обработчики, которые платформа принимает, а не предоставляет, подсказки о неверном пути и разделы, доступные только управляющему ключу.

FIX-0805-11: клиент на элементе смарт-процесса записывается, а при выключенном блоке «Клиент» приходит отказ вместо мнимого успеха

Было

Поле contactIds у элементов смарт-процессов (PATCH /v1/items/:entityTypeId/:id) и у предложений (PATCH /v1/quotes/:id) было помечено только для чтения, поэтому запись отклонялась с 400 READONLY_FIELD. Основанием считалось, что привязка контактов меняется не через crm.item.update; проверка на реальном портале это не подтвердила — метод набор привязок меняет.

Вторая половина той же истории: если у смарт-процесса выключен блок «Клиент», Битрикс24 принимает contactId, contactIds и companyId, отвечает успехом и значение не сохраняет. Платформа этот успех передавала как есть — вызывающая сторона получала 200 на запись, которой не произошло, и узнать об этом можно было только повторным чтением элемента.

Стало

contactIds доступно на запись у элементов смарт-процессов и у предложений. Передавайте полный список: набор привязок заменяется целиком, а не дополняется, — первый контакт списка становится основным. Поле contacts (развёрнутые объекты, а не идентификаторы) остаётся только для чтения.

Запись клиента в смарт-процесс с выключенным блоком «Клиент» теперь отклоняется до обращения к Битрикс24 — 400 с кодом CLIENT_BLOCK_DISABLED. Сообщение называет поле, из-за которого отказ, и GET /v1/smart-processes/:entityTypeId, где в поле isClientEnabled видно состояние блока. Правило распространяется на все три поля клиента — contactId, contactIds, companyId, — потому что блок гейтит их одинаково.

Правило действует на всех поверхностях записи, включая пакетные: и POST /v1/items/:entityTypeId/batch, и POST /v1/batch. На пакетной поверхности сущности отказ относится ко всей пачке и называет индекс элемента, на общей — приходит по конкретному подвызову и остальные подвызовы не затрагивает.

Пустые значения под правило не попадают: 0, '', [] и null означают «клиента нет», а не запись клиента. Это важно для сценария «прочитать элемент, поменять одно поле, отправить объект целиком»: у элемента с выключенным блоком клиентские поля читаются именно такими и возвращаются в каждом обновлении. Если метаданные типа получить не удалось, запись пропускается — сбой чтения настроек не блокирует обновление.

Влияние на интеграторов

Запрос, который писал клиента в смарт-процесс с выключенным блоком, раньше получал 200, а теперь получит 400 CLIENT_BLOCK_DISABLED. Это и есть исправление: сохранения не было ни тогда, ни сейчас, но теперь об этом видно сразу. Либо включите блок «Клиент» у типа смарт-процесса, либо не отправляйте клиентские поля. Запросы к типам с включённым блоком не затронуты.

FIX-0805-12: фильтр в виде JSON-объекта применяется, а попытка ИЛИ получает свой код ошибки

Было

У параметра filter в списочных запросах было две формы записи, и вторая молча не работала. Скобочная (?filter[id]=3) применялась. Форма JSON-объектом (?filter={"id":3}) — та, которую показывают примеры в документации, — не распознавалась: параметр отбрасывался, запрос возвращал 200 и всю коллекцию целиком. Отличить работающий фильтр от отброшенного по ответу было нельзя.

Отдельная проблема — попытка выразить ИЛИ. Разборщик строки запроса поддерживает два уровня вложенности скобок, поэтому ?filter[$or][0][id]=1 до фильтра не доходил вовсе и читался как имя поля. На сделках это давало UNKNOWN_FILTER_FIELD с именем «поля» filter[$or][0][id] — ответ отправлял разбираться с именами полей вместо того, чтобы сказать, что ИЛИ одним фильтром не выражается. Более короткие написания при этом отвечали правильным INVALID_FILTER_OPERATOR, то есть одна и та же ошибка получала два разных ответа.

Стало

Обе формы filter равноправны: скобочная и JSON-объектом. Значение, которое не является ни тем, ни другим (строка не разбирается как JSON, разобралась в число, массив или null, либо параметр пришёл массивом — форма ?filter[]=), отклоняется с 400 и кодом INVALID_FILTER — отказ происходит до обращения к Битрикс24. Пустое значение ?filter= по-прежнему означает «без фильтра». Повторный ?filter=a&filter=b массивом не приходит: разборщик оставляет последнее значение, и оно отклоняется как не-JSON.

Тем же кодом INVALID_FILTER отклоняется запрос, в котором смешаны обе формы — ?filter={"id":3}&filter[amount]=5. Разборщик строки запроса пишет их в одно и то же место, поэтому вторая форма замещает первую и половина условий теряется, а ответ выглядит корректно отфильтрованным. Восстановить потерянную половину нельзя, поэтому запрос отклоняется.

Логические ключи $or, $and, $not и logic теперь отклоняются с 400 INVALID_FILTER_OPERATOR при любой глубине вложенности и на любой сущности, одним и тем же текстом: он называет $in для ИЛИ по одному полю, пакетный запрос для ИЛИ по разным полям и напоминает, что И — поведение по умолчанию. Поле, чьё имя лишь начинается с такого ключа (например logicGroup), под правило не попадает.

Влияние на интеграторов

Если запрос присылал filter в нераспознаваемой форме, раньше он получал 200 и всю коллекцию, а теперь получит 400 INVALID_FILTER. Это и есть исправление: прежний ответ выглядел успешным, но данные приходили неотфильтрованными. То же касается запросов, где смешаны обе формы: раньше применялась половина условий, теперь такой запрос отклоняется — соберите фильтр в одной форме. Работающие запросы — целиком скобочные или целиком JSON — не затронуты.

FIX-0805-13: причина падения сборки в галактике больше не подменяется справкой npm

Было

Приложение без файла блокировки зависимостей проходит через автоматическую доустановку: платформа пробует npm ci, и если тот отказывается — ставит зависимости обычным способом. Сам шаг при этом завершается успешно, но отказ npm ci остаётся в журнале сборки, а его последней строкой идёт справка вида «Run npm help ci for more info».

Если сборка потом падала по совсем другой причине — например на сборщике интерфейса или на проверке типов — короткое поле provisionError показывало именно эту справку. Она выглядит как совет по установке зависимостей, поэтому реальная причина не читалась: разработчик пересобирал приложение снова и снова, разбираясь с шагом, который в действительности прошёл.

Стало

Служебные и справочные строки npm («Run npm help … for more info», «command failed», «command sh -c …») больше не могут стать заголовком ошибки — они отбрасываются наравне с уже отбрасываемыми ссылками на файл журнала.

Заодно платформа научилась узнавать отказы сборщиков интерфейса и проверки типов: сообщение о неудачном преобразовании файла, строка «ожидалось одно, встретилось другое», диагностика компилятора типов, неудачное разрешение импорта. Когда конкретной строки нет, берётся сообщение самого инструмента сборки — оно хотя бы называет, что упало. Полный журнал по-прежнему доступен в buildLog.

FIX-0805-14: справочник сотрудников доступен через личный ключ владельца сервера

Было

GET /v1/infra/servers/:id/b24-users у сервера, привязанного к ключу авторизации приложения, отдавал пустой список с подсказкой до тех пор, пока приложение не будет авторизовано на портале — даже если у владельца сервера был рабочий личный ключ.

Стало

Если ключ сервера и связанное приложение не дают доступа к порталу, справочник берётся через активный личный ключ владельца сервера. Формат ответа не изменился; подсказка приходит только когда ни один источник не сработал.

FIX-0805-15: на self-hosted портале отказ модуля по подписке снова завершает выписку

Было

Опубликованная 4 августа правка делала отказ модуля по подписке неокончательным: установка приложения (POST /v1/apps) на self-hosted портале повторялась через ключ разработчика вместо того, чтобы вернуть 403.

Стало

Правка отозвана. Отказ снова окончателен: запрос отвечает 403 с кодом B24_MARKET_SUBSCRIPTION_REQUIRED, второй способ выдачи не пробуется. Это то же поведение, что действовало до 4 августа. Облачные порталы не затронуты ни той правкой, ни её отзывом.

Влияние на интеграторов

Если вы опирались на заметку от 4 августа — повтор через ключ разработчика больше не выполняется, и ответ определяется наличием подписки на портале. Клиентам, которые видят этот 403, нужна активная подписка Маркетплейса.

NEW-0805-16: регион при создании сервера стал необязательным

Было

Создание отдельного сервера требовало тройку provider + plan + region. Запрос без региона отклонялся с 400 INVALID_REQUEST и сообщением о том, что все три поля обязательны. То же самое действовало на создание галактики.

Стало

region можно не присылать — платформа сама подставит регион по умолчанию для указанного провайдера (сначала предпочтительный, иначе первый доступный из каталога). Обязательными остаются provider и plan. Присланный регион по-прежнему уважается: тот, кто указывает его явно, получает ровно то, что просил. Если у провайдера нет ни одного региона, ответ — 400 INVALID_REGION с указанием провайдера.

Изменение затрагивает POST /v1/infra/servers и создание галактики.

NEW-0805-17: поля шести справочников приходят с названием и описанием

Справочник полей — это ответ /fields, по которому клиент или ИИ-агент понимает, что за поле перед ним. У шести сущностей он не отвечал на этот вопрос: поле описывалось только типом и признаком «только для чтения», а что именно в нём лежит, приходилось искать в документации.

Теперь название (label) и описание (description) есть у всех объявленных полей: GET /v1/payments/fields — 44 поля, GET /v1/basket-items/fields — 27, GET /v1/pages/fields — 27, GET /v1/catalog-sections/fields — 10, GET /v1/items/:entityTypeId/fields — 34, а у GET /v1/statuses/fields подпись получило служебное поле extra, единственное из одиннадцати, которое её не имело.

В описаниях названы вещи, на которых легко ошибиться. У оплат: datePayBefore Битрикс24 помечает устаревшим, companyId принимает и не использует, psStatus — это флаг Y/N, а не текст статуса, priceCod и externalPayment относятся к коробочной версии. У страниц прямо сказано, какие поля приходят строкой "Y"/"N" (deleted, public, sys, sitemap, folder) — в отличие от булева active. У позиций корзины названы коды единиц измерения и то, что properties и reservations приходят только в карточке, а в списке их нет.

Заодно объявлены поля, которые API уже возвращал в данных, но в справочнике не описывал: у элементов смарт-процессов — entityTypeId и блок UTM-меток (utmSource, utmMedium, utmCampaign, utmContent, utmTerm), у компаний — одиннадцать полей: разложенные по типам телефоны и адреса e-mail (phoneWork, phoneMobile, phoneMailing, emailWork, emailHome, emailMailing), контакт открытой линии imol, фактический и юридический адреса, entityTypeId и служебная строка поиска searchContent — у последней в описании прямо сказано, что её состав может меняться без предупреждения и опираться на неё не стоит.

Ключи добавляются к описанию поля, прежние type и readonly у ранее описанных полей не менялись — ради самих подписей менять ничего не нужно. Но в этом же выпуске есть записи FIX, где запись части полей ужесточилась: у компаний, у элементов смарт-процессов и у поля «активна» страниц сайта попытка записать то, что Битрикс24 всё равно не сохраняет, теперь отклоняется вместо ложного успеха. Если ваш код передаёт эти поля в теле, прочитайте те записи — там сказано, что убрать.

FIX-0805-18: у объявленных полей компаний пустое значение приходит как null, а запись в них больше не игнорируется молча

Одиннадцать полей компаний и шесть полей элементов смарт-процессов раньше приходили в данных, но справочник /fields их не описывал. Пока поле не описано, платформа пропускает его значение как есть и не проверяет запись — отсюда два следствия, которые видны клиенту.

Было

У полей компаний emailWork, emailHome, emailMailing, phoneWork, phoneMobile, phoneMailing, imol, address, addressLegal и searchContent незаполненное значение приходило пустой строкой "". Запись любого из них — как и entityTypeId, как и UTM-меток у элементов смарт-процессов — принималась с успехом и молча ничего не меняла: Битрикс24 эти поля из тела запроса не сохраняет. Так вели себя и создание, и обновление, и оба пакетных запроса: POST /v1/companies с полем address возвращал 201, компания создавалась, а адрес терялся.

Стало

Незаполненное значение этих полей приходит как null — так же, как у всех остальных строковых полей платформы, поэтому проверка if (value) работает единообразно. Запись любого из них в теле отклоняется с 400 READONLY_FIELD — и при создании, и при обновлении, и в подзапросах пакетных запросов: молчаливого «успеха без результата» больше нет. Заодно у компаний заработали фильтр и сортировка по этим полям — например filter[phoneWork] — раньше запрос отклонялся как обращение к неизвестному полю. Это следствие описания поля, а не отдельная возможность: у служебной строки searchContent фильтр тоже стал приниматься, но её состав может меняться без предупреждения, поэтому опираться на него не стоит. Записываются значения по-прежнему: телефоны и адреса e-mail — через мультиполя phone и email, адреса — в реквизитах компании, тип сущности задаётся адресом запроса.

Влияние на интеграторов

Если код читает эти поля компаний и рассчитывает на строку (например берёт длину или зовёт trim), добавьте проверку на null. Если код передавал любое из этих полей в теле создания или обновления — уберите его: значение всё равно никогда не сохранялось, а теперь запрос отклоняется целиком, поэтому вместе с ним не применятся и остальные поля тела. Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что значение молча терялось, — сохранять было бы нечего. Записи всех остальных полей и форма ответа list/get для описанных ранее полей не изменились.

FIX-0805-19: у страниц сайта поле active стало только для чтения — включение идёт через публикацию

Было

GET /v1/pages/:id/fields описывал active как обычное записываемое поле, и запрос с ним проходил: POST /v1/pages и PATCH /v1/pages/:id возвращали успех. Значение при этом терялось. Битрикс24 не принимает ACTIVE ни в landing.landing.add, ни в landing.landing.update — их контракт этого поля не объявляет, а новая страница всегда создаётся неактивной. Клиент получал «готово» и неопубликованную страницу, а расхождение обнаруживал, когда приходил смотреть на сайт.

Стало

active помечено readonly. Передача его в теле создания или обновления отклоняется с 400 READONLY_FIELD. В ответе list/get и в справочнике /fields поле остаётся — читается оно по-прежнему.

Публикация и снятие с публикации выполняются отдельными вызовами, которые действительно работают: POST /v1/pages/:id/publication и POST /v1/pages/:id/unpublish.

Влияние на интеграторов

Если код передавал active в теле создания или обновления страницы — уберите его и вызовите публикацию отдельным запросом. Значение всё равно никогда не сохранялось, но теперь запрос отклоняется целиком, поэтому вместе с ним не применятся и остальные поля тела: заголовок, символьный код, описание. Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что значение молча терялось, — сохранять было бы нечего.

NEW-0805-20: создание сервера с кодом внутри запроса встало в общую очередь тяжёлых запросов

Запрос на создание сервера может нести архив с кодом прямо в теле — в поле source.content. Такой запрос дорого обходится по памяти, и раньше он был единственным из тяжёлых, кто шёл без очереди: POST /:id/deploy и POST /:id/upload уже ограничивали число одновременных, а создание — нет.

Было

Одновременные создания серверов с архивом внутри запроса ничем не ограничивались. Ответ всегда шёл по существу — либо успех, либо ошибка проверки полей.

Стало

Такое создание встало в тот же счётчик, что и загрузка кода. Когда предел выбран, приходит 429 с кодом DEPLOY_BACKEND_BUSY и заголовком Retry-After: 30. Запросы без source (обычное создание сервера) и запросы со ссылкой вместо архива ограничения не касаются.

Чтобы не зависеть от очереди совсем, создавайте сервер без source, а код загружайте отдельным запросом со ссылкой — {source: {url: ...}}.

FIX-0805-21: repair восстанавливает туннель, даже когда входящий SSH недоступен

Было

POST /v1/infra/servers/:id/repair отчитывался об успехе шага serial_console, затем падал на ssh_install с SSH install failed (exit 255) примерно через 10 секунд, и сервер оставался DISCONNECTED — то есть документированное восстановление до CONNECTED не происходило, а deploy и exec на таком сервере оставались заблокированы. Отдельно: у сервера без публичного IP установка агента через serial console не срабатывала никогда.

Стало

Шаг serial_console больше не подтверждает успех, если открыть файрвол не удалось. Установка агента через serial console исправлена и теперь работает как для сервера без публичного IP, так и как резервный путь: если попытка по входящему SSH не удалась, repair доустанавливает агента out-of-band через serial console (агенту нужен только исходящий коннект). Имена шагов в GET /repair-status не изменились; при провале обоих путей поле error содержит обе причины через ; serial fallback:. Резервная попытка добавляет до двух минут к уже неуспешному вызову.

FIX-0805-22: встраивание: Битрикс24 назвал причину — называем её и мы

Было

Когда Битрикс24 отказывал в привязке места встраивания словами «приложение не найдено» или «доступ запрещён», POST /v1/placements/bind отвечал 502 BITRIX_UNAVAILABLE. Названную причину было видно только в служебных полях ответа, а по коду ответа отличить «приложения нет на аккаунте» от «нет прав на установку» было нельзя.

Стало

Два отказа приходят со своим кодом:

  • 404 B24_EMBEDDING_APP_NOT_FOUND — Битрикс24 не знает идентификатор приложения: локальное приложение удалили или переустановили. Лечение: создать локальное приложение заново и вызвать POST /v1/apps/:id/relink-oauth с новыми bitrixClientId и bitrixClientSecret.
  • 403 B24_EMBEDDING_INSTALL_DENIED — отказ доступа, устоявший против проверки активной подписки: у пользователя, чьим ключом разработчика идёт вызов, нет права ставить локальные приложения и/или нет доступа к самому приложению.

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

Третий отказ — нехватка права у служебного ключа — раньше на коробочном аккаунте выдавался за требование прав администратора, хотя выдача прав администратором ничего не меняет: право фиксируется при выписке ключа. Теперь он приходит как 403 BOX_WEBHOOK_NOT_DEVELOPER_KEY — тем же кодом, что уже отдают разделы кабинета, и до проверки подписки.

Влияние на интеграторов

Клиент, который ветвился на 502 для этих причин, теперь получает 4xx — обработку ошибок стоит перевести на коды. Новые коды перечислены в placements.bindPrerequisite.errorCodes у GET /v1/me, причём ровно те, что аккаунт реально может получить.

Затронутые эндпоинты: POST /v1/placements/bind, GET /v1/me

FIX-0805-23: личный ключ без вебхука Битрикс24 объясняет, чего ему не хватает

Было

Личный ключ (vibe_api_*) без вебхука Битрикс24 отвечал на каждый вызов сущности 401 TOKEN_MISSING с текстом «API key has no OAuth tokens configured. Key may need re-authorization.» У такого ключа OAuth нет в принципе — он ходит в портал по вебхуку, поэтому совет про повторную авторизацию вёл не туда. GET /v1/me при этом отвечал 200 и выглядел здоровым, а список ключей не отличал рабочий ключ от нерабочего.

Стало

Текст для личного ключа называет отсутствующий вебхук и адресует за причиной в /v1/me. Код ответа не изменился (TOKEN_MISSING), появилось необязательное поле error.details с машиночитаемой причиной: B24_MARKET_SUBSCRIPTION_REQUIRED, B24_MARKET_TRIAL_USED, INT_TARIFF_REQUIRED, VIBE_SCOPES_ONLY или WEBHOOK_NOT_CONFIGURED — плюс paywallCode и upgradeUrl, когда причина тарифная. details приходит на /v1/{сущность} и POST /v1/batch.

GET /v1/me для личного ключа несёт блок b24Credentials — ready, а при ready: false ещё reason, paywallCode, upgradeUrl и подсказку hint, когда состояние доступа стоит перечитать. Список и карточка ключа (GET /v1/keys, GET /v1/keys/{id}) отдают признак b24Ready: true — на ключе есть креды для вызовов портала, false — их нет, null — к ключу неприменимо (ключ авторизации или управленческий ключ). Секреты в ответах не появились.

FIX-0805-24: субдомен приложения отвечает машине JSON, а не страницей, и переживает короткий разрыв туннеля

Было

Пока сервер просыпался или его туннель переподключался, любой запрос к субдомену приложения получал HTML-страницу пробуждения со статусом 503. Браузер её опрашивал и в итоге попадал в приложение, а вебхук или интеграция получали разметку вместо ответа: тело, метод и путь запроса отбрасывались, и понять по ответу, применилось ли действие, было нельзя. Событие Битрикс24, пришедшее в это окно, терялось целиком.

Стало

Вызывающий, который не браузер (есть Authorization, X-Api-Key, Accept: application/json, X-Requested-With, Sec-Fetch-Dest: empty, либо это POST/PUT/PATCH/DELETE), получает обычный конверт ошибки с кодом и заголовком Retry-After: BH_SERVER_WAKING (503), BH_TUNNEL_CONNECTING (503), BH_TUNNEL_DISCONNECTED (502), BH_APP_STARTING (503), BH_SERVER_ERROR (500), BH_SERVER_NOT_FOUND (404), BH_WAKE_BLOCKED (402). У первых четырёх заголовок Retry-After и поле error.retryAfter совпадают.

Кроме того, короткий разрыв туннеля на уже работающем сервере теперь переживается незаметно: запрос удерживается до 15 секунд, и если туннель успевает вернуться, он доставляется в приложение и вызывающий получает настоящий ответ. Холодный старт в это окно не укладывается — там по-прежнему нужен повтор со стороны вызывающего.

Браузерные страницы пробуждения, запуска и ошибок не изменились, включая их опрос. Опрос страницы (?_bh_poll=) не затронут.

FIX-0805-25: лимит на создание обращения считается по вашему ключу, а не общий на всех

Было

POST /v1/feedback отвечал 429 RATE_LIMITED, даже когда ваш ключ отправил меньше пяти обращений в минуту: счётчик был общим для всех вызывающих сразу, поэтому чужой поток обращений исчерпывал ваш лимит. Наблюдалось это как редкий необъяснимый отказ на первом же вызове.

Стало

Счётчик ведётся отдельно для каждого ключа авторизации: чужой поток обращений ваш лимит больше не расходует.

Влияние на интеграторов

Менять ничего не нужно. Отказы, вызванные чужим потоком обращений, на этом методе исчезают. Пороговое число оставьте прежним: обработку 429 RATE_LIMITED с повтором по заголовку Retry-After стоит сохранить — она по-прежнему единственный надёжный способ узнать свой лимит.

FIX-0805-26: пагинация конфигураций открытых линий: окно больше не смещается дважды

Было

GET /v1/openline-configs и POST /v1/openline-configs/search применяли limit и offset дважды: сначала их учитывал Битрикс24, затем обёртка повторно вырезала окно из уже готовой страницы. Клиент получал пустую или сдвинутую выборку без ошибки: при limit=3&offset=3 ответ приходил пустым, при offset=2 первая запись оказывалась четвёртой, а не третьей. Поле hasMore считалось по той же урезанной странице и на полной странице всегда приходило false, из-за чего обход страниц завершался на первой.

Отдельно: дробное значение limit меньше единицы (например limit=0.5) обнулялось при округлении, и нижележащий метод трактовал нулевой лимит как «без ограничения». Ответ приходил пустым с hasMore: true — обход по этому признаку не завершался никогда.

Стало

Окно вырезает Битрикс24, обёртка его больше не двигает. Запрос уходит на одну запись шире запрошенного лимита — по её наличию и определяется hasMore; на потолке limit=200 признак выводится из того, что страница пришла заполненной целиком, поэтому за последней полной страницей возможен один пустой ответ. Дробный limit меньше единицы приводится к значению по умолчанию 50, как это уже происходило с limit=0 и нечисловыми значениями.

Влияние на интеграторов

Менять код не нужно. Обход страниц по offset и hasMore начинает возвращать полную выборку — прежде часть записей терялась молча. Семантика total не изменилась: это по-прежнему число записей в текущем окне, а не во всей выборке, и цикл постраничного чтения ограничивают по hasMore.

BC-0805-27: агрегат по крупной воронке: счётчики по стадиям без выгрузки сделок, отказ вместо усечения

Поддержка старого формата до: 05.02.2027

Оба изменения выкатываются выключенными и включаются платформенным администратором по порталам.

Было

POST /v1/{entity}/aggregate, которому для ответа нужны строки (числовые операции и/или groupBy), при total > 5000 всё равно выгружал первые 5000 записей и помечал ответ meta.truncated: true. На крупной воронке эта выгрузка не успевала — клиент ждал двадцать секунд и получал обрыв связи вместо ответа.

Стало

С включённым режимом отказа такой запрос сразу отвечает 422 AGGREGATION_LIMIT_EXCEEDED и не выгружает ни одной строки. В тексте ошибки — что делать: сузить фильтр, запросить только количество, либо (для сделок) взять количество с группировкой по стадии, которое считается без чтения строк.

С включённой группировкой по стадиям POST /v1/deals/aggregate с groupBy: ["stageId"] или ["stageSemanticId"] и скалярным categoryId в фильтре отвечает на воронке любого размера: количество по каждой стадии берётся отдельными дешёвыми подсчётами на стороне портала. В ответе meta.recordsProcessed: 0, meta.truncated: false, meta.aggregatePath: "fanout" и meta.stageCountDelta — расхождение между общим числом и суммой по стадиям (0, когда разбиение полное). Числовые операции по стадиям остаются доступны, пока суммарный размер групп укладывается в 5000.

Что не изменилось: запрос только количества без группировки (aggregate: [{"function": "count", "field": "*"}]) отвечает как раньше — одним подсчётом, на любом объёме; выборки до 5000 записей обрабатываются как прежде.

2026-08-04

BC-0804-1: V1 /wake и /start будят galaxy-приложение через хост

Поддержка старого формата до: 22.08.2026

Было

POST /v1/infra/servers/:id/wake и /start для kind=GALAXY_APP отвечали 422 VM_MISSING (у приложения нет cloud VM) и советовали редеплой, даже когда контейнер просто спал после idle. Периодические задачи после первого сна не могли подняться через API.

Стало

Для размещённого galaxy-приложения оба verb'а вызывают host-mediated wake (как кабинет): будят общий хост при необходимости и стартуют контейнер. Успех — HTTP 200. ?wait=true на galaxy не ждёт RUNNING до таймаута / WAKE_TIMEOUT — после cold wake приложение может остаться SLEEPING; опрашивайте GET. Хост с preventWake блокирует и /wake, и /start (403 SERVER_WAKE_BLOCKED или 402 paywall-коды MARKETPLACE_REQUIRED / … — не маркеры TRIAL_EXPIRED). Слот без host/galaxyId → 404 GALAXY_HOST_NOT_FOUND (не 422 VM_MISSING). Для STANDALONE VM_MISSING / override /start без изменений.

BC-0804-2: тип архива при сохранении версии сверяется с его первыми байтами

Поддержка старого формата до: 03.08.2026

Было

Сохранение версии исходников — POST /v1/infra/servers/:id/sources и POST /v1/apps/:id/sources — верило заголовку Content-Type на слово. Архив zip, отправленный с Content-Type: application/gzip, принимался, сохранялся как gzip и получал имя с расширением .tar.gz; выкладка такой версии на сервер падала на распаковке с невнятной ошибкой чтения архива. Автосохранение версии после выкладки записывало формат как gzip всегда, независимо от того, что было в теле запроса.

Стало

При приёме читаются первые байты архива. Прямое противоречие между заявленным типом и содержимым — заявлен application/gzip, а байты zip, или наоборот — отвергается с кодом 415 UNSUPPORTED_ARCHIVE_FORMAT; тело ответа содержит error.hint.declared и error.hint.detected. Типы application/x-tar и application/octet-stream под этот отказ не попадают никогда: их сигнатура не читается в первых восьми байтах, поэтому противоречие с ними установить нечем. Нераспознанное содержимое принимается как раньше.

Автосохранение после выкладки отказа не даёт вообще: там тип архива клиент не заявляет, и распознанные байты просто записываются честно — zip сохраняется как zip и при повторной выкладке уходит в нужный распаковщик.

Что делать интеграторам

Отправляйте Content-Type, соответствующий архиву (application/gzip для tar.gz, application/zip для zip), либо application/octet-stream, если тип неизвестен. Клиенты, которые уже отправляют корректный заголовок, ничего не меняют.

NEW-0804-3: названия и описания полей у разделов товаров, шаблонов реквизитов и банковских реквизитов

Что нового

GET /v1/product-sections/fields, GET /v1/requisite-presets/fields и GET /v1/bank-details/fields возвращали только type и readonly — ни одно поле не имело человекочитаемого названия, поэтому по справочнику нельзя было понять, что означает, например, rqAccNum. Теперь названия объявлены у всех полей: 7 у разделов товаров, 11 у шаблонов реквизитов, 34 у банковских реквизитов. У шаблонов реквизитов и банковских реквизитов вместе с названием приходит и описание.

Ответ дополняется ключами label и description рядом с уже существующими type и readonly — прежние поля ответа не изменились.

Динамический справочник Битрикс24 эти названия дать не мог: он дополняет только те поля, которых нет в статической схеме, поэтому у объявленных полей его подписи отбрасывались. Операция aggregate у банковских реквизитов остаётся отключённой.

NEW-0804-4: `limit=0` больше не игнорируется молча

Что нового

limit=0 не является размером страницы: параметр отбрасывается, и применяется значение по умолчанию. Раньше это происходило без единого признака — ответ приходил с кодом 200 и полной страницей записей, как будто параметр учли. Теперь в такой ответ добавляется предупреждение в meta.warnings:

JSON
{
  "meta": {
    "warnings": [
      {
        "code": "LIMIT_ZERO_IGNORED",
        "field": "limit",
        "message": "limit=0 is not a page size and was ignored. Valid range: 1..5000; pass an explicit limit (e.g. 5000) to read the whole collection."
      }
    ]
  }
}

Работает на GET /v1/{entity} и POST /v1/{entity}/search для всех сущностей. Само значение не изменилось — прежние вызовы продолжают возвращать столько же записей, сколько и раньше; чтобы прочитать всю коллекцию, передайте явный limit (максимум 5000).

FIX-0804-5: банковские реквизиты: `entityTypeId` помечен как невозвращаемое поле

Было

GET /v1/bank-details/fields описывал entityTypeId обычным числовым полем — читаемым и записываемым. Про то, что Битрикс24 это значение принимает при создании, но никогда не возвращает при чтении, было сказано только в тексте описания поля. Клиент, который строит модель по машиночитаемому справочнику, а не по прозе, добавлял поле в тип чтения, а на месте числа получал пустоту.

Стало

У поля появился признак notReturned: true — в GET /v1/bank-details/fields, в GET /v1/guide и в OpenAPI-схеме (там это аннотация x-notReturned и фраза в описании). Тот же признак этот выпуск ввёл для serverName у телефонных линий.

Поле остаётся доступным для записи: POST /v1/bank-details по-прежнему принимает entityTypeId (всегда 8 — владелец-реквизит). Признак говорит только про чтение.

Влияние на интеграторов

Ничего менять не нужно — признак аддитивен. Если вы генерируете типы по справочнику, entityTypeId можно исключить из модели чтения и оставить в модели создания.

FIX-0804-6: discover называет настоящий идентификатор в пути

Было

GET /v1/guide и OpenAPI-схема показывали путь одиночной записи как /:id у всех сущностей. У четырёх это неправда: smart-processes адресуется публичным entityTypeId (1030+), telephony-lines — номером линии, а bizproc-robots и bizproc-activities — кодом (code), причём собственного id у них нет вовсе. Клиент, прочитавший {id}, подставлял собственное поле id записи и получал 404 SMART_PROCESS_NOT_FOUND, где то же значение названо entityTypeId — притом что опубликованная документация уже писала :entityTypeId и :code.

Стало

GET /v1/guide показывает /v1/smart-processes/:entityTypeId, /v1/telephony-lines/:number, /v1/bizproc-robots/:code и /v1/bizproc-activities/:code; у остальных сущностей путь остался /:id. В OpenAPI имя параметра осталось id: имя path-параметра обязано совпадать с плейсхолдером в шаблоне пути, а сам адрес не менялся — вместо этого описание параметра теперь называет настоящий идентификатор. Там, где у сущности нет собственного поля id (телефонные линии и оба bizproc-справочника), описание так и говорит, а не отправляет сверяться с полем, которого не существует. Описание поля id у smart-processes тоже уточнено.

Влияние на интеграторов

Ничего менять не нужно: маршруты и коды ответов не изменились, изменились только описания. Если вы подставляли в путь smart-processes внутренний id записи, теперь понятно, почему приходил 404 — используйте entityTypeId; для роботов и действий бизнес-процессов — code.

FIX-0804-7: телефонные линии: `name` объявлен nullable, `serverName` помечен как невозвращаемый

Было

GET /v1/telephony-lines/fields объявлял name обычной строкой, хотя у линии без названия приходит null — модель, построенная по справочнику, ломалась на первом же таком значении. Поле serverName выглядело в справочнике обычным читаемым полем, хотя Битрикс24 его не хранит и никогда не возвращает.

Стало

У name появился признак nullable: true — и в GET /v1/telephony-lines/fields, и в GET /v1/guide, и в OpenAPI-схеме (там это форма type: ["string","null"]). У serverName появился признак notReturned: true в тех же местах, а в OpenAPI — аннотация x-notReturned и фраза в описании. Поле осталось в справочнике намеренно: запись в него по-прежнему отклоняется с 400 READONLY_FIELD, и клиент должен иметь возможность найти поле и прочитать причину.

Влияние на интеграторов

Ничего менять не нужно — признаки аддитивны. Если вы генерируете типы по справочнику, name станет string | null, а serverName можно исключить из модели чтения.

FIX-0804-8: рабочие группы: работают `limit > 50` и точное смещение

Было

GET /v1/workgroups?limit=500 возвращал первые 50 записей независимо от того, сколько групп доступно, и meta.hasMore не помогал дочитать остальное. Причина — метод списка у этой сущности называется не .list, а sonet_group.get, и признак «это список» у неё не был объявлен, поэтому ни limit, ни авто-пагинация до Битрикс24 не доходили. Смещение при этом округлялось вниз до границы страницы: offset=30 отдавал записи с первой, а не с тридцать первой.

Стало

limit доходит до Битрикс24, и при limit > 50 платформа сама читает нужное число страниц — как это давно работает у пользователей, отделов и хранилищ. Смещение стало точным: offset=30 начинает выдачу с 31-й записи. То же поведение на POST /v1/workgroups/search и в списочном подзапросе POST /v1/batch.

Влияние на интеграторов

Если вы листали рабочие группы вручную и компенсировали округление смещения на своей стороне (например, отбрасывали первые записи страницы), эту компенсацию нужно убрать — иначе записи будут пропускаться дважды. Для глубокого листания надёжнее курсор: фильтр {">id": последнийId} с сортировкой по id.

FIX-0804-9: удаление приложения доводит снятие регистрации с портала до конца

Было

DELETE /v1/apps/:id снимал приложение с портала одной попыткой. Если портал был недоступен, права отозвали или у автора приложения не оказалось ключа разработчика, попытка молча пропадала: приложение удалялось у нас, но оставалось установленным на портале Битрикс24 — его пункт оставался в меню. Встройки при этом отвязывались только у опубликованных в каталоге приложений и только на облачных порталах.

Стало

Исход попытки сохраняется, и незакрытое снятие повторяется в фоне с нарастающей паузой, пока портал не подтвердит удаление. Встройки снимаются у любого приложения независимо от того, публиковалось ли оно в каталоге.

Влияние на интеграторов

Ответ эндпоинта не изменился — по-прежнему 204 сразу после удаления на нашей стороне. Изменился результат: пункт приложения на портале теперь исчезает и в тех случаях, когда раньше оставался навсегда.

NEW-0804-10: Отказ по таймауту шлюза на выполнении команды несёт подсказку по восстановлению

Отказ с кодом GATEWAY_TIMEOUT у POST /v1/infra/servers/:id/exec теперь дополнен объектом hint с полями reason, recovery и recoveryAction — так же, как это уже сделано для EXEC_TIMEOUT и агентского EXEC_BUSY. Подсказка говорит главное: код возврата не получен, поэтому исход команды неизвестен и она может продолжать выполняться на сервере. Повторный запуск вслепую способен создать вторую копию поверх первой, поэтому сначала стоит выяснить фактическое состояние — прочитать логи или выполнить короткую read-only команду. Для работы, которая заведомо не укладывается в предельное время, подсказка направляет на фоновую задачу.

Поля code и message не изменились — подсказка добавлена аддитивно, и прежние вызовы продолжают работать. Приходит в обоих режимах ответа: в JSON-конверте и SSE-событием error.

FIX-0804-11: отказ при недоступной модели — 429 с выдержкой вместо 502

Было

Когда доступ к моделям временно закрывался из-за череды неудачных вызовов, POST /v1/chat/completions и POST /v1/embeddings отвечали 502 с кодом AI_PROVIDER_UNAVAILABLE, а в error.message приходила внутренняя служебная строка вместо объяснения. Заголовка Retry-After не было, поэтому клиенту нечего было ждать — типовая реакция библиотеки на 5xx — повторить сразу, что продлевало недоступность.

Стало

Тот же отказ приходит как 429, error.type: "rate_limit_exceeded", error.code: "ai_provider_cooldown" (в потоковом кадре — то же имя в верхнем регистре, как у остальных кодов этого семейства), с заголовком Retry-After в секундах; в потоковом ответе то же значение приходит полем retryAfter внутри терминального кадра ошибки. Значение — остаток окна ожидания, не меньше секунды. error.message больше не содержит служебных строк и адресов. Отказ временный: дождитесь Retry-After и повторите тот же запрос.

FIX-0804-12: деплой архива формой multipart сохраняет версию исходников даже при неудачном деплое

Было

POST /v1/infra/servers/:id/deploy с телом multipart/form-data сохранял архив в хранилище исходников только после успешного деплоя. Если деплой падал, версия не появлялась вовсе — разбирать было нечего, а повторная выкладка требовала заново отправить те же байты.

Стало

Архив помещается в хранилище исходников до старта деплоя, и деплой продолжается по ссылке на эту версию. Версия остаётся в истории при любом исходе: при успехе она получает deployStatus: "success", при неудаче — "failed". Блок data.source в ответе не изменился: autoSaved, savedVersionId, sha256 и newVersion заполняются как раньше, повторная отправка тех же байт по-прежнему дедуплицируется и новой версии не создаёт.

Появился новый код отказа SOURCE_DEPOT_UNAVAILABLE (502): хранилище исходников не приняло архив, деплой не стартовал, запрос можно повторить без изменений. Раньше такой сбой приходил как VALIDATION_ERROR (400), то есть выглядел ошибкой запроса.

Влияние на интеграторов

Действий не требуется. Список версий (GET /v1/infra/servers/:id/sources) у клиентов, деплоящих формой multipart, теперь может содержать версии неудавшихся выкладок — они помечены deployStatus: "failed".

NEW-0804-13: поля адресов приходят с человекочитаемыми подписями и описаниями

Раньше GET /v1/addresses/fields у шести полей из четырнадцати отдавал в title имя поля Битрикс24 — TYPE_ID, ENTITY_TYPE_ID, ENTITY_ID, COUNTRY_CODE, ANCHOR_TYPE_ID, ANCHOR_ID. Показать такую подпись пользователю нельзя, а значения кодов приходилось искать в документации.

Теперь у этих шести полей в title приходит подпись на языке портала, а у полей, которым есть что добавить к подписи, появился ключ description с назначением поля и расшифровкой кодов: типы адреса (все двенадцать, с 1 по 12 — какие из них доступны, зависит от страновой зоны портала) и типы владельца (1 — лид, 3 — контакт, 4 — компания, 8 — реквизит). У поля countryCode описание не обещает формат: в документации REST Битрикс24 это поле помечено как неиспользуемое и оставленное для обратной совместимости. У кода 1 расшифровка дополнена подписью из англоязычного интерфейса Битрикс24 — Street address: сам Битрикс24 называет этот тип по-разному в русской и английской версиях, и без оговорки словарь расходился бы с подписью, которую пользователь видит в интерфейсе.

Подписи, которые Битрикс24 отдаёт сам, не изменились. Рядом с title та же подпись теперь приходит и в ключе label — так же, как у остальных сущностей, поэтому читать подписи можно одним способом на любой сущности. Ключи description и label добавляются к описанию поля, прежние ключи остаются на месте, поэтому менять ничего не нужно.

Заодно исправлены расшифровки кодов типа адреса в документации операций создания, чтения, изменения и удаления адреса: там были указаны неверные значения, а код 13 не существует вовсе — набор типов заканчивается на 12, и какие из них доступны, зависит от страновой зоны портала.

NEW-0804-14: поля товаров приходят с подписями, описаниями и словарём формата описания

GET /v1/products/fields описывал двадцать одно поле товара только типом и признаком «только для чтения» — ни подписи, ни описания. Понять по такому ответу, что measure это единица измерения, а vatId — ставка НДС, было нельзя.

Теперь каждое из двадцати одного поля несёт label на языке портала, а поля, у которых есть что добавить к подписи, — ещё и description: где взять список допустимых значений (GET /v1/currencies, GET /v1/product-sections, GET /v1/catalogs, GET /v1/users), как работает сортировка и что задаёт формат описания. У поля descriptionType появился словарь enum со значениями text и html.

Ключи добавляются к описанию поля, прежние type и readonly не меняются, поэтому менять ничего не нужно. Свойства каталога PROPERTY_<N> по-прежнему приходят с подписью из настроек портала.

NEW-0804-15: все 45 полей предложения приходят с подписью и описанием

GET /v1/quotes/fields отдавал подпись у тридцати полей из сорока пяти. Пятнадцать оставшихся — ровно базовые: id, title, dealId, contactId, companyId, amount, currency, assignedById, createdBy, comments, isManualOpportunity, beginDate, closeDate, createdTime, updatedTime — описывались только типом и признаком «только для чтения». Описания (description) не было ни у одного поля.

Теперь подпись есть у всех сорока пяти полей, и у каждого появилось описание: назначение поля, где взять список допустимых значений (GET /v1/currencies, GET /v1/users, GET /v1/deals и другие), поведение при записи. Отдельно названы расхождения имён, на которых легко ошибиться: сумма в Битрикс24 называется opportunity, валюта — currencyId, а даты начала и закрытия — begindate и closedate целиком в нижнем регистре.

У поля stageId словаря значений нет намеренно: набор стадий настраивается на портале, поэтому в описании стоит ссылка на справочник GET /v1/statuses?filter[entityId]=QUOTE_STATUS — статический словарь устарел бы.

Ключи добавляются к описанию поля, прежние type и readonly не меняются, поэтому менять ничего не нужно.

NEW-0804-16: поля товаров каталога приходят с подписями и описаниями

GET /v1/catalog-products/fields описывал все сорок два поля товара каталога только типом и служебными признаками — ни подписи, ни описания. Понять по такому ответу, чем purchasingCurrency отличается от валюты цены, а quantityTrace — от canBuyZero, было нельзя.

Теперь каждое из сорока двух полей несёт label на языке портала, а тридцать восемь полей — ещё и description. В описаниях названо то, что раньше приходилось выяснять на практике: iblockId задаётся только при создании и не даёт перенести товар между каталогами; iblockSection принимается только при записи, а на чтении основной раздел приходит скаляром iblockSectionId; available и bundle вычисляет Битрикс24; recurSchemeLength, recurSchemeType и trialPriceId работают только в коробочной версии Битрикс24 при продаже контента. Где значение берётся из справочника, указан эндпоинт — GET /v1/catalogs, GET /v1/catalog-sections, GET /v1/currencies, GET /v1/users.

У полей previewTextType и detailTextType набор значений приходит машиночитаемым словарём enum (text и html) — так же, как у формата описания товара, а не прозой внутри описания.

Ключи добавляются к описанию поля, прежние type, readonly, createOnly и nullable не меняются, поэтому менять ничего не нужно.

FIX-0804-17: схема полей шаблона реквизитов объявляет inShortList булевым

Было

GET /v1/requisite-presets/:presetId/fields/schema описывал поле inShortList типом char, хотя чтение строк того же шаблона отдаёт true/false, а запись принимает true/false. Схема противоречила данным, которые она описывает, и клиент, полагавшийся на объявленный тип, готовился разбирать односимвольную строку.

Стало

В том же ответе inShortList.type приходит как boolean. Остальные ключи описания поля (isRequired, isReadOnly, title и прочие) не изменились, типы остальных полей — тоже.

Влияние на интеграторов

Менять ничего не нужно: данные и раньше приходили булевыми. Проверка, сравнивающая inShortList.type со строкой char, перестанет совпадать — сверяйте с boolean.

NEW-0804-18: скачивание записей звонков и вложений таймлайна

Появились два эндпоинта для файлов CRM, до которых нельзя было добраться через скачивание файла Диска.

GET /v1/activities/:activityId/files/:fileId/download отдаёт файл дела, в том числе запись звонка. До этого сделать это через API было нельзя: файл дела не является объектом Диска, его идентификатор живёт в отдельном пространстве, пересекающемся с идентификаторами Диска, а ссылка из ответа дела приходит с пустым параметром авторизации — запрос по ней возвращает страницу входа с кодом 200. Эндпоинт добавляет авторизацию сам, проверяет, что файл действительно принадлежит названному делу, и отдаёт поток байтов.

GET /v1/timelines/:commentId/files/:fileRef/download отдаёт вложение комментария таймлайна. В fileRef принимается любой из двух идентификаторов: ID привязки, который показывает интерфейс портала, и ID объекта Диска — ключ объекта в поле files ответа комментария. Первый считает доступ через сам комментарий, поэтому дотягивается до вложений, которые скачивание файла Диска отдавать отказывается; второй идёт через личные права на Диске. Сначала читается сам комментарий, затем пробуется ID объекта Диска, и только если его нет в списке файлов комментария — ID привязки; указывать выбор не нужно.

Оба эндпоинта требуют скоуп crm и никогда не возвращают адрес для скачивания: в нём содержится код авторизации, поэтому наружу уходит только содержимое. Файл, принадлежность которого названному делу или комментарию не подтверждается, получает 404 — это касается и вложения, которое висит на записи другого типа, например на задаче с тем же номером, — без этой проверки эндпоинт позволял бы перебирать файлы портала, поскольку сам Битрикс24 в таком случае отвечает страницей под кодом 200, а не отказом.

Проверка адреса распространяется и на перенаправления: эндпоинт проходит их сам, сверяя каждый шаг, ограничивает цепочку по длине и по времени и не идёт по перенаправлению на внутренний адрес — такой ответ становится 502. Это же касается скачивания файла Диска, которое пользуется той же обвязкой.

404 на скачивании вложения означает именно неверную ссылку. Временная причина — лимит запросов Битрикс24, недоступность портала, сработавшая защита от повторяющихся ошибок — приходит как она есть: 429 либо 502/503 с заголовком Retry-After. Разница практическая: по 404 повторять запрос бессмысленно, по 429/5xx — нужно, с задержкой. Срок в Retry-After считается по тому вызову Битрикс24, который действительно упёрся в лимит.

Контракт скачивания файла Диска не менялся: те же параметры, те же ответы. Общей обвязкой оно унаследовало только проверку адреса и перенаправлений, описанную выше.

FIX-0804-19: сообщение бота без текста отбивается понятной ошибкой, а не мнимым успехом

Было

POST /v1/bots/:botId/messages с текстом в неузнанном поле — например {"dialogId": "…", "text": "привет"} — уходил в Битрикс24 без содержимого, и тот отвечал 422 с кодом EMPTY_MESSAGE и текстом «Message can't be empty». Понять из такого ответа, что дело в имени поля, было нельзя: текст-то передан.

Хуже вело себя обновление. PATCH /v1/bots/:botId/messages/:messageId в той же ситуации отвечал 200 {"result": true}, а текст сообщения не менялся — мнимый успех, после которого интегратор считал правку применённой.

Стало

Оба запроса проверяют содержимое до обращения к Битрикс24 и при его отсутствии отвечают 400 с кодом MESSAGE_REQUIRED. В тексте ошибки перечислены нераспознанные ключи тела и указано, что текст сообщения кладётся в поле message. Пустой массив в attach и пустая строка в message содержимым не считаются; блок attach без текста — считается, как и число (0 это текст «0»).

Обёртка fields — способ передать тело в родном виде Битрикс24, и «обёртка передана» теперь понимается одинаково на всех шагах обработки. Значение, обёрткой не являющееся (false, 0, пустой массив), обёрткой и не считается: {"dialogId": "…", "fields": false} получает тот же 400 с кодом MESSAGE_REQUIRED, а {"message": "привет", "fields": []} отправляет текст, а не теряет его.

Это тот же код и та же формулировка, что у соседнего POST /v1/chats/:dialogId/messages: один контракт на одинаковую ошибку в двух родственных эндпоинтах.

Влияние на интеграторов

Запрос с текстом в поле message работает как раньше. Запрос, который раньше получал 422 EMPTY_MESSAGE, теперь получает 400 MESSAGE_REQUIRED с указанием, что исправить. Поле text синонимом message не становится: на чтении содержимое сообщения действительно называется text, но принимать оба имени молча значило бы развести контракт с эндпоинтом чатов.

2026-08-03

FIX-0803-1: переоткрытие архивированного тикета тоже очищает штамп решения

Было

Переоткрытие тикета из статуса ARCHIVED в активный (NEW/REVIEWING/AWAITING_USER/NEEDS_REVIEW) — через PATCH /v1/feedback/:id или POST /v1/feedback/:id/comments — не сбрасывало resolvedAt/resolvedBy, если тикет ранее был решён и затем заархивирован. На чтении (GET /v1/feedback/:id) такой тикет выглядел активным и решённым разом. Сброс срабатывал только для источников RESOLVED/WITHDRAWN.

Стало

ARCHIVED присоединён к RESOLVED/WITHDRAWN: переоткрытие из любого закрытого статуса в активный очищает resolvedAt/resolvedBy. На PATCH-пути очищается и resolution (в том числе причина архива) — активный тикет решения не несёт; явный resolution в том же запросе имеет приоритет. На пути комментария resolution равен телу коммента. Переход в ARCHIVED штамп по-прежнему сохраняет (архивирование хранит историю решения).

FIX-0803-2: пропавшие байты вложения отдаются как 404, а не как оборванный ответ

GET /v1/feedback/{id}/attachments/{attId}/file и .../thumb теперь проверяют наличие байтов до отправки заголовков. Если запись о вложении есть, а самих байтов в хранилище нет (последствие ручной уборки или каскадного удаления), ответом будет обычный 404 NOT_FOUND.

Было

Ответ начинался как 200, а затем обрывался посреди тела: клиент получал усечённую картинку или пустой поток с уже отправленным успешным статусом, и отличить это от медленной сети было нечем.

Стало

404 { "success": false, "error": { "code": "NOT_FOUND", "message": "Not found" } } — тот же код, что и для чужого или удалённого вложения. Клиентам, которые уже обрабатывают 404 на этих маршрутах, менять ничего не нужно.

Изменение сопровождает перенос файлов вложений в объектное хранилище: раздача вложений больше не зависит от того, какая именно машина приняла загрузку. Формат ответов и адреса маршрутов не изменились.

FIX-0803-3: создание сервера честно сообщает, что вернуло уже существующий сервер приложения

Если ключ вызова принадлежит приложению, у которого слот сервера уже занят, POST /v1/infra/servers возвращает этот сервер вместо создания нового. Так было и раньше, но узнать об этом из ответа было почти нечем: единственным признаком было недокументированное поле reused, а имя в ответе принадлежало существующему серверу, а не запрошенному.

Теперь такой ответ несёт полное раскрытие: data.reusedReason со значением APPLICATION_ALREADY_HAS_SERVER, data.requestedName с эхом переданного вами name (всегда, даже если оно совпадает с именем существующего сервера) и массив warnings рядом с data минимум с одной записью — она называет существующий сервер и предупреждает, что деплой заменит работающий на нём код. Поля reused и deploying теперь описаны в схеме и в документации.

Точно так же раскрывается и вторая молчаливая потеря: переданные displayName и description переиспользование не применяет — сервер сохраняет собственные имя и описание. Теперь это видно по data.metaIgnored и отдельному предупреждению; переименовать сервер можно осознанно через PATCH /v1/infra/servers/:id.

Если в запросе был передан source, а развернуть его в переиспользуемый сервер нельзя (у galaxy-приложения уже есть работающий контейнер, либо это отдельная виртуальная машина), архив отбрасывается — и теперь ответ говорит об этом полем data.sourceIgnored и отдельным предупреждением. Раньше архив отбрасывался молча.

data.next в reuse-ответе теперь приходит только тогда, когда перезаписывать нечего. Раньше поле приходило на любом двухшаговом reuse — в том числе когда возвращённый сервер уже выполнял чужой код, и машинное «следующий шаг — деплой» противоречило подсказке в том же теле. Если сервер может выполнять код, next отсутствует намеренно: сначала убедитесь, что сервер тот. Отсутствие next — не ошибка.

Два прежних поля изменились по смыслу, хотя менять клиентский код не нужно: data.hint на reuse-ответе переписан целиком — вместо «задеплойте сюда» он теперь начинается с REUSED — no new server was created и объясняет, чем это чревато; а описание data.next в схеме исправлено — раньше оно называло единственной причиной появления поля пустой galaxy-слот, хотя next приходит и на reuse-ответе. Прежние поля и коды не изменились, менять ничего не нужно. Но перед вызовом POST /v1/infra/servers/:id/deploy читайте reused: деплой заменяет то, что уже работает на сервере.

NEW-0803-4: справочник значений списочных свойств каталога

Появилась сущность catalog-product-property-enums — все возможные варианты свойства-списка торгового каталога: GET /v1/catalog-product-property-enums, GET /v1/catalog-product-property-enums/:id, POST /v1/catalog-product-property-enums/search и GET /v1/catalog-product-property-enums/fields. Сущность только для чтения: записывающие операции и агрегация не зарегистрированы и отвечают 404, а data.batch приходит пустым массивом.

Раньше значение списочного свойства у товара можно было получить только как идентификатор варианта: семейство /v1/products отдаёт в PROPERTY_<N> объект с числовым value, а GET /v1/products/fields описывает свойство только именем — развернуть идентификатор в текст было нечем. Теперь список вариантов запрашивается напрямую: GET /v1/catalog-product-property-enums?filter[propertyId]=166&limit=1000 возвращает пары «идентификатор — читаемый текст», по которым строится карта String(id) → value.

Фильтр filter[propertyId] обязателен: справочник читается по одному свойству за раз, запрос без него отклоняется с 400 MISSING_REQUIRED_FILTER до обращения к Битрикс24 — на списке и поиске. Проверка смотрит на наличие ключа и не распространяется на подвызовы пакетного запроса. Перечисление есть только у свойств с propertyType: "L" — у свойства любого другого типа ответ будет пустым списком, а не ошибкой. Пагинация обычная: limit + offset, конец выборки — по meta.hasMore. Потолок — 5000 записей за вызов. Нужен скоуп catalog.

Заодно уточнены существующие страницы. GET /v1/catalog-products/:id теперь показывает захваченный ответ со списочным свойством и разбор тройки value / valueEnum / valueId, а также форму значения при listType: "C" — голый скаляр "Y"/"N", то есть состояние галочки, а не id варианта. GET /v1/products/:id объясняет, что пустое значение свойства означает либо незаполненное свойство, либо свойство, обслуживаемое только каталожным семейством, и как различить эти случаи одним перекрёстным вызовом. GET /v1/products/fields прямо говорит, что дескриптор PROPERTY_<N> несёт только название и куда идти за значениями. В справочнике ошибок исправлен перечень сущностей с обязательным фильтром.

FIX-0803-5: Обращения в поддержку работают при заморозке баланса

Было

На аккаунте, замороженном из-за баланса, все ручки /v1/feedback возвращали 402 ACCOUNT_FROZEN — сообщить о проблеме из продукта было нельзя ровно в тот момент, когда это нужнее всего.

Стало

По ключу доступна вся переписка: POST /v1/feedback (создать), GET /v1/feedback (список), GET /v1/feedback/{id} (открыть обращение) и POST /v1/feedback/{id}/comments (ответить команде). Все четыре читают и пишут только ваши же данные. Запись вложений (POST /v1/feedback/attachments), их скачивание и PATCH /v1/feedback/{id} остаются под гейтом заморозки, как и остальные /v1-эндпоинты.

FIX-0803-6: include возвращает полную связанную запись, а не только метаданные

Было

При ?include=<relation> во вложенном объекте приходили только метаданные связи — без id и полей связанной записи.

Стало

include возвращает полную связанную запись (id + поля), как и описано в контракте — отдельный запрос за связанной сущностью больше не нужен.

FIX-0803-7: методы дашборда Открытых линий в раскатке возвращают METHOD_NOT_YET_AVAILABLE

Было

На портале, куда обновление ещё не приехало, методы дашборда Открытых линий отдавали сырой 422 BITRIX_ERROR — по нему нельзя было отличить «метод раскатывается» от реальной ошибки интеграции.

Стало

Такой ответ распознаётся и возвращается как 422 METHOD_NOT_YET_AVAILABLE с версией релиза — понятный сигнал, что метод пока не доступен на этом портале, а не сбой интеграции. Ответ не меняется и при регулярном опросе: такие вызовы больше не засчитываются в защиту от петли ошибок, поэтому вместо понятного 422 не приходит 429 ERROR_LOOP_DETECTED (для методов, которые не раскатываются, защита работает как раньше).

FIX-0803-8: запись исходников в хранилище возвращает точные коды ошибок вместо общего 500

Было

Клиентские сбои записи в хранилище исходников маскировались общим 500 SOURCE_STORAGE_ERROR — по нему нельзя было понять, что делать.

Стало

Причина различима: при недостатке средств на балансе — 402 BILLING_INSUFFICIENT, при временном сбое выдачи ключей доступа к хранилищу — 503 STORAGE_STS_UNAVAILABLE (можно повторить запрос). Таблица кодов на странице source-storage дополнена.

FIX-0803-9: список значений в фильтре statuses отклоняется чистым 400, а не 500

Было

GET /v1/statuses со списком значений в фильтре ({поле: {$in: [...]}} или массив) уходил в Bitrix24, и метод справочника отвечал по-разному в зависимости от поля: по id и name — внутренней ошибкой, которая доезжала до клиента как 502 BITRIX_UNAVAILABLE, по entityId, statusId, semantics и sort — ошибкой «значение должно быть строкой», а по categoryId — успешным ответом с записями чужой воронки.

Стало

Список значений отклоняется до вызова Bitrix24 с 400 UNSUPPORTED_FILTER по любому полю фильтра: метод справочника не поддерживает его нигде. Принимается одно точное значение ({поле: значение}); несколько значений запрашивайте отдельными вызовами или через POST /v1/batch.

FIX-0803-10: /v1/tasks/:taskId/time принимает ключ со скоупом task

Было

Эндпоинт учёта времени задачи возвращал 403 INSUFFICIENT_SCOPE ключу со скоупом task — работал только tasks, хотя это алиасы одного разрешения.

Стало

task и tasks трактуются как алиасы (как на всех остальных task-эндпоинтах) — ключ с любым из них проходит.

NEW-0803-11: одношаговое создание galaxy-приложения принимает `healthPath`

Тело POST /v1/infra/servers с полем source теперь принимает необязательное поле healthPath — путь, по которому проверяется готовность приложения внутри контейнера. Валидация та же, что у POST /v1/infra/servers/{id}/deploy: строка до 500 символов, начинается со /. Значение по умолчанию — /.

Раньше healthPath был объявлен только в теле деплоя, поэтому одношаговый вызов, который платформа сама рекомендует в GET /v1/me, отвечал 400 UNKNOWN_PARAM — а описание в /v1/me при этом называло healthPath поддерживаемым на галактическом пути. Поле принято именно там, где рекомендация его обещает; на создании отдельного сервера оно игнорируется.

Влияние на интеграторов

Ничего менять не нужно — поле необязательное. Если раньше приходилось разбивать вызов на два шага только ради healthPath, теперь достаточно одного.

FIX-0803-12: `/start` и `/wake` у galaxy-приложения объясняют, почему они не применимы

Было

POST /v1/infra/servers/{id}/start и POST /v1/infra/servers/{id}/wake проверяли статус раньше типа сервера, поэтому galaxy-приложение вне списка разрешённых статусов (например RUNNING, ERROR или STOPPED) получало текст про standalone-сервер: «Server is RUNNING; /start requires one of SLEEPING, ERROR, PROVISIONING». Формально верно и бесполезно: перечисленные статусы тоже не помогли бы, а о том, что у приложения-контейнера вообще нет облачной машины, не говорилось ничего.

Стало

Проверка типа идёт первой — как в /reboot. Для galaxy-приложения в таком статусе message называет причину («это galaxy-приложение, у него нет облачной машины»), а userMessage называет операции, которые действительно работают: POST /v1/infra/servers/{id}/deploy (годится в любом из этих статусов) и POST /v1/infra/servers/{id}/reboot (только для запущенного или упавшего приложения). Код ошибки не меняется — по-прежнему SERVER_WRONG_STATE (422) с полями currentState и availableActions.

Влияние на интеграторов

Ничего менять не нужно: HTTP-статус и код те же, изменился только текст. Ответ для статусов ИЗ списка разрешённых остался прежним побайтово — в частности, /start и /wake для спящего galaxy-приложения по-прежнему отвечают задокументированным VM_MISSING.

FIX-0803-13: причина падения galaxy-приложения называется прямо в `provisionError`

Было

Когда приложение собиралось, запускалось и тут же падало, provisionError содержал только обобщённый вывод: «приложение не удержалось, смотрите логи» или, если сработал признак нехватки памяти, «вероятно, превышен лимит памяти галактики». Настоящая причина — например TypeError: webidl.util.markAsUncloneable is not a function из-за несовместимой версии среды — лежала в buildLog, а в списке серверов виден только provisionError. Поэтому по короткому тексту нельзя было понять, дело в памяти или в коде, и совет «перейдите на отдельный сервер» отправлял по ложному пути.

Стало

К той же формулировке дописывается строка из логов контейнера: … Actual cause from the container logs: <строка>. Строка выбирается из хвоста, который платформа снимает при отказе: сначала типизированное исключение или код ошибки (TypeError: …, EADDRINUSE, FATAL ERROR: … heap out of memory), затем известные причины сборки, затем последняя строка с признаком ошибки. Хвост проходит ту же очистку, что и buildLog — внутренние пути хоста заменяются на <build-context>. Если полезной строки в хвосте нет, текст остаётся прежним. Формулировка про память сохраняется и дополняется причиной: признак oom иногда срабатывает и там, где память ни при чём, и тогда приклеенная строка — единственная правда, которую видит читатель.

Влияние на интеграторов

Ничего менять не нужно. Прежние подстроки в тексте сохранены, поэтому клиент, который сопоставлял их, продолжает работать; появился только дописанный хвост. Полный лог по-прежнему доступен в buildLog (GET /v1/infra/servers/{id}).

FIX-0803-14: `/reboot` спящего galaxy-приложения запускает починку хоста и говорит об этом

Было

У спящего galaxy-приложения, чей хост потерял туннель, не было пути обратно. Задокументированный способ разбудить приложение — деплой, а деплой на недостижимый хост падает. Починка туннеля хоста при этом уже запускалась из перезапуска приложения, но запуск лежал ЗА проверкой статуса, которая спящее приложение отклоняет, — то есть до починки дело не доходило.

Стало

Перед тем же отказом 422 SERVER_WRONG_STATE платформа запускает фоновую починку туннеля хоста и, если починка действительно началась, добавляет в ответ необязательный объект hint с полями reason, recovery (какую операцию повторить) и retryAfterSeconds (нижняя граница ожидания, не обещание). Если починка не началась — сработал рубильник, туннель на самом деле жив, ремонт уже идёт или машина заблокирована для пробуждения, — hint отсутствует: сообщать о запуске, которого не было, нельзя. То же поведение добавлено в перезапуск из кабинета.

Влияние на интеграторов

Ничего менять не нужно: код и HTTP-статус те же, hint аддитивен. Клиенту, который читает hint, достаточно повторить POST /v1/infra/servers/{id}/deploy через названное время.

FIX-0803-15: zip-архив в исходниках galaxy-приложения больше не падает на распаковке

Было

Платформа определяет формат архива по его первым байтам и заявляет .zip поддерживаемым, но на галактическом пути (POST /v1/infra/servers с полем source и POST /v1/infra/servers/{id}/deploy для galaxy-приложения) архив передавался на распаковку без подготовки хоста. Если распаковщик на хосте отсутствовал, деплой падал с текстом вида exec: "unzip": executable file not found in $PATH — из него не следовало ни что делать, ни что тот же архив в .tar.gz прошёл бы.

Стало

Перед загрузкой zip-архива платформа доустанавливает распаковщик на хосте (тот же шаг уже выполнялся на отдельном сервере). Шаг идемпотентен: если распаковщик уже есть, он ничего не делает, и на повторных деплоях времени не занимает. Если установить не удалось, архив не загружается вообще, а деплой завершается отказом с честной причиной и подсказкой прислать тот же исходник как .tar.gz; текст доступен в buildLog и в provisionError.

Влияние на интеграторов

Ничего менять не нужно. Деплои с .tar.gz идут прежним путём без изменений.

NEW-0803-16: Версия исходников принимает до 500 МБ, а деплой по нашей ссылке линкуется к версии на любом ключе

Было

Архив можно было сохранить версией только до 200 МБ, при том что тот же архив разрешалось передать прямо в теле деплоя до 500 МБ. Крупный проект деплоился единственным способом — целиком в теле запроса.

Отдельно: деплой формы {"source": {"url": "…"}} по ссылке, полученной из GET /v1/infra/servers/:id/sources/:versionId/download, не связывался с версией, если сервер принадлежит персональному ключу vibe_api_*. В ответе приходило data.source.autoSaved: false и skippedReason: "external-url-or-toggles-off", а у версии оставались пустыми linkedDeployId и deployStatus.

Стало

Потолок версии исходников — 500 МБ на обоих приёмных эндпоинтах: POST /v1/infra/servers/:id/sources и POST /v1/apps/:id/sources. Тело по-прежнему принимается потоком, поэтому размер архива не влияет на скорость приёма. Заявленная в Content-Length длина сверх потолка отбивается кодом 413 до чтения тела. Значение публикуется в capabilities.apps.sourceStorage.limits.maxBlobBytes у GET /v1/me — теперь 524288000.

Деплой по нашей ссылке связывается с версией независимо от типа ключа-владельца сервера: ответ несёт data.source.autoSaved: true и savedVersionId, а у версии заполняются linkedDeployId и deployStatus.

Затронутые эндпоинты: POST /v1/infra/servers/:id/sources, POST /v1/apps/:id/sources, POST /v1/infra/servers/:id/deploy, GET /v1/me

FIX-0803-17: отзыв доступа гасит все ключи связки, а не только последний

Было

Когда пользователь повторно проходил согласие для одного и того же приложения, платформа выдавала новый ключ, но прежний оставался рабочим. Отзыв доступа гасил только ключ последней авторизации — прежние ключи продолжали ходить в API, хотя пользователь считал, что доступ закрыт.

Стало

Отзыв доступа гасит все ключи, которые пользователь выдал приложению для этого портала, включая ключи прежних авторизаций. Ключ, переживавший отзыв, теперь отвечает 401 KEY_INACTIVE. Ключи других сотрудников того же портала не затрагиваются. Партнёру достаточно штатной обработки 401 KEY_INACTIVE — предложить пользователю повторно авторизоваться.

2026-08-02

FIX-0802-1: подкоманда batch со своим start=-1 больше не отдаёт выдуманный total

Подкоманда POST /v1/batch со своим params.start: -1 больше не получает выдуманное количество записей. Значение -1 — это инструкция Битрикс24 «не считай коллекцию», поэтому счёта в ответе портала нет; конверт же брал за размер то, что оказалось под рукой — эхо result_total от методов, которые в этом режиме отдают 0 рядом с полной страницей, либо просто длину первой страницы. Затронуты обе ветки: подкоманда с limit до 50 и подкоманда с limit больше 50, которая читается отдельной автопагинацией.

Было

{"entity":"activities","action":"list","params":{"start":-1}} → meta.<id>.total: 0 и data.totals.<id>: 0 рядом с 50 записями, meta.<id>.hasMore: false. {"entity":"deals","action":"list","params":{"limit":5000,"start":-1}} → 50 записей, meta.<id>.total: 50, meta.<id>.hasMore: false — то есть на запрос 5000 записей приходил уверенный ответ «их всего 50».

Стало

На такой подкоманде ключей total нет ни в meta.<id>, ни в data.totals — счёт не заказывался, и придумывать его нечем. hasMore определяется полнотой страницы: заполненная до запрошенного лимита → true, короткая → false. Полнота считается против того ограничения, которое реально ушло в Битрикс24, поэтому {"limit":10,"start":-1} при 10 записях в ответе — это полная страница, и hasMore там теперь true, а не false. Обход не строит план дочитывания из выдуманного размера и возвращает непрерывный префикс.

Признак «счёт не заказан» читается по нормализованному значению: start приводится к целому числу так же, как это делает Битрикс24 (усечение к нулю, строка читается по числовому префиксу), поэтому -1, "-1", -1.5, "-1abc", "-1 x" — одно и то же. Положительное смещение (start: 100) — обычный счётный курсор, total по нему приходит как раньше.

Значение, которое числом не является вовсе ("abc", пустая строка, null, объект, массив, true), в Битрикс24 больше не уходит: оно читается как непереданное. Страница от этого не меняется — «начать с нуля» и «start не передан» это одна и та же страница, — но такая подкоманда попадает под общий выбор платформы и может прийти без total.

Отдельно: подкоманда, завершившаяся ошибкой, больше не публикует data.totals.<id> рядом с этой ошибкой.

Влияние на интеграторов

Клиент, который передавал start: -1 и читал total, получал заведомо неверное число: у методов формы «эхо result_total: 0» это был ноль, у остальных — длина страницы. Если цикл чтения останавливался по meta.hasMore, он обрывался на первой странице. Проверяйте наличие ключа total (meta.<id>.total !== undefined), а продолжение обхода ведите по meta.<id>.hasMore. Нужен точный счёт — не передавайте start: -1: этим значением подсчёт отменяет сам клиент, поэтому вернуть по такой подкоманде нечего, и withTotal: true его не восстановит.

BC-0802-2: доступ коробочного портала больше не выдаётся безусловно

Поддержка старого формата до: 30.01.2027

Было

Коробочный (self-hosted) портал считался коммерческим всегда: отметка ставилась при подключении портала, а не по факту оплаты. Ни подписка Маркетплейса Битрикс24, ни её окончание на доступ к платформе Вайбкод не влияли.

Стало

Доступ коробки определяется её подпиской Маркетплейса Битрикс24 — как у облачного портала. Ответы меняются там, где раньше доступ был всегда: POST /v1/infra/servers у портала без действующей подписки отвечает 402 с кодом отказа вместо создания сервера, а capabilities.servers.create в GET /v1/me приходит недоступной, с причиной. Те же правила действуют на создании агентов и ботов, которые заводят сервер.

Платная лицензия Битрикс24 сама по себе доступ к платформе Вайбкод не даёт: лицензия — про коробку, подписка — про Маркетплейс. Портал с оплаченной коробкой, но без подписки, попадает под отказ.

Пока состояние подписки прочитать не удалось, доступ не ограничивается: отсутствие сигнала не приравнивается к отсутствию подписки.

Отказ в доступе включается отдельным решением, не выкладкой: до включения ответы прежние. Два поля меняются раньше — на выкладке:

  • wasEverCommercial в GET /v1/me у коробочных порталов перестаёт быть односторонним: у портала, за которым не нашлось ни подписки, ни платежей, значение однократно меняется с true на false — прежнее ставилось при подключении, а не по наблюдению.
  • placements.bindPrerequisite в том же ответе у коробки начинает описывать подписочную модель (другой набор errorCodes, другой note) — раньше портал без определённого региона описывался как международный.

Что делать интеграторам

Проверять capabilities.servers.create в GET /v1/me перед созданием инфраструктуры и обрабатывать 402 на создании — код и текст отказа приходят в теле ответа. Если подписка оформлена, а отказ приходит, поможет принудительное обновление: GET /v1/me?refresh=tariff. Не полагаться на монотонность wasEverCommercial для коробочных порталов.

2026-08-01

FIX-0801-1: тело ответа AI-эндпоинтов не несёт служебных полей платформы

Было

В ответах POST /v1/chat/completions на моделях Битрикс24 поле system_fingerprint отдавало служебный идентификатор инфраструктуры платформы. Рядом с объявленными полями приходили и другие служебные поля, которых нет в документации, — как в самом конверте ответа, так и внутри choices и choices[].message. У потоковых ответов и у POST /v1/embeddings эти поля не наблюдались, но проверка там тоже не стояла.

Стало

system_fingerprint отдаётся нейтральным значением vibecode. Если апстрим отпечатка не прислал — поля в ответе нет, как и раньше. Служебные поля убраны из конверта ответа и из объектов choices; внутри choices[].message убран контейнер provider_specific_fields. В служебном событии ошибки внутри потока объект error сохраняет поля message, type, param, code, retryAfter и retryable; служебные поля рядом с ними убраны.

Объявленный контракт не изменился: id, object, created, model, choices, usage у чата и object, data, model, usage у эмбеддингов приходят как прежде — включая расширения провайдера внутри usage, поля рассуждения внутри choices[].message и вызовы инструментов. Служебное событие ошибки внутри потока по-прежнему приходит и по-прежнему несёт error. Клиенту менять ничего не нужно.

Правка касается только тела ответа моделей Битрикс24. В служебном событии ошибки внутри потока текст error.message теперь приводится к публичному имени модели, а если апстрим положил в message не строку — поле не возвращается вовсе. Текст ошибок в обычных ответах платформы (4xx, 5xx) не изменился.

FIX-0801-2: деплой galaxy-приложения больше не сообщает об обрыве после фактически успешного обновления

Было

Если соединение с площадкой обрывалось в середине POST /v1/infra/servers/:id/deploy, платформа перепроверяла состояние приложения один раз и, если ответ на эту проверку не приходил, отдавала 502 GALAXY_DEPLOY_INTERRUPTED. В типичном случае обновление в этот момент ещё шло и завершалось успешно — уже через несколько секунд приложение отвечало на health, работало на новой версии и с новыми переменными окружения. Отличить такой мнимый отказ от настоящего можно было только вручную, повторный вызов деплоя разворачивал ту же версию заново.

Стало

После обрыва платформа перепроверяет состояние приложения не однократно, а в течение ограниченного времени, и если новая версия поднялась — отвечает success, как если бы обрыва не было. 502 GALAXY_DEPLOY_INTERRUPTED остаётся только для случаев, когда за это время подтверждения так и не появилось. Время ожидания подобрано под окно, в течение которого платформа обязана ответить, поэтому длительность запроса в худшем случае не растёт.

Тело этой ошибки дополнительно несёт error.retryable: true — признак, по которому клиент может отличить её от неустранимого отказа, не разбирая текст. Прежние поля (error.code, error.message, error.hint) не изменились.

BC-0801-3: публикация проверяет авторизацию приложения раньше свежести снапшота

Поддержка старого формата до: 30.01.2027

Было

POST /v1/apps/:id/publish сначала проверял свежесть снапшота исходников, и только потом — авторизацию приложения. У вызывающего без авторизации ответ зависел от постороннего условия: при устаревшем снапшоте приходил 409 SNAPSHOT_REQUIRED, при свежем — 400 NO_USER_TOKEN. Чередование читалось как «проверка токена то проходит, то нет», хотя авторизации не было ни в одном из случаев.

Стало

Наличие авторизации проверяется до гейта свежести. Приложение без авторизации получает 400 NO_USER_TOKEN сразу, независимо от состояния снапшота. Последовательность стала монотонной: сначала закрываете авторизацию, дальше остаётся только требование к снапшоту.

Изменение затрагивает случаи, где раньше отвечал не тот код: нет авторизации И снапшот устарел или отсутствует — раньше 409 SNAPSHOT_REQUIRED, теперь 400 NO_USER_TOKEN; нет авторизации И итоговое имя для каталога длиннее лимита — раньше 400 TITLE_TOO_LONG_FOR_CATALOG, теперь 400 NO_USER_TOKEN (статус тот же, код другой). Если авторизация есть, но токен не удалось продлить, ответ по-прежнему 400 и приходит после гейта свежести. Публикация на self-hosted-аккаунте через ключ разработчика авторизации приложения не требует и этой проверкой не затронута.

Что делать интеграторам

Если ваш обработчик реагировал только на 409 SNAPSHOT_REQUIRED и повторял сохранение исходников в цикле, добавьте ветку на 400 NO_USER_TOKEN — в ней нужно авторизовать приложение, а не сохранять исходники ещё раз. Поле error.hint в этом ответе описывает действие.

NEW-0801-4: подсказки в ответах публикации: hint при NO_USER_TOKEN и presentedAt при SNAPSHOT_REQUIRED

Ответ 400 NO_USER_TOKEN у POST /v1/apps/:id/publish теперь несёт объект error.hint с полями requiredAction (что именно сделать, чтобы авторизовать приложение), docsUrl и oauthDocsUrl. Форма объекта совпадает с error.hint у 409 SNAPSHOT_REQUIRED на этом же эндпоинте.

Ответ 409 SNAPSHOT_REQUIRED дополнен полем error.hint.lastSnapshot.presentedAt — время, когда версию последний раз предъявили сохранением. Именно от него считается ageMinutes, поэтому по паре полей видно, почему версия признана устаревшей. Поле timestamp рядом по-прежнему означает время создания версии.

Оба поля аддитивные: прежние вызовы работают без изменений.

FIX-0801-5: повторное сохранение тех же исходников открывает публикацию

Было

Проверка свежести перед POST /v1/apps/:id/publish отсчитывала окно от времени создания версии. Повторное сохранение тех же байтов возвращало HTTP 201 с deduplicated: true, но новую версию не создавало и время создания не двигало, поэтому publish продолжал отвечать 409 SNAPSHOT_REQUIRED. Вызывающий, у которого исходники не менялись, попадал в цикл publish → 409 → POST /v1/apps/:id/sources → publish → 409, который не завершался: единственным способом обновить снапшот было изменить содержимое архива.

Стало

Сохранение отмечает версию как заново предъявленную, и окно свежести отсчитывается от этой отметки. Дедуплицированное сохранение открывает публикацию наравне с настоящим. Время создания версии (data.timestamp) не меняется, поэтому имя файла в депо и место версии в политике хранения остаются прежними.

Влияние на интеграторов

Менять ничего не нужно. Рецепт из подсказки к 409 — «сохрани исходники и повтори» — теперь работает и когда исходники не менялись.

BC-0801-6: оборвавшийся exec больше не отвечает успехом с exitCode -1

Поддержка старого формата до: 01.02.2027

Было

Если поток POST /v1/infra/servers/:id/exec завершался, не прислав статус выхода, ответ приходил как success: true с exitCode: -1. Исход команды на сервере при этом неизвестен, то есть успехом такой ответ не был. В потоковом режиме (?stream=true) поток в этом случае просто молча закрывался.

Стало

На отдельной виртуальной машине (kind: "STANDALONE") такой ответ приходит как success: false с кодом EXEC_NO_EXIT и объектом hint, указывающим на проверку состояния сервера. В data возвращается накопленный к моменту обрыва вывод (stdout, stderr); полей exitCode, duration и truncated там нет — их значения неизвестны, и подставлять вместо них нули значило бы утверждать то, чего платформа не знает. В потоковом режиме приходит событие error с этим же кодом. У galaxy-приложения этот случай пока приходит по-старому.

Что делать интеграторам

Обработайте EXEC_NO_EXIT наравне с прочими кодами ошибок. Если код читал data.exitCode без проверки success, теперь он получит undefined вместо -1 — ветвитесь по success. Команду можно повторить, если она идемпотентна; если нет, сначала посмотрите состояние сервера через GET /v1/infra/servers/:id/logs.

FIX-0801-7: тело JSON-ответа /exec и /deploy начинается с открывающей скобки

Было

В JSON-режиме (без ?stream=true) POST /v1/infra/servers/:id/exec и POST /v1/infra/servers/:id/deploy удерживают соединение, отправляя пробелы каждые 15 секунд. Эти пробелы шли перед JSON-документом, поэтому у команды длиннее 15 секунд тело ответа начиналось с пробелов. Клиенты со строгой проверкой формата отказывались его разбирать — команда на сервере при этом отрабатывала успешно.

Стало

Пробелы удержания соединения идут внутри уже открытого JSON-объекта, поэтому тело с первого байта — {. Набор полей ответа не изменился.

Влияние на интеграторов

Клиенты, которые разбирали ответ обычным JSON-парсером, изменений не заметят — обе формы тела валидны. Клиентам, которые снимали ведущие пробелы вручную, это больше не нужно.

FIX-0801-8: дата-время без часового пояса больше не сдвигается для порталов вне Москвы

Было

Значение даты-времени без явного смещения — например deadline 2026-07-15T13:00:00 — уходило в Битрикс24 как есть и читалось в часовом поясе владельца веб-хука портала, а не клиента. Интеграция из Берлина, записавшая 13:00, получала в хранилище 10:00 UTC вместо 11:00 UTC: минус час летом и минус два зимой. Значение выглядело правдоподобно, поэтому порча дат платежей, дедлайнов и встреч замечалась не сразу.

Стало

Клиент может объявить свой часовой пояс заголовком X-Vibe-Timezone (IANA-имя, например Europe/Berlin; браузер получает своё значение из Intl.DateTimeFormat().resolvedOptions().timeZone). Даты-время без смещения штампуются смещением этого пояса, действовавшим на саму дату значения, — переходы на летнее время учитываются по каждому значению. Это касается полей, которые действительно хранят время суток: часть полей Битрикс24 называет датой-временем, а хранит как дату, и там смещение сдвинуло бы сам день, поэтому такие поля не трогаются. Значения с явным Z или смещением не переписываются никогда. Без заголовка (или с нераспознанным поясом) поведение прежнее — существующие интеграции не затронуты.

Заголовок влияет только на запись. Битрикс24 отбрасывает суффикс часового пояса внутри фильтра, поэтому платформа его срезает и значения фильтра всегда читаются в поясе портального аккаунта. С включённым заголовком запись «2026-07-15T13:00:00» и последующий фильтр по тому же литералу не совпадут: передавайте в фильтр тот момент времени, который хотите сравнить, либо задавайте диапазон с запасом на смещение.

FIX-0801-9: удаление бота идемпотентно: «бота уже нет» — это успех, а не 502

Было

DELETE /v1/bots/:botId отвечал 502 BOT_DELETE_PARTIAL и сохранял запись в базе Вайбкод при ЛЮБОЙ ошибке Битрикс24 — в том числе когда Битрикс24 сообщал, что такого бота у него уже нет. Целевое состояние было достигнуто, а вызов считался провалившимся, и запись оставалась в GET /v1/bots навсегда: обычным удалением её было не убрать, помогал только ?force=true.

Стало

Если Битрикс24 отвечает, что бота у него нет, удаление считается успешным: запись удаляется, ответ 200 содержит новое поле data.alreadyAbsentOnB24: true. Дополнительно, когда Битрикс24 отклоняет отмену регистрации по иной причине, Вайбкод один раз проверяет, есть ли бот на портале: если бот уже снят — тоже успех, если снятие подтвердить не удалось — прежний 502 BOT_DELETE_PARTIAL с сохранением записи. При ?force=true ответ на «бота нет» теперь тоже несёт alreadyAbsentOnB24: true вместо forced: true — сироты на стороне Битрикс24 в этом случае не возникает. В теле 502 появилось поле error.incidentCode — шестизначный код для обращения в поддержку.

Влияние на интеграторов

Менять ничего не нужно. Сценарий «удалил бота, получил ошибку, бот остался в списке» больше не возникает; ?force=true остаётся только для порталов, которые недоступны навсегда. Если код ветвится на data.forced, учтите, что в случае «бота на Битрикс24 уже нет» теперь приходит data.alreadyAbsentOnB24.

FIX-0801-10: сообщения об ошибках Битрикс24 приходят без HTML-разметки

Было

Валидационный текст, полученный от Битрикс24, форвардился в поле error.message вместе с прицепленным тегом <br>. Клиент, выводящий сообщение как текст — а JSON-контракт ровно это и предполагает, — показывал пользователю буквальный тег после каждой ошибки, на его родном языке. То же касалось поля error.validation[].message.

Стало

Разметка убирается на границе ответа: <br> в любом написании становится переводом строки. Именно переводом, а не пробелом — Битрикс24 склеивает через этот тег ошибки по разным полям, и граница между ними сохраняется. Локализация не меняется: текст остаётся на языке портала и не переводится. Угловые скобки внутри пользовательских данных (адрес почты в тексте ошибки, знак сравнения) не затрагиваются — убирается именно тег переноса строки, а не разметка вообще. То же касается ответов POST /v1/batch, где ошибка приходит по каждому подвызову. Если вы вырезали <br> на своей стороне, эту обработку можно снять.

Заодно введены два технических ограничения: сообщение длиннее 8192 символов усекается, а из массива error.validation отдаётся не больше 100 элементов. Оба нужны потому, что и текст, и число полей приходят от портала и ничем не ограничены; на реальных ответах ни то, ни другое не срабатывает.

Охват: error.message и error.validation[] во всех конвертах ошибок V1, ответы POST /v1/batch и POST /v1/{entity}/batch, meta.pageErrorSample при автопагинации, POST /v1/bots. У ответа POST /v1/chats/messages/bulk при этом поля ошибок приведены к общей форме {code, message} — раньше объект ошибки портала форвардился как есть, со своими ключами error/error_description.

Отдельно: счётные фразы в письмах на французском и португальском теперь считают по правилам CLDR — ноль относится к единственному числу («0 jour», не «0 jours»). И англоязычная подсказка про модуль «Универсальные списки» больше не содержит русского названия на международных порталах.

NEW-0801-11: место встраивания CALL_CARD — панель в карточке звонка

Было

GET /v1/placements/available не отдавал CALL_CARD, а POST /v1/placements/bind отвечал VALIDATION_ERROR на этот код — хотя Битрикс24 его поддерживает.

Стало

CALL_CARD есть в справочнике и принимается на привязку. Приложению нужен скоуп telephony: Битрикс24 отдаёт это место только приложениям с таким правом, иначе привязка отклоняется с «Placement not found». Тот же скоуп теперь требуется и для соседнего TELEPHONY_ANALYTICS_MENU — раньше он был в справочнике без права, и привязка молча не удавалась на стороне Битрикс24.

FIX-0801-12: четыре тихих отказа: дела, склады, файлы, пользовательские поля

Было

Четыре запроса отвечали кодом 200 и возвращали не то, о чём их просили.

Дела: поля providerParams и settings объявлены типом object, но пустое значение приходило пустым массивом. Тип поля зависел от наполнения, поэтому клиент, собранный по схеме, падал на десериализации именно тех записей, где значение пустое.

Склады: GET /v1/warehouses и GET /v1/warehouses/:id/stock со смещением, не кратным 50, отдавали первую страницу — offset=1, offset=2 и offset=3 возвращали одни и те же записи, при этом hasMore сообщал, что дальше есть ещё. Обход по смещению зацикливался на первой странице.

Файлы и папки: фильтр по полю, которое Битрикс24 не умеет фильтровать (createdBy, size, updatedBy), молча отбрасывался, и вместо отобранных записей приходило всё содержимое папки. Несуществующее имя поля вело себя так же.

Пользовательские поля: DELETE /v1/userfields/:entity/:id и DELETE /v1/items/:entityTypeId/userfields/:id с заголовком Content-Type: application/json и пустым телом отвечали ошибкой FST_ERR_CTP_EMPTY_JSON_BODY — запрос не доходил до обработчика. Без заголовка тот же запрос работал.

Стало

Дела: пустое значение providerParams и settings приходит пустым объектом, объявленный тип верен всегда. Непустые значения не изменились.

Склады: смещение построчное — offset=1&limit=3 возвращает вторую, третью и четвёртую записи. Если запрошенное окно не покрывается одной страницей Битрикс24, data придёт пустым, а в meta.warnings — код OFFSET_BEYOND_FETCHED_PAGE.

Файлы и папки: фильтр по неподдерживаемому полю отвергается ошибкой 400 UNSUPPORTED_FILTER со списком полей, по которым фильтровать можно: id, name, code, storageId, type, folderId (для папок — parentId), deletedType, createdAt, updatedAt, deletedAt. Навигация по дереву не затронута: родительская папка остаётся в списке разрешённых, поэтому обе формы — параметром ?folderId= и фильтром ?filter[folderId]= — работают как раньше.

Пользовательские поля: удаление принимается с заголовком Content-Type: application/json и без него. Некорректный JSON по-прежнему отвергается, с кодом INVALID_JSON_BODY.

Влияние на интеграторов

Ничего менять не нужно. Три оговорки на случай, если ваш код полагался на прежнее поведение: проверка Array.isArray больше не отличает пустое значение дела от заполненного (проверяйте число ключей); обход складов по смещению теперь честно двигается по строкам, а не по страницам; фильтр файлов и папок по полю вне списка выше теперь вернёт ошибку вместо всего содержимого папки.

NEW-0801-13: служебные поля задачи получили названия, описания и словарь допустимых значений

GET /v1/tasks/fields теперь отдаёт человекочитаемое label и description для двадцати служебных полей Битрикс24, у которых раньше вместо названия стояло само имя поля: NOT_VIEWED, DURATION_TYPE, GUID, CHAT_ID, CHECKLIST, FAVORITE, IS_MUTED, IS_PINNED, IS_PINNED_IN_GROUP, ALLOW_CHANGE_DEADLINE, ALLOW_TIME_TRACKING, NEW_COMMENTS_COUNT, SERVICE_COMMENTS_COUNT, FORUM_ID, FORUM_TOPIC_ID, EXCHANGE_ID, EXCHANGE_MODIFIED, OUTLOOK_VERSION, SITE_ID, XML_ID.

Вместе с этим в схеме полей появились два ключа, которые Битрикс24 присылал и раньше, а мы отбрасывали: values — словарь допустимых значений в виде массива [{ "value": "Y", "label": "Да" }], и default — значение, которое портал подставит, если поле не передано. Они приходят у всех полей, где портал их отдаёт, а не только у перечисленных выше — например словарь Y/N теперь виден и у MULTITASK, TASK_CONTROL, SUBORDINATE, ADD_IN_REPORT, REPLICATE. Подписи в values формирует сам портал и локализует своими настройками, поэтому у поля с кодовыми значениями подписи может не быть — так у DURATION_TYPE приходят только коды secs, mins, hours, days, weeks, monts, years (опечатка monts — со стороны Битрикс24, портал принимает именно это написание).

values — отдельный ключ, он не заменяет items: items по-прежнему отдаёт сырой перечислимый справочник у полей типа enumeration в формате [{ "ID": "1", "VALUE": "Первый" }]. Гарантия — на уровне ключа: у каждого всегда своя форма, поэтому читайте нужный по имени. На сегодняшних порталах у одного поля приходит только один из двух, но одновременное присутствие не запрещено.

Прежние вызовы работают без изменений: новые ключи аддитивны, набор полей и их типы не менялись. Пять полей — FAVORITE, IS_MUTED, IS_PINNED, NEW_COMMENTS_COUNT и NOT_VIEWED — описывают отношение к задаче того пользователя, от имени которого работает ключ, а не свойство самой задачи: другой ключ на том же портале увидит другие значения.

2026-07-31

BC-0731-1: приём исходников требует Content-Length

Поддержка старого формата до: 30.01.2027

Эндпоинты приёма исходников — POST /v1/apps/{id}/sources и POST /v1/infra/servers/{id}/sources — теперь принимают тело только с заголовком Content-Length. Запрос без него (передача по частям, Transfer-Encoding: chunked) получает 411 с кодом MISSING_CONTENT_LENGTH.

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

Подавляющее большинство клиентов заголовок и так шлют: его проставляют curl --data-binary, fetch с телом-буфером и любой HTTP-клиент, отправляющий файл целиком. Затронуты только клиенты, которые сознательно стримят тело неизвестной длины.

Заодно повторное сохранение одного и того же архива теперь стоит дороже: совпадение по содержимому определяется после приёма тела, поэтому ответ приходит чуть медленнее, а объём засчитывается в операции хранилища. Результат прежний — deduplicated: true и та же версия.

Что делать интеграторам

Отправлять архив целиком (--data-binary @file в curl, буфер или файл в теле запроса), а не потоком неизвестной длины. Если клиент стримит тело сам — посчитать размер заранее и выставить Content-Length.

FIX-0731-2: имя тарифа в GET /v1/me — из справочника платформы и по-русски

Было

Поле data.tariff.name бралось из сохранённого снимка тарифа: имя лицензии, как его отдал портал, а если портал имени не дал — значение внутреннего словаря без учёта языка. На части порталов это не было названием тарифа вовсе: приходило либо русское «Демо» на нерусскоязычном портале, либо сам код лицензии, например pro100, выданный за человекочитаемое название.

Стало

Название разрешается в момент ответа. Если код тарифа известен справочнику редакций платформы, поле берётся оттуда и по-русски — Демо-период; имя, сообщённое порталом, при этом не используется, поэтому подпись может измениться и там, где всё было в порядке. Если код справочнику неизвестен, поле по-прежнему несёт имя от портала — как есть, на его языке. И только когда имени нет вовсе либо вместо него приходит сам код лицензии, поле равно null: код лицензии в качестве названия больше не подставляется никогда. Тип поля не менялся — строка или null, сам код по-прежнему в data.tariff.code.

Подписи известных редакций заодно уточнились: Демо → Демо-период, Проект → Проект — архивный бесплатный, NFR → NFR — партнёрская лицензия.

FIX-0731-3: поиск пользователей сервера возвращает только активных сотрудников

Было

GET /v1/infra/servers/:id/b24-users мог возвращать неактивных пользователей и пользователей, которые не являются сотрудниками.

Стало

Эндпоинт возвращает только пользователей, которые подтверждены как активные сотрудники Битрикс24. Изменения со стороны интеграций не требуются.

NEW-0731-4: приложение само решает, включать ли авто-высоту iframe во встройке

У приложения появилось необязательное поле placementResizeEnabled (по умолчанию false). Оно управляет тем, отдаёт ли платформа при открытии встройки страницу-обёртку, которая подстраивает высоту iframe под контент вашего приложения.

Поле возвращается в GET /v1/apps и GET /v1/apps/:id и принимается в PATCH /v1/apps/:id. Прежние вызовы работают без изменений: у всех существующих приложений значение false, то есть поведение открытия встройки прежнее.

Включайте только вместе с правкой на стороне приложения. Обёртка открывает приложение во вложенном iframe на источнике платформы, поэтому у приложения появляется новый источник-предок. Если приложение отдаёт собственный заголовок Content-Security-Policy с директивой frame-ancestors, добавьте в неё источник платформы — иначе браузер откажется открывать приложение. Приложения без своей директивы frame-ancestors правок не требуют.

Если авто-высота у вашего приложения уже работала, включите поле, чтобы сохранить прежнее поведение. Учтите, что поле — необходимое условие, но не единственное: сама возможность раскатывается по порталам постепенно.

Чтобы приложение сообщало платформе свою высоту, оно постит родительскому окну сообщение { type: 'vibe:resize', height } — контракт описан в разделе Приложение в Битрикс24.

FIX-0731-5: пустое тело с заголовком JSON больше не отклоняется на маршрутах инфраструктуры

Было

Операция, которой тело не нужно, отвечала 400 с кодом FST_ERR_CTP_EMPTY_JSON_BODY, если клиент присылал заголовок Content-Type: application/json без тела. Так поступают клиенты, которые ставят этот заголовок на любой запрос — например axios и PowerShell Invoke-RestMethod. Отказ происходил на разборе тела, то есть раньше проверки ключа, поэтому по ответу нельзя было понять, что не так с доступом. Затрагивало DELETE /v1/infra/servers/:id/access-tokens/:tokenId, POST /v1/infra/servers/:id/wake и остальные операции сервера без тела.

Стало

Пустое тело принимается как {}, и операция отвечает по существу — 401 на неверном ключе, 404 на несуществующем сервере, 200 при успехе. Прежний обход, когда тело {} передавалось явно, продолжает работать. Заодно неразбираемое тело на этих маршрутах теперь даёт код INVALID_JSON_BODY вместо FST_ERR_CTP_INVALID_JSON_BODY — тот же код, что уже возвращали соседние операции того же сервера, включая deploy, exec и lock.

2026-07-30

NEW-0730-1: withTotal в списочных вызовах пакетного запроса

Списочный вызов внутри POST /v1/batch принимает в params параметр withTotal — тот же, что у одиночного GET /v1/{entity}. withTotal: false означает «количество не нужно»: подсчёт у Битрикс24 не заказывается, а data.totals.<id> и meta.<id>.total в ответе не приходят. Границей листания остаётся meta.<id>.hasMore.

Границы применимости стоит знать до того, как параметр окажется в коде. Он действует на вызовах с action: "list" — и на тех, у которых limit не больше 50 и offset равен нулю (именно такие уезжают в Битрикс24 одной командой пакета), и на тех, у которых limit больше 50. У action: "search" и у пакета одной сущности POST /v1/{entity}/batch параметр инертен — подсчёт заказывается как раньше, total приходит.

Правило присутствия total там же, что у одиночного вызова: если подсчёт не заказан, а страница пришла короче полной, точное количество всё равно приходит — оно известно из самой страницы.

У вызова, который подсчёт не заказывал, meta.<id>.hasMore выводится из полноты страницы, а не из отсутствующего количества: пришла полная страница — записи ещё есть, пришла короткая — коллекция закончилась. Цикл «читать, пока hasMore» доходит до конца и без total; если размер коллекции ровно кратен размеру страницы, последний вызов вернёт пустой список — это штатный признак конца, а не ошибка.

Вместе с этим у вызова, который подсчёт не заказывал, data.totals.<id> и meta.<id>.total перестают приходить и на позиционированной странице — при offset больше нуля. Так ведут себя и одиночные эндпоинты: количество не должно то появляться, то исчезать внутри одного обхода.

FIX-0730-2: meta.total приходит на короткой странице, даже когда подсчёт не заказывался

Было

Если вызов списка не заказывал подсчёт, meta.total в ответе отсутствовал всегда — в том числе там, где количество было известно точно. Страница короче запрошенного limit означает, что коллекция на ней и закончилась, то есть число записей равно числу отданных строк, — но поле всё равно не приходило.

Стало

В этом случае meta.total приходит и содержит точное число. Правило простое: total появляется только на вызове с offset = 0 и только если страница пришла короче запрошенного. Пустой результат — тоже число: "total": 0.

Явный withTotal=false в запросе по-прежнему означает «поля total не будет» — обещание про этот параметр не изменилось. Настройка totalDefault на API-ключе и платформенное умолчание точный total из короткой страницы не запрещают: про них такого обещания не давалось. Отсюда следствие, о котором стоит знать заранее: два одинаковых запроса от двух разных ключей могут вернуть ответы разной формы.

Правило действует и на списочных вызовах внутри POST /v1/batch.

Влияние на интеграторов

Менять ничего не нужно. meta.total остаётся необязательным полем: проверяйте его наличие в конкретном ответе, а не предполагайте. Границей цикла листания остаётся meta.hasMore — арифметика по meta.total для этого не годится ни до, ни после изменения.

FIX-0730-3: бесплатный тариф больше не получает 402 PLAN_NOT_ALLOWED_ON_TRIAL при деплое в галактику

Было

POST /v1/infra/servers с { name, source, runtime, start } на портале с galaxy-размещением возвращал 402 PLAN_NOT_ALLOWED_ON_TRIAL (details.allowedPlans: ["bc-micro"]), хотя GET /v1/me в capabilities.servers.create отдавал available: true с тем же bc-micro. Плана в запросе не было вовсе: платформа сама поднимала общий galaxy-хост на своём (не-bc-micro) плане, и этот внутренний выбор проверялся тем же списком планов, что и VM, создаваемая пользователем. Каждое galaxy-приложение при этом расходовало единственный слот бесплатного тарифа, поэтому второй деплой упирался в 402 TRIAL_PORTAL_LIMIT.

Стало

Одношаговый create-and-deploy работает на бесплатном тарифе. Общий galaxy-хост занимает единственный VM-слот, план ему выбирает платформа (минимальный стабильный план сегмента), а контейнеры-приложения на этом хосте слот не расходуют — их число ограничено только ёмкостью хоста. Список планов из capabilities.servers.create.limits.allowedPlans теперь относится только к VM, которую вы создаёте сами; deployment.galaxyApp._rules (правило QUOTA) описывает это явно. Для placement: "dedicated" ограничения бесплатного тарифа не изменились.

FIX-0730-4: /v1/me не предлагает эндпоинты токенов доступа, когда раздел выключен

Было

Блок data.infra.preview ответа GET /v1/me всегда содержал адреса mintUrl, listUrl, revokeUrl и refreshUrl — в том числе когда раздел токенов доступа выключен на платформе и все четыре эндпоинта отвечают 503 FEATURE_DISABLED. Соседние блоки data.capabilities.servers.preview и data.deployment.preview в том же ответе сообщали {"available": false, "reason": "FEATURE_DISABLED"}, то есть один ответ противоречил сам себе.

Стало

data.infra.preview следует тому же признаку, что и два соседних блока. При включённом разделе рядом с адресами приходит "available": true, при выключенном — {"available": false, "reason": "FEATURE_DISABLED"} без адресов. Правило data.api._rules про проверку деплоя через режим api-bearer называет эту проверку и запасной путь — шаги healthcheck и tunnel_routing в отчёте POST /v1/infra/servers/:id/deploy.

Влияние на интеграторов

Клиент, который читал адреса из data.infra.preview безусловно, при выключенном разделе получит блок без них. Проверяйте поле available перед обращением к адресам — вызовы по ним и раньше отвечали 503 FEATURE_DISABLED.

FIX-0730-5: заголовок X-Vibe-User-Id больше не смешивает идентификаторы

Было

Заголовок X-Vibe-User-Id, который платформа передаёт приложению на каждом запросе, документировался как числовой ID пользователя Битрикс24. На части маршрутов входа в него попадал внутренний идентификатор платформы Вайбкод без какого-либо признака или значение 0 — заглушка «пользователь не опознан». Приложение отличить это от настоящего ID не могло. Опасен именно внутренний идентификатор, начинающийся с цифр: Битрикс24 приводит строку к числу, поэтому 47a4cff2-… превращалось в 47 — валидный ID постороннего сотрудника портала. Приложение, подставлявшее заголовок в DIALOG_ID метода im.message.add, отправляло личное сообщение не тому человеку.

Стало

Значение заголовка всегда одно из двух: либо строка из одних цифр — ID пользователя портала Битрикс24, либо значение с явным префиксом, если ID в портале у посетителя нет: net_ — пользователь Битрикс24 Нетворк вне портала, share: — анонимный посетитель по гостевой ссылке, vibe: — пользователь платформы Вайбкод, чей ID в портале определить не удалось. Значение 0 не отправляется вовсе: если идентичность неизвестна, заголовка просто нет. При входе через кабинет Вайбкод платформа теперь определяет настоящий ID пользователя в портале и присылает именно его — там, где раньше приходил внутренний идентификатор.

Если приложение подставляет заголовок в параметры методов Битрикс24 (USER_ID, DIALOG_ID, RESPONSIBLE_ID и подобные) — проверяйте, что значение состоит только из цифр. Значение с префиксом означает, что у посетителя нет ID в портале, и передавать его в Битрикс24 нельзя. Для логов, аналитики и собственного ACL заголовок годится в любом виде. Полный контракт — Что приходит в приложение.

BC-0730-6: галактика не создаётся на бесплатном тарифе Битрикс24

Поддержка старого формата до: 30.08.2026

Было

Аккаунт Битрикс24 на бесплатном тарифе с включённым режимом галактик получал галактику под свои приложения. Одношаговое создание POST /v1/infra/servers с полем source разворачивало приложение контейнером на общем хосте, а GET /v1/me в блоке deployment описывал galaxy-контракт.

Стало

На бесплатном тарифе новая галактика не создаётся. Пока у аккаунта нет галактики, каждое приложение разворачивается на отдельной виртуальной машине, а создание с полем source возвращает 400 SOURCE_AT_CREATE_GALAXY_ONLY. GET /v1/me в этом состоянии не отдаёт deployment.galaxyApp, ставит deployment.primary в standalone и объясняет причину в новом поле deployment.placementNote.

Аккаунт, у которого галактика уже есть, продолжает разворачивать приложения в ней — ограничение касается только создания новой. Коммерческий тариф Битрикс24 снимает ограничение целиком.

Что делать интеграторам

Разворачивайте в два шага, как на обычном сервере: POST /v1/infra/servers с provider, name, plan, region (без source), дождитесь status: "running" и blackholeStatus: "CONNECTED", затем POST /v1/infra/servers/:id/deploy с source, runtime, start. Модель размещения определяйте по GET /v1/me — по наличию блока deployment.galaxyApp, а не по режиму портала.

FIX-0730-7: POST /v1/apps честнее сообщает о причине отказа на коробочном портале

Было

При установке приложения на коробочном портале без активной подписки «BitrixGPT + Маркетплейс» POST /v1/apps возвращал непрозрачный 502 CONNECTOR_APP_INSTALL_FAILED: причина отказа наружу не выдавалась вовсе, а сам код обещал временный сбой — повтор вызова выглядел осмысленным, хотя помочь не мог.

Стало

Отказ по подписке распознаётся и на коробочном портале: POST /v1/apps возвращает 403 с кодом, называющим причину. Аккаунт в подписочном регионе (Россия, Беларусь) без активной подписки получает B24_MARKET_SUBSCRIPTION_REQUIRED — вместе с понятным сообщением и ссылкой на оформление в error.details.upgradeUrl. Аккаунт на тарифной модели доступа, а также портал, на котором REST недоступен, получает INT_TARIFF_REQUIRED (нужен коммерческий тариф Битрикс24) без error.details.upgradeUrl: подписки, которую можно оформить, там нет. Прочие отказы установки через коннектор классифицируются как прежде.

Влияние на интеграторов

Менять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал 502 CONNECTOR_APP_INSTALL_FAILED, стоит дополнительно ловить 403 B24_MARKET_SUBSCRIPTION_REQUIRED / INT_TARIFF_REQUIRED — первый приходит на аккаунте подписочного региона, второй на тарифной модели доступа и на портале с недоступным REST — и подсказывать пользователю оформить подписку на портале либо перейти на коммерческий тариф Битрикс24: этот отказ терминальный, повторять запрос бессмысленно.

NEW-0730-8: новый отказ 429 TIMEOUT_QUARANTINE: метод, который перестал отвечать, ставится на паузу

Если один и тот же метод несколько раз подряд не ответил вашему аккаунту Битрикс24 за отведённое вызову время, Вайбкод перестаёт отправлять к нему запросы и отвечает 429 с кодом TIMEOUT_QUARANTINE, заголовком Retry-After и полем error.retryAfter. Пауза действует на пару «аккаунт + метод»: остальные методы работают как обычно, и от того, каким ключом сделан вызов, она не зависит.

Это отказ на стороне Вайбкод, а не лимит Битрикс24: запрос до аккаунта не дошёл, поэтому ничего не изменилось — повтор безопасен даже для методов записи. Пауза снимается сама: время от времени один вызов пропускается для проверки, и первый успешный ответ снимает её немедленно. Вмешательство не требуется, отдельного эндпоинта для снятия нет.

В ответе появилось машиночитаемое поле error.scope: у этого отказа "portal" — пауза общая для ВСЕХ ключей аккаунта, включая чужие интеграции. У соседнего отказа OPERATION_TIME_LIMIT (Битрикс24 приостановил метод, исчерпавший бюджет рабочего времени) оно равно "apiKey" — там приостановлен только вызывающий ключ. Те же два значения error.scope уже приходят в отказе по квоте обращений FEEDBACK_QUOTA_EXCEEDED, словарь общий. Различие даёт ответ на вопрос «чинить свой код или ждать вместе с аккаунтом», не разбирая текст ошибки. Рядом приходит error.hint с действием — на русском. Оба кода теперь описаны в справочнике ошибок. Количество чужих ключей, их имена и объём их неудач в ответе не приходят — это данные других клиентов аккаунта.

Что делать: дождаться срока из Retry-After и повторить, добавив к паузе случайную задержку, — а не крутить повтор в цикле. Интервал повторов при этом не сокращайте: пока пауза действует, один вызов раз в 5 минут пропускается как проба восстановления, и агрессивный повтор занимает этот слот собой — метод остаётся закрытым для всего аккаунта дольше, чем если бы вы просто подождали. Если метод не отвечает стабильно, облегчите вызов: меньше полей в select, меньше страница, более узкий фильтр или более узкий интервал дат. Тяжёлый запрос и есть причина, по которой аккаунт не успевает ответить, — пауза снимется тем вызовом, который аккаунт сумеет выполнить.

Код может прийти на любом вызове, который Вайбкод проксирует в Битрикс24 под именем метода Битрикс24: на одиночных чтениях и записях — как 429 с заголовком, а на подвызовах батча, которые Вайбкод исполняет отдельными запросами (поиск и список с limit больше 50), — внутри ответа 200 строкой data.errors[<id>] вида { "code": "TIMEOUT_QUARANTINE", "message": …, "retryAfter": …, "scope": "portal", "hint": … }: конверт объединяет разные вызовы, поэтому заголовка Retry-After для отдельного подвызова у него нет, срок приходит полем. В батче одной сущности (POST /v1/{entity}/batch) отказ приходит так же внутри 200, но элементом массива data: { "error": { "code": "TIMEOUT_QUARANTINE", "message": …, "retryAfter": …, "scope": "portal", "hint": … } }. Сам конверт POST /v1/batch под паузу не попадает: он объединяет разные методы, и его собственная задержка не говорит о том, какие из них перестали отвечать. Опрос событий бота (GET /v1/bots/{botId}/events) тоже не попадает — у него свой ответ на таймаут, с подсказкой про восстановление подписки. В поиске с разбиением по датам (POST /v1/{entity}/search) отказ приходит либо тем же 429, либо — если часть окон успела прочитаться — в meta.windowErrorSample.code при ответе 200: это признак неполной выдачи.

Защита включается постепенно, по аккаунтам, поэтому этот код увидят пока не все.

FIX-0730-9: отказ OPERATION_TIME_LIMIT в батче приходит со своим кодом и сроком, а не под общим кодом

Было

Битрикс24 приостанавливает метод, исчерпавший бюджет рабочего времени, примерно на 5 минут, и Вайбкод отбивает такие вызовы на входе, зная, что пауза ещё действует. На одиночном вызове этот отказ приходил как 429 с кодом OPERATION_TIME_LIMIT, заголовком Retry-After и полями retryAfter и scope. А внутри батча тот же отказ терял и код, и срок: на подвызовах POST /v1/batch, которые Вайбкод исполняет отдельными запросами (поиск и список с limit больше 50), он приходил как data.errors[<id>] с общим кодом AUTO_PAGINATION_FAILED, а в батче одной сущности (POST /v1/{entity}/batch) — как data[i].error с кодом CALL_FAILED и текстом Internal error. Ответ был 200, поэтому отличить приостановленный метод от внутреннего сбоя было нечем, а срок повтора не приходил вовсе — оставалось повторять вслепую по методу, который аккаунт держит закрытым.

Стало

Обе батчевые поверхности отдают тот же отказ, что одиночный вызов: { "code": "OPERATION_TIME_LIMIT", "message": …, "retryAfter": …, "scope": "apiKey", "hint": … } — в data.errors[<id>] у POST /v1/batch и в data[i].error у POST /v1/{entity}/batch. Конверт объединяет разные вызовы, поэтому заголовка Retry-After для отдельного подвызова у него нет — срок приходит полем retryAfter. Поле scope равно "apiKey": приостановлена связка «ваш ключ + этот метод», другие методы работают, и другие ключи аккаунта тот же метод вызывать могут. Контраст — TIMEOUT_QUARANTINE со scope: "portal", где пауза общая для всего аккаунта. Локализованного поля userMessage в 200-конверте нет ни у одного отказа, поэтому нет и здесь. Ограничение снято и из справочника ошибок.

Влияние на интеграторов

Менять ничего не нужно: коды сузились с общих до конкретного, а поля добавились. Если ваш код ветвился на AUTO_PAGINATION_FAILED или CALL_FAILED, чтобы поймать приостановленный метод, теперь для этого есть OPERATION_TIME_LIMIT и срок в retryAfter — дождитесь его и повторите, а не крутите повтор в цикле.

NEW-0730-10: машинная схема описывает 51 эндпоинт, который работал, но в ней отсутствовал

Все они отвечали и раньше — но клиент, который строит вызовы по /v1/openapi.json (кодогенератор, ИИ-агент, наш собственный справочник), их не видел и не мог о них узнать.

Товарные позиции — работа с отдельной строкой и её схемой полей: GET|PATCH|DELETE /v1/{deals,leads,quotes,invoices}/{id}/products/{rowId} и GET /v1/{deals,leads,quotes,invoices}/{id}/products/fields. То же для смарт-процессов: /v1/items/{entityTypeId}/{id}/products/{rowId} и .../products/fields.

Схемы полей: GET /{сущность}/fields появился у 22 сущностей, у которых Битрикс24 не отдаёт метод схемы (заказы, документы, шаблоны документов, платежи, позиции корзины, статусы заказа, каталоги, разделы и цены каталога, свойства товаров, бронирования, события и группы, подразделения, сайты и страницы, шаблоны/активности/роботы бизнес-процессов, линии телефонии, конфигурации открытых линий, узлы оргструктуры). Ответ там — набор полей, объявленный обёрткой, без пользовательских (UF) полей: в описании операции это сказано прямо, чтобы никто не ждал большего.

Открытые линии: GET|POST /v1/openline-configs, PATCH|DELETE /v1/openline-configs/{id}, POST /v1/openline-configs/search и read-only POST /v1/openline-configs/batch (в батче доступны только list, get, fields — записи идут через перечисленные выше операции). У списка описан его настоящий конверт: total — размер выданного окна, а не всего набора, поэтому цикл постраничного чтения ограничивают по hasMore.

Бронирования: GET /v1/bookings и POST /v1/bookings/search. Окно дат (dateFrom, dateTo) описано как обязательное — без него метод Битрикс24 молча вернул бы пустой список, поэтому обёртка отклоняет такой вызов.

Конвертация лида: POST /v1/leads/{id}/convert — в описании операции сказано, что она не идемпотентна (повторный вызов создаст второй набор сущностей).

Затронутые эндпоинты: GET /v1/openapi.json

FIX-0730-11: текст ошибки проверки BYOK-ключа больше не содержит сам ключ

Было

Если провайдер отвечал на проверку учётных данных ошибкой и повторял в ней присланный ключ, этот текст возвращался вызывающему и сохранялся в поле lastError, которое отдаёт GET /v1/ai/credentials. Скрывались только ключи вида sk-…, адреса и пары «логин:пароль@хост», поэтому ключи прочих провайдеров попадали в ответ и в поле дословно — и были доступны любому участнику портала.

Стало

Присланный ключ и адрес прокси удаляются из текста ошибки по значению, независимо от их формата: и в ответе POST /v1/ai/credentials, PATCH /v1/ai/credentials/{id}, POST /v1/ai/credentials/{id}/test, и в сохраняемом lastError на их месте стоит редакция. Коды ошибок и статусы не изменились.

FIX-0730-12: многостраничный список отдаёт начало выборки вместо ошибки, когда подсчёт не удался

Было

Многостраничный вызов списка — GET /v1/{entity} и POST /v1/{entity}/search с limit больше 50, а также их списочные подзапросы в POST /v1/batch — начинался с подсчёта записей. Подсчёт коллекции стоит Битрикс24 несоразмерно дорого, и если он срывался (тайм-аут, лимит запросов, ошибка портала), вызов возвращал ошибку целиком — без единой записи, хотя первая страница уже была получена.

Параметр withTotal=false на таких вызовах не действовал: подсчёт заказывался всё равно, meta.total приходил.

Стало

Подсчёт выполняется, только когда без него не обойтись, и его срыв больше не отменяет ответ. Если записи получены, а подсчёт не удался, приходит 200 с непрерывным началом выборки: meta.hasMore равен true, meta.total отсутствует, а в meta.pageErrorSample лежат code и message с причиной. Два новых значения code — оба коды самого Вайбкод, а не Битрикс24:

  • PAGE2_COUNT_FAILED — подсчёт не удался: тайм-аут, лимит запросов или ошибка портала.
  • LAZY_COUNT_NO_PROGRESS — обход остановлен: следующая страница не принесла ни одной новой записи, хотя в коллекции их больше, чем уже отдано.

В пакетном запросе то же самое приходит в data.meta.<id>: hasMore равен true, pageErrorSample заполнен, а total и data.totals.<id> отсутствуют — количество не сосчитано, и число отданных строк его не заменяет.

Заодно withTotal=false начал действовать при limit больше 50 — на GET /v1/{entity}, на POST /v1/{entity}/search и на вызовах action: "list" внутри POST /v1/batch: теперь meta.total не приходит ни при каком исходе. Границы применимости у action: "search" внутри пакета и у пакета одной сущности POST /v1/{entity}/batch не изменились. Без этого параметра количество приходит как раньше.

Оговорка про нагрузку: при limit больше 50 этот параметр — про форму ответа, а не про стоимость. Подсчёт платформе всё равно нужен, чтобы спланировать обход, поэтому дешевле вызов не станет, а точное количество, которое короткая первая страница отдаёт бесплатно, будет отброшено. Подсчёт параметр отменяет только при limit не больше 50.

Влияние на интеграторов

Проверьте, как код узнаёт о неполном ответе. Раньше признаком служила сама ошибка: цикл с повтором по 429 и 5xx срабатывал сам собой. Теперь неполнота уезжает в тело успешного ответа — обработчик ошибок не сработает, а заголовка Retry-After, который приходил вместе с 429, в таком ответе нет.

Признак неполноты — meta.pageErrorSample рядом с meta.hasMore. Продолжить обход можно двумя способами, и порядок между ними не произвольный.

Курсором — это первый выбор. meta.nextAfterId передаётся обратно как filter[>id], смещение при этом остаётся нулевым, и продолжение снова идёт тем же путём, на котором подсчёт заказывается не всегда. Приходит курсор не везде: только у сущностей с курсорным обходом и только при сортировке строго по id по возрастанию.

Смещением — запасной путь. Новый offset равен исходному плюс число отданных записей, но вызов с ненулевым offset уходит на счётный путь и заказывает ровно тот подсчёт, который только что не удался. После PAGE2_COUNT_FAILED это с высокой вероятностью тот же тайм-аут, поэтому повторяйте с паузой, а не в плотном цикле.

У GET /v1/tasks курсора нет — задачи идут без курсорного обхода, и nextAfterId в их ответах не приходит. Для них смещение остаётся единственным способом продолжить, со всеми оговорками выше.

Границей цикла листания по-прежнему остаётся meta.hasMore, а не арифметика по meta.total: поле было необязательным и до этого изменения.

FIX-0730-13: спека объявляет обязательные поля на создании и перестала требовать их на обновлении

Было

Создание через POST /v1/bizproc-robots, POST /v1/bizproc-activities и POST /v1/folders отклонялось, если не передать code, name и handler (для папки — name), а спека объявляла эти поля необязательными: клиент или SDK, сгенерированный по ней, получал отказ на первом же вызове. Обратная сторона той же причины: описание тела запроса было общим для создания и обновления, поэтому там, где обязательные поля всё же были объявлены — POST /v1/activities, POST /v1/documents, POST /v1/bizproc-templates — их требовал и PATCH, хотя частичное обновление одного поля API принимает.

Стало

Требование объявлено на операции создания, а не в общем описании тела. POST перечисляет поля, без которых откажет рантайм; PATCH их не требует и принимает частичное обновление, как и раньше на деле. Поведение не изменилось — изменилось описание, которое теперь соответствует обеим операциям. Те же поля помечены обязательными и на двух других описательных поверхностях: GET /v1/folders/fields с аналогами и GET /v1/guide.

2026-07-29

FIX-0729-1: комментарий к задаче больше не отбивается по правам ключа

Было

POST /v1/tasks/:taskId/comments на порталах с новой карточкой задач мог вернуть 403 с сообщением портала «Недостаточно прав доступа: отсутствует необходимый scope» — даже когда у ключа есть право task, а соседние вызовы задач в ту же секунду отвечают 200. Формулировка уводила в тупик: она читалась как «выдайте приложению доступ к задачам», хотя набор прав определяется при выпуске ключа, а не действиями пользователя.

Стало

Права на комментарий выдаются ключу в обеих формах, которых требуют старый и новый маршрутизаторы Битрикс24, поэтому вызов проходит. Ключам, выпущенным раньше, набор прав досылается на месте при первом таком отказе, и запрос повторяется — вмешательство не нужно. Если после этого портал всё равно отказывает, ответ 403 теперь прямо называет причину (у вебхука ключа набор прав уже, чем у самого ключа) и подсказывает переиздать ключ — вместо пересказа сообщения портала.

Влияние на интеграторов

Менять ничего не нужно. Клиент, ловивший этот 403 как постоянную ошибку, теперь получает 201.

FIX-0729-2: изменение доступа к приложению на общем хосте доезжает до его показа в Битрикс24

Было

Для приложения на общем хосте (kind=GALAXY_APP), встроенного в интерфейс Битрикс24, изменение списка доступа доезжало до показа не всегда. Пользователь, у которого доступ отозвали, мог продолжать видеть приложение на своём месте встройки — доступ к самому приложению при этом уже был закрыт.

Это касалось смены политики доступа и правки списка через PATCH /v1/infra/servers/:id/access-policy, POST /v1/infra/servers/:id/access и DELETE /v1/infra/servers/:id/access/:accessId.

Стало

Изменение доступа отражается на показе: приложение пропадает из интерфейса Битрикс24 у тех, кто доступ потерял, и появляется у тех, кому его выдали.

Влияние на интеграторов

Менять ничего не нужно. Тела запросов, ответы и коды ошибок прежние — изменился только наблюдаемый эффект вызова. Приложений на собственной виртуальной машине изменение не касается: там показ и раньше следовал за доступом.

FIX-0729-3: агенты и боты больше не засыпают по простою

Было

PATCH /v1/infra/servers/:id/sleep принимал любое значение sleepAfterMinutes, включая серверы, созданные под агента или бота (createdVia agent или bot).

Стало

Для сервера с createdVia agent или bot и ненулевым sleepAfterMinutes эндпоинт отвечает 400 с кодом AGENT_IDLE_SLEEP_FORBIDDEN. Значение null (никогда не засыпать) по-прежнему принимается.

Влияние на интеграторов

Спящий агент или бот перестаёт опрашивать Битрикс24 и не просыпается на новое сообщение, поэтому прежнее значение было нерабочим — менять в рабочем сценарии нечего. Для экономии по расписанию используйте Запланированное пробуждение.

BC-0729-4: сессия в Authorization должна принадлежать приложению из X-Api-Key

Поддержка старого формата до: 29.07.2026

Было

Сессия (vibe_session_*) выписывается одному приложению на одном аккаунте Bitrix24, но при вызове /v1/* эта привязка не проверялась. Ключ авторизации приложения B принимал сессию, выписанную приложению A, и запрос выполнялся под правами ключа B — то есть чужая сессия открывала доступ к данным через ключ другого приложения.

Стало

/v1/* проверяет, что сессия в Authorization: Bearer принадлежит тому же приложению и тому же аккаунту Bitrix24, что и ключ в X-Api-Key. Несовпадение — 403 с кодом SESSION_APP_MISMATCH. Ключ авторизации приложения, у которого приложение отвязано или удалено, сессию больше не принимает. Личных ключей vibe_api_* изменение не касается: сессию в Authorization они не читают — ни до него, ни после.

Вызовы без Authorization (только по ключу) не затронуты. Сессия, предъявленная с ключом своего приложения, работает как раньше.

Влияние на интеграторов

Проверьте, что оба заголовка относятся к одному приложению: X-Api-Key — ключ авторизации того приложения, которое выписало сессию через POST /v1/oauth/token. Если сервис обслуживает несколько приложений, храните пару «ключ + сессия» вместе и не берите их из разных мест.

Проверка включена с выпуска этого изменения, без переходного периода: она закрывает доступ к данным по чужой сессии. Ошибка 403 SESSION_APP_MISMATCH описана в справочнике ошибок.

FIX-0729-5: приложение на личном ключе снова получает X-Vibe-Authorization

Было

Приложение, размещённое в Black Hole на личном ключе (vibe_api_*), теряло заголовок X-Vibe-Authorization примерно через минуту после открытия плейсмента. Первые запросы проходили с сессией, дальше она пропадала и не возвращалась ни через перезагрузку страницы, ни через повторное открытие приложения — только через новое открытие плейсмента, и опять на минуту.

Причина: сессия, которую плейсмент выписывает пользователю, привязана к приложению через его адрес (appUrl), а восстанавливалась она только по цепочке «сервер → ключ → приложение». У личного ключа приложения нет, поэтому восстановление отвечало «сервер не найден», и Gateway запоминал этот отказ. X-Vibe-User-Id при этом продолжал приходить, поэтому со стороны приложения это выглядело как «пользователь есть, а токена нет».

Стало

Когда у ключа-владельца сервера нет приложения, оно ищется по адресу приложения: берутся приложения того же аккаунта Bitrix24, созданные владельцем ключа, чей appUrl указывает ровно на этот адрес приложения. Сессия восстанавливается, и заголовок продолжает приходить на всё время жизни сессии.

Приложения на ключе авторизации (vibe_app_*) работают как раньше — у них цепочка «сервер → ключ → приложение» есть, и она остаётся главной.

Влияние на интеграторов

Менять ничего не нужно. Если приложение обходило проблему повторным открытием плейсмента или собственным хранением токена — эти костыли можно снять.

NEW-0729-6: управляющий ключ владельца, аккаунт которого ждёт удаления, получает 503

Было

Заморозка аккаунта на время запроса на удаление данных действовала на веб-кабинет и на обычные ключи приложения, но не на управляющие ключи: владелец с аккаунтом в состоянии ожидания удаления продолжал выпускать, ротировать и удалять ключи через /v1/keys.

Стало

Управляющий ключ владельца, аккаунт которого находится в процессе удаления данных, получает 503 с кодом ACCOUNT_PENDING_ERASURE и заголовком Retry-After: 3600 — так же, как это давно работает для обычных ключей приложения. Если запрос на удаление отменён, ключ начинает работать снова без перевыпуска.

FIX-0729-7: деплой galaxy-приложения проверяет доступность и отдаёт шаги

Было

Успешный деплой galaxy-приложения через POST /v1/infra/servers/:id/deploy возвращал success: true, status: "running" без data.steps[] и без проверки того, что приложение действительно отвечает по HTTP. Контейнер, который поднялся, но не слушал порт, всё равно рапортовался как успех — отличить рабочий деплой от сломанного было нельзя. Плюс GET /v1/infra/servers/:id для такого приложения показывал runtime: null и порт по умолчанию — развёрнутый рантайм и порт не сохранялись.

Стало

Успешный ответ несёт data.steps[]: шаг { step: "build", status: "ok" } и — когда проба выполнялась — шаг { step: "healthcheck", status: "ok" | "warning", httpCode, healthPath }. status: "ok" означает, что приложение ответило 2xx/3xx на data.appUrl; warning — ответило 4xx/5xx (всё ещё достижимо, деплой прошёл). Контейнер, который поднялся, но не ответил по HTTP на своём порту, теперь честно завершается 502 GALAXY_APP_START_FAILED (то же семейство, что крах после старта — в теле остаётся хвост buildLog), а не рапортуется как успех. Необязательное поле healthPath (по умолчанию /) в теле деплоя задаёт путь пробы. GET /v1/infra/servers/:id теперь отражает развёрнутый runtime и порт.

Влияние на интеграторов

Приложение, которое отвечает по HTTP на порту деплоя, ничего не заметит. Приложение, которое стартует, но не начинает отвечать в окне пробы, получит 502 GALAXY_APP_START_FAILED вместо ложного успеха — убедитесь, что оно слушает порт, указанный при деплое, и что healthPath возвращает ответ. Шаг build в data.steps[] и сохранение runtime/порта в GET доступны сразу; сама HTTP-проба (шаг healthcheck и 502 GALAXY_APP_START_FAILED) раскатывается — включается на платформе постепенно, поэтому до её активации деплой ведёт себя как раньше (без пробы).

FIX-0729-8: серверы просыпаются после погашения долга и на постоплатном счёте

Было

Портал с постоплатным биллингом, ушедший в минус до лимита овердрафта, глушил серверы и помечал их «заморожено биллингом». После пополнения счёта API снова отвечал 200, но серверы так и оставались выключенными: POST /v1/infra/servers/{id}/wake возвращал отказ (SERVER_WAKE_BLOCKED), и снять пометку можно было только обращением в поддержку. На предоплатном биллинге тот же сценарий отрабатывал нормально.

Стало

Как только баланс перестаёт быть отрицательным, пометка снимается и серверы поднимаются автоматически — одинаково на предоплате и постоплате. Частичное пополнение, оставляющее баланс в минусе, снова открывает API, но серверы держит выключенными: они возвращаются к работе, когда долг закрыт полностью. Серверы, остановленные по другим причинам (истёк доступ, остановка вручную), пробуждение не затрагивает.

FIX-0729-9: деплой не рапортует ложный успех после отката усиленного юнита

Было

Деплой на POST /v1/infra/servers/:id/deploy мог отрапортовать успех (healthcheck:ok, hardening:warning), обслуживая при этом ответ чужого процесса, занявшего порт приложения. Это происходило, когда усиленный (hardened) юнит падал, деплой автоматически откатывался на обычный юнит, но откат не освобождал порт — а на первой же проверке чужой держатель порта отвечал 200. В результате выкладка сообщала об успехе, хотя новая версия порт так и не заняла и в продакшене продолжала работать предыдущая.

Стало

Если после отката порт по-прежнему держит тот же процесс, что заблокировал усиленный юнит (откатанный юнит порт не занял), деплой завершается ошибкой healthcheck:error с явным сообщением о том, что порт всё ещё занят, вместо ложного healthcheck:ok. Штатный откат, при котором порт занимает уже новый экземпляр приложения, по-прежнему завершается успехом.

FIX-0729-10: кэш метаданных учитывает портал и схемы полей

Было

Кэш GET /v1/statuses был описан как привязанный к личному ключу, а схемы GET /v1/{entity}/fields не были отражены в разделе кэширования. Клиент не видел в документации, какие повторные запросы получают X-Cache: HIT и как запросить свежую схему полей.

Стало

GET /v1/statuses описан как кэш портала на 5 минут. GET /v1/{entity}/fields описан как кэш схемы полей на 5 минут с учётом портала, ключа авторизации, сущности, параметров пути, параметров запроса и языка ответа. Обход кэша через Cache-Control: no-cache доступен для всех этих чтений, а для /fields также доступен refresh=true.

Влияние на интеграторов

Код менять не нужно. Повторные чтения метаданных меньше нагружают очередь портала, а заголовки X-Cache и X-Cache-Bypass-Reason показывают, был ли использован кэш.

NEW-0729-11: дельта последних диалогов по параметру updatedAfter

GET /v1/chats/recent принимает updatedAfter — момент в формате ISO 8601, начиная с которого нужно вернуть изменённые диалоги. Это отдельный режим ответа: data приходит плоским массивом диалогов, а meta несёт mode со значением delta и returned с их числом.

Размером выборки в этом режиме управляет сервер — читается одна страница до 200 диалогов. Переданный limit на неё не влияет и возвращается обратно в meta.requestedLimit вместе с применённым meta.appliedLimit. Когда дельту не удалось подтвердить полной — Битрикс24 сообщил, что за отданной страницей есть ещё диалоги, либо форму ответа не удалось разобрать, — в meta приходит truncated со значением true: в этом случае не сдвигайте updatedAfter, а получите полный список постраничным режимом с курсором lastMessageDate. Число возвращённых строк признаком полноты не является.

Граница включительна — диалог, у которого dateUpdate равен переданному моменту, попадает в ответ. Дата обязана нести явное смещение или Z: значение вида 2026-06-29 10:00:00 читается по-разному в зависимости от часового пояса сервера и отклоняется с 400 INVALID_PARAMS. Тем же кодом отклоняется сочетание updatedAfter с offset или lastMessageDate — постраничная навигация и дельта используют разные курсоры.

NEW-0729-12: транзиентный 503 на границе платформы теперь говорит, через сколько повторять

Когда все реплики бэкенда на мгновение недоступны — например в момент редеплоя galaxy-приложения — граница платформы отвечает 503 SERVICE_UNAVAILABLE на префиксах /api/ и /v1/. В теле было только человекочитаемое «Retry in a few seconds», и машинного признака повторяемости не было: клиент не мог отличить секундную дыру от постоянной недоступности и либо ронял задачу, либо ретраил наугад.

Теперь этот ответ несёт HTTP-заголовок Retry-After: 5 и поле retryAfter: 5 внутри объекта error — рядом с code и message, ровно так же, как это делает сама платформа для своего транзиентного 503. Прежние вызовы не меняются: код SERVICE_UNAVAILABLE и статус остались теми же, добавлены заголовок и поле. Текст сообщения теперь дополнительно ссылается на заголовок — на него по-прежнему не следует опираться, ветвитесь по error.code. Полный список кодов — /docs/errors.

Тот же блок границы отвечает и на таймаут чтения от бэкенда. В этом случае запрос до бэкенда доехал и, возможно, всё ещё выполняется, поэтому для не-идемпотентных операций перед повтором сверьте состояние сущности.

Отдельно стоит сказать, чего это изменение НЕ делает: оно не устраняет причину, по которой апстримы бэкенда оказались недоступны. Оно делает ошибку честной и машинно-понятной, чтобы клиент корректно подождал и повторил.

BC-0729-13: пакетный запрос отклоняет сортировку у сущностей, чей метод Битрикс24 её не умеет

Поддержка старого формата до: 29.07.2026

Было

Одиночный список сущности и её POST /v1/departments/search уже отвечали 400 INVALID_SORT_FIELD, если метод Битрикс24 за списком не принимает порядок сортировки. Подзапрос общего POST /v1/batch этой проверки не имел: та же сортировка уходила в метод, тот её выбрасывал, и подзапрос возвращал успех с несортированным списком. Получалось расхождение на одном и том же запросе — через одиночный маршрут отказ, через пакетный молчание.

Стало

Подзапрос POST /v1/batch с действием list или search проверяет то же самое и отвечает 400 INVALID_SORT_FIELD в errors своего подзапроса; остальные подзапросы выполняются как обычно. Отказ приходит до обращения к Битрикс24. Проверяются оба написания — и sort, и order. Это касается двух сущностей: подразделений (departments) и телефонных линий (telephony-lines). Хранилищ это НЕ касается — их метод сортировку умеет, и отказ у них снят отдельной записью этого же выпуска.

Что делать интеграторам

Если подзапрос к одной из этих двух сущностей передавал сортировку — уберите её: она никогда не применялась, список приходил в порядке Битрикс24. Нужен свой порядок — сортируйте полученный список на своей стороне. Подзапросы без сортировки, а также limit, offset, select и фильтр, работают как раньше. Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что параметр молча игнорировался, — сохранять было бы нечего.

BC-0729-14: `defaultOperatorData` у открытых линий стал объектом, пустое значение — `null`

Поддержка старого формата до: 27.01.2027

Было

Поле объявлялось массивом, и незаданное значение приводилось к []. Настоящий тип другой: Битрикс24 отдаёт объект вида { "NAME": …, "AVATAR": … }, когда данные оператора по умолчанию заданы. То есть GET /v1/openline-configs и GET /v1/openline-configs/:id обещали в fields массив, а при заполненном значении присылали объект.

Стало

Тип поля — object; незаданное значение приходит как null, а не как []. Заполненное значение приходит объектом, как и раньше. Соседние kpiFirstAnswerList и kpiFurtherAnswerList — настоящие массивы строк, они по-прежнему приводятся к [].

Что делать интеграторам

Код, который считал длину или перебирал это поле (defaultOperatorData.length, .map, .forEach), сломается на null — замените проверку на if (config.defaultOperatorData) { … } и читайте поля объекта напрямую. Если вы ориентировались на fields и ждали массив — сверьтесь с новым типом object.

BC-0729-15: телефонные линии: запись `serverName`, сортировка, фильтр и смещение больше не теряются молча

Поддержка старого формата до: 29.07.2026

Было

serverName в схеме числился обычным изменяемым полем, но Битрикс24 его не хранит и не возвращает: POST /v1/telephony-lines с этим полем отвечал 201, а значение исчезало; PATCH того же поля упирался в ошибку самого Битрикс24. Отбор, порядок и смещение вели себя так же тихо: метод Битрикс24 за этим списком не принимает входных параметров вовсе, поэтому ?order[number]=desc, ?name=…, ?filter[number]=…, ?offset=50 и те же значения в теле POST /v1/telephony-lines/search отбрасывались, а список приходил 200 — и выглядел отсортированным, отфильтрованным и перелистнутым, хотя не был ничем из этого. В подзапросе POST /v1/batch тем же образом терялась сортировка.

Стало

Все четыре случая стали явной ошибкой до обращения к Битрикс24. Запись serverName — 400 READONLY_FIELD; любая сортировка — 400 INVALID_SORT_FIELD; любой фильтр — 400 UNSUPPORTED_FILTER; ненулевое смещение — 400 UNSUPPORTED_OFFSET. Отказ фильтру приходит на списке, в поиске, в POST /v1/telephony-lines/aggregate и в подзапросах обоих пакетных запросов — общего POST /v1/batch и POST /v1/telephony-lines/batch; отказ сортировке и смещению — на списке, в поиске и в подзапросе общего пакетного запроса (агрегат этих параметров не читает). В общем POST /v1/batch отказ приходит в errors своего подзапроса, а остальные подзапросы выполняются как обычно; если отклонены все подзапросы, запрос отвечает 400, и разбор по подзапросам остаётся в errors. В POST /v1/telephony-lines/batch иначе: нарушение контракта отбора отклоняет весь пакет одним 400 с номером подзапроса в сообщении, ни один подзапрос не выполняется. Само поле serverName осталось видимым в GET /v1/telephony-lines/fields с признаком «только для чтения», так что понять его назначение по-прежнему можно.

Что делать интеграторам

Если вы передавали serverName при создании или обновлении линии — уберите поле: значение всё равно никогда не сохранялось. Если полагались на сортировку, фильтр или смещение — ни одно из них никогда не применялось; список внешних линий приложения приходит целиком одной страницей, поэтому сортируйте, отбирайте и разбивайте его на страницы на своей стороне. Обычный список без этих параметров, а также limit и select, работают как раньше. Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что параметр молча игнорировался, — сохранять было бы нечего.

NEW-0729-16: список хранилищ можно сортировать

Сортировка по полям хранилища работает на GET /v1/storages и в POST /v1/storages/search: ?sort=-id, ?order[name]=desc и те же значения в теле поиска. Минус перед именем поля означает убывание.

Порядок выдачи меняют id, name, entityType, entityId, rootFolderId — по каждому из них проверено на живом аккаунте, что по возрастанию и по убыванию приходят разные выдачи. Поля code и module метод тоже принимает без ошибки, но на проверенных аккаунтах значение этих колонок одинаково у всех хранилищ, поэтому порядок по ним не меняется — не полагайтесь на них как на сортировку.

Раньше любая сортировка хранилищ отклонялась с 400 INVALID_SORT_FIELD. Отказ был ошибочным: он был выведен из описания метода Битрикс24, а метод порядок сортировки принимает и применяет — это проверено на живом портале, где выдача по возрастанию, по убыванию и без сортировки различаются. Если вы обходили этот отказ, сортируя список у себя, обходной путь можно убрать; он продолжает работать.

Заодно порядок выдачи стал устойчивым. К вашей сортировке добавляется id последним ключом, а список без сортировки теперь приходит по возрастанию id — раньше он приходил в неопределённом порядке аккаунта. Это не косметика: список отдаётся страницами по 50, и при сортировке по неуникальному полю (например по названию) строка с тем же значением могла на границе страниц попасть в две страницы сразу или пропасть из выдачи вовсе. Теперь порядок полный и однозначный, поэтому и постраничный обход повторяем. Если вы сортировали по id сами, ваше направление сохраняется — второй ключ не добавляется.

Разбиение на страницы у хранилищ идёт постранично по 50 записей, поэтому при сортировке запрашивайте нужный объём одним вызовом (limit до 5000), а не листайте страницы вручную.

FIX-0729-17: перезапуск galaxy-приложения через /reboot

Было

Для galaxy-приложения (kind=GALAXY_APP) у POST /v1/infra/servers/:id/reboot не было рабочего сценария: вызов возвращал 422 VM_MISSING (у контейнера нет собственной виртуальной машины), а единственным путём восстановления зависшего приложения оставалось удаление — оно стирает постоянный том /data.

Стало

/reboot перезапускает контейнер приложения как «пинок» самовосстановления — постоянный том /data при этом сохраняется. Вызов принимается в статусе running или error и возвращает совещательный вердикт: restarted (контейнер перезапущен) и healthy (контейнер запустился и перестал перезапускаться — проверка контейнера, не HTTP-ответа приложения), а при healthy: false — поле hint о том, как залить исправленную версию. Перезапуск не снимает состояние ошибки — крашащееся приложение авторитетно чинится редеплоем исходников через POST /v1/infra/servers/:id/deploy. Новые коды ошибок для этого сценария: GALAXY_APP_REBOOT_USE_AGENT_CONTROLS (409 — приложением агента или бота управляют из его панели), GALAXY_APP_BUSY (409 — на хосте выполняется другая команда, в ответе Retry-After), GALAXY_HOST_UNREACHABLE (502 — хост недоступен). Перезагрузка обычного сервера не изменилась.

NEW-0729-18: totalDefault — умолчание по meta.total на самом ключе

У API-ключа появилась настройка totalDefault: заказывать ли подсчёт количества спискам этого ключа, когда сам запрос не передал withTotal. Значение true — присылать meta.total, false — не присылать, null — наследовать платформенное умолчание. По умолчанию у всех ключей null.

Настройка правится в кабинете на странице ключей и через PATCH /v1/keys/:id полем totalDefault (управляющий ключ vibe_live_). Перевыпуск ключа через POST /v1/keys/:id/rotate настройку сохраняет — как и режим доступа. Изменение попадает в журнал аудита.

Действующее значение видно в GET /v1/me — блок totalDefault показывает всю цепочку: key (настройка ключа), platform (платформенное умолчание), effective (что получится, если запрос не передаст withTotal) и source — откуда взялось действующее значение.

Настройка нужна там, где менять код интеграции дороже, чем один раз переключить ключ: она задаёт умолчание сразу всем спискам этого ключа. Точечно её перекрывает параметр запроса withTotal.

NEW-0729-19: meta.nextAfterId — курсор следующей страницы при сортировке по id

Ответы GET /v1/{entity} и POST /v1/{entity}/search получили необязательное поле meta.nextAfterId — идентификатор последней отданной записи, строкой.

Поле приходит, когда выполнены три условия сразу: у сущности числовой идентификатор и поддержка курсорного листания, сортировка запроса — строго id по возрастанию, и meta.hasMore равен true. На последней странице поля нет: идти уже некуда. Сегодня условиям отвечают сделки, лиды, контакты, компании, предложения и элементы смарт-процессов.

Значение передаётся обратно тем же фильтром, которым курсорное листание делалось и раньше: filter[>id]=<nextAfterId> при сортировке id по возрастанию. Нового параметра запроса не появилось — поле лишь избавляет от чтения идентификатора из последней строки ответа вручную.

Такое листание не зависит от смещения и не дорожает к концу коллекции, поэтому для обходов в десятки тысяч записей оно предпочтительнее, чем растущий offset.

NEW-0729-20: withTotal — списку можно не заказывать подсчёт количества

Списочные вызовы приняли необязательный параметр withTotal. У GET /v1/{entity} это параметр запроса ровно с двумя допустимыми значениями — true и false; у POST /v1/{entity}/search — поле тела с булевым значением. Любая другая запись читается как «параметр не передан», ошибки не будет.

withTotal=false — просьба не считать количество. Там, где платформа может её выполнить, подсчёт у Битрикс24 не заказывается и meta.total в ответе отсутствует. Там, где обойтись без подсчёта нельзя, параметр не действует и meta.total приходит как раньше. Поэтому наличие поля проверяйте, а не предполагайте.

Листать надо по meta.hasMore — он считается по полноте страницы и доводит цикл «читай, пока hasMore» до конца независимо от того, был ли подсчёт. При сортировке строго по id по возрастанию ответ дополнительно несёт meta.nextAfterId, который передаётся обратно в filter[>id].

Если параметр не передан, значение берётся из настройки ключа, а при её отсутствии — из платформенного умолчания. Действующее сейчас значение и всю эту цепочку показывает блок totalDefault в GET /v1/me.

Когда точное количество действительно нужно, спрашивайте его прямо: POST /v1/{entity}/aggregate с функцией count отдаёт число одним вызовом, без выгрузки записей. Считать количество постраничным обходом коллекции не надо — это десятки вызовов вместо одного и самый дорогой способ узнать одну цифру.

FIX-0729-21: meta.hasMore в списках считается по полноте страницы, а meta.total может отставать до минуты

Было

meta.hasMore в ответах GET /v1/{entity} и POST /v1/{entity}/search выводился из meta.total: «есть ещё» означало «offset плюс отданные строки меньше общего количества». Пока количество считалось на каждый вызов, это совпадало с правдой.

Стало

Платформа перестаёт запрашивать у Битрикс24 подсчёт количества на каждый повторный вызов с той же парой «ключ и запрос» — подсчёт непропорционально дорог для портала. Отсюда два наблюдаемых следствия.

meta.hasMore на таких ответах считается по полноте страницы: пришла полная страница — «возможно, есть ещё»; пришла неполная — список закончился. Цикл «читай, пока hasMore» по-прежнему доходит до конца всегда. Если коллекция ровно кратна limit, последний шаг вернёт пустой список — это штатный признак конца.

meta.total остаётся числом и остаётся на месте, но становится информативным: он может отставать до минуты, поэтому число отданных строк может оказаться больше него.

Влияние на интеграторов

Менять ничего не нужно, если листание идёт по meta.hasMore — это рекомендованный способ. Если код опирается на meta.total как на точную границу цикла или проверяет «отдано не больше, чем total», переключитесь на meta.hasMore. Точное количество на момент запроса даёт POST /v1/{entity}/aggregate с функцией count.

2026-07-28

NEW-0728-1: приложение стёртого автора больше не выдаёт новые пользовательские токены

Когда автор приложения удалил свои персональные данные, приложение перестаёт выдавать токены новым пользователям. Раньше такой запрос доходил до Битрикс24 и создавал рабочий токен вместе с именем и почтой нового пользователя, хотя автора в системе уже нет.

Возврат из GET /v1/oauth/callback в этом случае приходит на ваш redirect_uri с ?error=app_unavailable — той же формой, что уже используют token_exchange_failed, invalid_domain и profile_fetch_failed. POST /v1/oauth/placement-session отвечает 403 с кодом APP_UNAVAILABLE — тем же кодом отвечает и обработчик размещения, куда Битрикс24 открывает виджет приложения (раньше там приходил 401 с общим USER_AUTH_REQUIRED, который читался как проблема авторизации пользователя).

Уже выданные токены и сессии этого приложения не продлеваются. Ранее работавшие вызовы не затронуты: пока автор активен, поведение обоих эндпоинтов прежнее.

NEW-0728-2: снятие зависшего лока стало кросс-репличным; ответ DELETE /lock несёт broadcast и localLock

DELETE /v1/infra/servers/:id/lock теперь рассылает снятие лока на все реплики платформы, поэтому снимает зависший лок и тогда, когда он держится на другой реплице (частый случай под горизонтальным масштабированием). Ответ дополнен полями broadcast (снятие разослано по флоту, best-effort) и localLock (держался ли лок на этой реплице). Поле released теперь описывает только текущую реплику и не является подтверждением снятия по всему флоту — при зависшем локе на другой реплице released может быть false, хотя лок реально снят; не опрашивайте эндпоинт в цикле до released: true, повторите операцию. Прежние вызовы работают без изменений (поля добавлены аддитивно). Дополнительно зависший exec-лок теперь гарантированно снимается серверным авто-сбросом вскоре после истечения TTL.

Ответ POST /v1/infra/servers/:id/exec при 502 EXEC_BUSY для galaxy-приложения теперь несёт error.hint с честным путём восстановления (общий exec-канал хоста; эскалация к платформенной команде — DELETE /lock тут не помогает, так как блокирует мьютекс агента). Ответ POST /v1/infra/servers/:id/deploy при 409 GALAXY_APP_BUSY дополнен error.hint, retryable: true, retryAfter и заголовком Retry-After.

NEW-0728-3: указатели на открытые линии, чек-листы задач и ТЗ приложений в ответах самоописания

Ответ GET /v1/guide дополнен указателем data.appBlueprints — ссылка на документацию готовых ТЗ приложений и условие ответа 403 BLUEPRINTS_DISABLED.

Для ключа со скоупом imopenlines в ответ добавлен блок data.openLines: описание раздела, ссылки на все семь страниц документации и разграничение двух групп эндпоинтов. Настройка линий и действия оператора доступны на любом портале. Статистика дашборда до прихода обновления Битрикс24 отвечает 422 METHOD_NOT_YET_AVAILABLE, а без права на просмотр статистики — 403 B24_TARIFF_RESTRICTION.

В ответе GET /v1/me блок api._rules получил три новых указателя — на чек-листы задач, открытые линии и ТЗ приложений.

Поля аддитивные, существующие клиенты не затронуты. Сами эндпоинты не менялись.

FIX-0728-4: деплой в галактику восстанавливает оборванный туннель хоста

Было

Деплой галакси-приложения на хост, туннель которого молча оборвался под нагрузкой сборки (в том числе «фантомно-CONNECTED» хост — флаг завис, а туннель уже мёртв), зацикливался на GALAXY_HOST_UNREACHABLE / GALAXY_DEPLOY_INTERRUPTED: платформа не чинила туннель сама, и повторные попытки клиента били в тот же мёртвый туннель.

Стало

Такой обрыв на пути деплоя теперь запускает фоновый ремонт туннеля хоста, поэтому честный повтор попадает уже на восстановленный туннель и деплой доезжает. Коды ошибок и их «повторяемая» семантика не изменились — меняется только поведение (самовосстановление).

BC-0728-5: единый конверт 404 для несуществующих маршрутов /v1

Поддержка старого формата до: 28.07.2026

Прежняя форма тела не возвращается — переходного периода с двойным форматом нет, изменение действует с даты публикации.

Было

Запрос на несуществующий путь или неподдерживаемый HTTP-глагол под /v1/ отвечал телом веб-сервера вне единого конверта API:

JSON
{
  "message": "Route GET:/v1/dealz not found",
  "error": "Not Found",
  "statusCode": 404
}

Стало

Тот же запрос отвечает в едином конверте V1 с новым кодом ROUTE_NOT_FOUND. HTTP-статус прежний — 404:

JSON
{
  "success": false,
  "error": {
    "code": "ROUTE_NOT_FOUND",
    "message": "Route GET:/v1/dealz not found. Check GET /v1/guide for available endpoints and verbs."
  }
}

ROUTE_NOT_FOUND означает «такого маршрута или глагола не существует» — сверьте путь со списком в GET /v1/guide. Не путайте с ENTITY_NOT_FOUND и доменными кодами вида *_NOT_FOUND: там маршрут существует, не найден запрошенный объект. Вне /v1/ форма тела 404 не изменилась.

Что делать интеграторам

Ветвитесь по error.code, а не по форме тела. Клиент, который разбирал поля message, error и statusCode прежнего тела на путях /v1/, должен перейти на единый конверт success и error.code.

BC-0728-6: календарь: неизвестное имя в select отвечает ошибкой, а не пустым объектом

Поддержка старого формата до: 28.07.2026

Было

GET /v1/calendar-events (а также GET /v1/calendar-events/{id}, POST /v1/calendar-events/search и calendar-events-подвызовы POST /v1/batch) при незнакомом имени поля в select (например dateFrom — такого поля нет, реальное имя from) молча возвращали объекты, состоящие из одного id. Клиент считал, что сузил трафик, а на деле терял данные.

Стало

Незнакомое имя поля в select отвечает 400 UNKNOWN_SELECT_FIELD с перечнем допустимых имён (Available: …). Имена в формате Битрикс24 (DATE_FROM) и алиасы дат (updatedAt) по-прежнему принимаются и проецируют канонический ключ — ошибку вызывают только имена, которые не разрешаются ни в одно поле схемы. На остальных сущностях поведение прежнее: незнакомое имя даёт предупреждение в meta.warnings, без ошибки.

Что делать интеграторам

Брать имена полей из GET /v1/calendar-events/fields и убрать из select несуществующие имена (dateFrom/dateTo → from/to). Клиенты, не передающие select или передающие корректные имена, не затронуты.

NEW-0728-7: календарь: поля occurrenceIndex и version

У событий календаря появились два поля только для чтения. occurrenceIndex — порядковый номер вхождения в развёрнутой серии повторяющегося события, с нуля: строки серии делят один id, и пара id + occurrenceIndex однозначно идентифицирует строку набора. version — монотонный счётчик изменений события, растёт при каждом изменении и не зависит от региональных настроек аккаунта. Рецепт диффа: запросите GET /v1/calendar-events с select=id,version, сравните пары со своим снимком и дочитайте изменённые события по id.

NEW-0728-8: чаты: эхо ограничения limit в meta

Три эндпоинта чатов — GET /v1/chats/recent, GET /v1/chats/:dialogId/messages и GET /v1/chats/:dialogId/users — при срезании переданного limit до допустимого диапазона дополняют ответ полем meta с парой requestedLimit и appliedLimit: сколько запросили и сколько применено. Когда limit в диапазоне, meta не добавляется — конверт прежний. Для чтения сообщений задокументирован реальный потолок облачного Битрикс24 — не больше 50 записей за вызов независимо от limit, продолжение читается курсором lastId.

NEW-0728-9: взаимные алиасы полей дат updatedAt и createdAt

Поля дат в каталоге сущностей носят два семейства имён: одни сущности объявляют updatedAt и createdAt, другие — updatedTime и createdTime. Теперь члены пары принимаются на вход взаимозаменяемо: в filter и select — на всех сущностях, где объявлен парный ключ, в sort — на сущностях с camelCase-схемой полей. Например, updatedTime на сущности с полем updatedAt работает как updatedAt, и наоборот. Канонические имена полей в ответах не меняются — алиас действует только на входе.

NEW-0728-10: POST-алиас поиска по Базе знаний

Поиск документов Базы знаний 2.0 принимает и POST /v1/note/documents/search с JSON-телом { "query": "...", "limit": 20 } — для агентов, которые ожидают поиск POST-запросом по аналогии с остальными сущностями. Канонической остаётся форма GET /v1/note/documents/search с query-параметрами. Обе формы принимают только query и limit, при передаче параметра и в теле, и в query приоритет у тела.

NEW-0728-11: лента: limit до 200 записей за запрос

GET /v1/posts принимает limit от 1 до 200. Страница ленты Битрикс24 фиксирована в 50 записей — при limit больше 50 платформа склеивает до четырёх страниц в один ответ. Значение больше 200 отвечает прежним 400 INVALID_LIMIT. В meta добавлено поле returned — фактическое число записей в ответе, а meta.nextOffset при многостраничном чтении выводится из окна ответа, чтобы цепочка страниц продолжалась как раньше.

FIX-0728-12: календарь: честные offset и hasMore, детерминированный порядок

Было

GET /v1/calendar-events при любом offset возвращал начало одного и того же набора: запрошенный диапазон приходит от Битрикс24 одним массивом без пагинации, поэтому каждая «страница» повторяла первую, meta.hasMore оставался true, а хвост набора за пределами первой страницы был недоставаем.

Стало

Полный набор сортируется детерминированно — по началу события from, при равенстве по id, затем по occurrenceIndex — и из него отдаётся честное окно от offset до offset + limit. meta.total — число вхождений в наборе: повторяющиеся события развёрнуты по-вхожденно, строки серии делят один id. meta.hasMore отвечает true только пока за окном остаются записи. Порядок элементов в ответе стал детерминированным и может отличаться от прежнего.

Влияние на интеграторов

Обход набора через offset теперь отдаёт весь диапазон. Клиентам, которые обходили дубли страниц самостоятельно, ничего менять не нужно — дублей больше нет.

FIX-0728-13: select принимает объявленные имена Битрикс24 и предупреждает о незнакомых полях

Было

Параметр select понимал только канонические имена полей из GET /v1/{entity}/fields. Имя в другом написании — исходное имя Битрикс24 (DATE_FROM, UF_DEPARTMENT) или другой регистр — молча выпадало из проекции: поля не было в ответе без какого-либо признака ошибки, а запрос из одних таких имён вырождался в объекты с единственным полем id. Пакетные вызовы select не применяли вовсе: и глобальный POST /v1/batch, и пакетный вызов одной сущности POST /v1/{entity}/batch возвращали полные объекты.

Стало

select принимает канонические имена без учёта регистра, объявленные исходные имена Битрикс24 (DATE_FROM проецирует from, UF_DEPARTMENT — departmentId) и взаимные алиасы полей дат — поле приходит в ответе под каноническим ключом. Незнакомое имя больше не теряется молча: список, поиск и получение записи по id дополняют ответ массивом meta.warnings с записями { "code": "UNKNOWN_SELECT_FIELD", "field": "<имя>" } — до 10 предупреждений на ответ. Оба пакетных вызова применяют select к операциям списка и поиска так же, как одиночные эндпоинты; получение записи по id внутри глобального пакета select не применяет. Глобальный вызов дополнительно отдаёт предупреждения о незнакомых именах в meta по каждому своему вызову. Пакетный вызов одной сущности предупреждений не несёт и жёсткого отклонения незнакомых имён не выполняет — незнакомое имя там по-прежнему просто отсутствует в ответе.

Влияние на интеграторов

Ответы одиночных эндпоинтов только дополняются. В пакетных вызовах клиент, который передавал select и при этом читал поля за его пределами, теперь получит только запрошенные поля — уберите select из вызова или перечислите в нём все нужные поля.

FIX-0728-14: быстрый отказ оконного поиска при таймауте портала

Было

Поиск POST /v1/{entity}/search с широким диапазоном дат разбивается на временные окна. Таймаут Битрикс24 на первом окне пропускался, и остальные окна выполнялись каждое со своим таймаутом: до ответа проходило 60–75 секунд, после чего приходил 503 BITRIX_TIMEOUT. Если поздние окна успевали, возможен был частичный 200 с неполными данными после той же минуты ожидания.

Стало

Таймаут первого окна (BITRIX_TIMEOUT) завершает запрос сразу: ответ 503 с кодом BITRIX_TIMEOUT и заголовком Retry-After приходит примерно за 15 секунд, остальные окна не выполняются. Частичный 200 при таймауте первого окна больше невозможен — это осознанный размен: окна одинаковы по форме, таймаут первого предсказывает таймауты остальных, а частичный ответ после минуты ожидания провоцировал волны повторов. Таймаут любого последующего окна обрабатывается как раньше — окно пропускается, ответ может быть частичным.

Влияние на интеграторов

Повторяйте запрос по заголовку Retry-After. Клиенты, которые полагались на частичный ответ при перегрузке портала, теперь получают быстрый 503 — сузьте диапазон дат или повторите позже.

FIX-0728-15: Galaxy-деплой из .zip: честная причина ошибки извлечения вместо EMPTY_BUILD_CONTEXT

Деплой Galaxy-приложения из .zip теперь возвращает реальную (санитизированную) причину ошибки извлечения архива в buildLog 502-ответа, а не вводящее в заблуждение «EMPTY_BUILD_CONTEXT»/общее сообщение.

FIX-0728-16: отбой лимитера времени операций Битрикс24 возвращается как `OPERATION_TIME_LIMIT` с `Retry-After`

Было

Когда портал отбивал вызов метода, израсходовавшего бюджет рабочего времени, платформа отдавала 429 RATE_LIMITED с Retry-After: 2, а сам вызов повторяла до трёх раз. Против адресного отказа на несколько минут эти повторы не помогали, а Retry-After: 2 вводил в заблуждение: клиент возвращался через две секунды и получал тот же отказ.

Стало

Такой отбой возвращается кодом OPERATION_TIME_LIMIT — тем же, что отдаёт портал, — с Retry-After, посчитанным от известного срока снятия ограничения, и понятным текстом в userMessage. Повторы отключены: ограничение адресное — на тройку «портал + ключ + метод», ровно так же, как его накладывает сам Битрикс24, — и до истечения срока платформа отбивает вызовы этого метода сама, не обращаясь к порталу. Остальные методы портала, как и тот же метод под другим ключом, не затронуты. Прочие отказы 429 (в том числе RATE_LIMITED и QUEUE_OVERFLOW) не изменились.

Соблюдайте Retry-After: раньше срока тот же вызов пройти не может. Тяжёлые чтения стоит разредить по времени или сузить — меньше полей, меньше страница, POST /v1/batch.

FIX-0728-17: Значение * в select возвращает все поля

Было

Привычная для Битрикс24 запись select: ["*"] (и ["*", "UF_*"]) на одиночных эндпоинтах приводила к обратному результату: * не совпадает ни с одним объявленным полем, поэтому в ответе оставался только id. Признака ошибки не было — запись просто приходила пустой.

Стало

* и UF_* (в любом регистре) распознаются как запрос «вернуть все поля»: отбор полей не применяется, приходит полная запись. Незнакомое имя, переданное рядом со звёздочкой, не отклоняется — ответ дополняется предупреждением UNKNOWN_SELECT_FIELD. Это работает одинаково в списке, поиске, получении записи по id и в обоих пакетных вызовах — POST /v1/batch и POST /v1/{entity}/batch. У событий календаря, где незнакомое имя поля отвечает ошибкой 400, значение * ошибкой не считается.

Влияние на интеграторов

Ничего менять не нужно. Клиент, переносивший запросы из портального кода Битрикс24 вместе с select: ["*"], начнёт получать полные записи вместо объектов с одним id.

FIX-0728-18: оконный поиск останавливается, набрав запрошенное, и сообщает о неполном окне

Было

POST /v1/{entity}/search с широким диапазоном дат разбивает диапазон на временные окна и объединяет их результаты. Обход шёл до конца диапазона даже тогда, когда запрошенных limit записей уже набрано: поиск с limit: 50 по диапазону в несколько месяцев вычитывал весь диапазон целиком — отвечал долго и создавал на аккаунте Битрикс24 нагрузку, несопоставимую с размером ответа. Если внутри одного окна подходящих записей оказывалось больше, чем отдаёт одно чтение окна, окно возвращало только начало своего набора, и делало это молча: ни признака в ответе, ни способа дочитать остаток (оконный поиск отвергает offset > 0 кодом UNSTABLE_OFFSET_PAGINATION).

Молчала и вторая причина неполноты, на аккаунтах, где окна читаются пакетами: набрав предельные 5000 записей, поиск перестаёт отправлять оставшиеся окна и отдаёт срезанный префикс — в ответе об этом тоже ничего не было.

Стало

Обход окон прерывается, как только набрано больше уникальных записей, чем запрошено в limit. В ответе по-прежнему не больше limit записей, а признак «есть ещё» несёт hasMore. При досрочной остановке meta.total равен количеству набранного, то есть это нижняя оценка числа подходящих записей, а не полный счёт по диапазону — ровно так уже вёл себя этот поиск на аккаунтах с пакетным чтением окон, теперь поведение единое.

Неполная выдача больше не молчит — ни по одной из двух причин, независимо от того, читаются окна по одному или пакетами, на сущностях, чей метод списка Битрикс24 отдаёт постранично. Ответ дополняется предупреждением { "code": "WINDOW_TRUNCATED", "field": "…", "message": "…" } в массиве meta.warnings, где field — поле диапазона, по которому шло разбиение. Код предупреждения один и тот же в обоих случаях — ветвиться по нему можно, не разбирая текст; message называет сработавшую причину: одно окно держало больше записей, чем отдаёт одно чтение окна, либо набраны предельные 5000 записей и оставшиеся окна не отправлялись.

Исключения названы прямо. Три сущности предупреждения не получат никогда, потому что их метод списка Битрикс24 не отдаёт постранично: файлы и папки (/v1/files, /v1/folders) и рабочие группы (/v1/workgroups). Там запрос идёт одиночным вызовом, limit в Битрикс24 не передаётся вовсе, и аккаунт отдаёт свою страницу примерно на 50 записей: окно, в котором лежит 200 дисковых объектов, вернёт 50 и промолчит, как и до правки. У страниц (/v1/pages), сайтов (/v1/sites) и событий календаря (/v1/calendar-events) метод списка тоже не постраничный, но он отдаёт весь запрошенный набор за один вызов — там предупреждение просто недостижимо, а неполноты не возникает.

Дополнительно снижена нагрузка, которую этот поиск создаёт на аккаунте Битрикс24: вырожденная нижняя граница по id больше не уходит в Битрикс24. Снимаются две формы, и опираются они на разное. >= со значением 0 или меньше и > с отрицательным значением исключают только отрицательные id — они тавтологичны при одном допущении неотрицательности. > ровно с нулём (filter[>id]=0 — именно её передают в начале обхода курсором) исключает запись с id = 0, то есть дополнительно опирается на автоинкрементную нумерацию записей Битрикс24 от единицы; это целевой случай правки, и он снимается сознательно. Граница >=id=1 сохраняется — гард намеренно узкий и смотрит только на значения 0 и меньше.

Состав выдачи не меняется ни на одной сущности, где Битрикс24 действительно применяет фильтр по id. Известное исключение — воронки (/v1/categories): среди них есть запись с id: 0 («Общая»), но метод списка воронок игнорирует filter целиком, поэтому и до, и после правки в ответ приходит полный набор воронок. Форма ответа прежняя.

Влияние на интеграторов

Не считайте meta.total точным числом подходящих записей на широком диапазоне дат — при досрочной остановке это нижняя оценка; ориентируйтесь на hasMore. Дочитать оконный поиск постранично нельзя (offset > 0 отвергается), поэтому «есть ещё» закрывается либо большим limit (до 5000), либо более узким диапазоном дат.

Проверяйте meta.warnings на WINDOW_TRUNCATED: это признак неполной выдачи, и пагинацией он тоже не лечится — сузьте диапазон дат или добавьте фильтров, чтобы поиск перестал упираться в потолок. Одного hasMore для этого недостаточно: оконное разбиение работает только при offset === 0, поэтому запрос следующей страницы уйдёт уже не оконным и даст другой результат. Поиск по узкому диапазону, который не разбивается на окна, не затронут.

Учтите границу оконного поиска: сортировка применяется внутри окна, а не поверх объединения окон. Окна строятся от старых к новым и склеиваются в этом же порядке, после чего результат режется до limit; глобальной сортировки по объединению нет. Поэтому запрос с сортировкой по убыванию и небольшим limit по широкому диапазону дат возвращает самые СТАРЫЕ подходящие записи, а не самые новые. Само поведение не новое, но досрочная остановка закрывает сортировку после слияния как способ починки (записи поздних окон больше не вычитываются), поэтому называем это прямо. Нужен настоящий «последние N по дате» — либо сузьте диапазон так, чтобы разбиение на окна не включалось, либо возьмите диапазон одним запросом с большим limit и отсортируйте на своей стороне.

2026-07-27

NEW-0727-1: фавикон приложения одной строкой — /_gw/icon

Фавикон во вкладке браузера теперь ставится одной статической строкой без id сервера:

html
<link rel="icon" href="/_gw/icon">

/_gw/icon — платформенный путь на домене приложения; он всегда отдаёт текущую загруженную иконку. Одна загрузка POST /v1/infra/servers/:id/icon управляет и карточкой в каталоге Bitrix24, и фавиконом: перезалили иконку — фавикон обновится сам (~5 минут), пересобирать приложение не нужно. Свой статический файл иконки в приложение класть больше не нужно. Строка совместима с созданием приложения одним запросом (id не требуется).

Дополнительно ответ POST /v1/infra/servers/:id/deploy и создания приложения одним запросом POST /v1/infra/servers теперь возвращает запись в warnings[], если иконка ещё не загружена — с точным эндпоинтом для загрузки (иконка грузится отдельным запросом, id известен только после создания). Тот же warnings[] по-прежнему подсказывает, если не заданы displayName/description. Успешный деплой без иконки или названия больше не выглядит завершённым молча.

NEW-0727-2: source-at-create: ошибка SOURCE_AT_CREATE_GALAXY_ONLY теперь несёт подсказку с путём к галактике

Отказ POST /v1/infra/servers с source, который не удалось разместить в галактике (на портале в режиме «обе стратегии» без открытого galaxy-хоста, либо на портале только со standalone), теперь дополнительно несёт error.hint — с понятным путём: как получить galaxy-хост и/или как задеплоить в два шага на выделенный сервер. Код и текст ошибки не изменились.

FIX-0727-3: galaxy-приложения: PATCH /sleep и PATCH /port теперь отвечают 400 — управляйте ими со страницы «Галактики»

Было

Для приложения, размещённого в галактике (GALAXY_APP), PATCH /v1/infra/servers/:id/sleep возвращал 200 и записывал sleepAfterMinutes, а PATCH /v1/infra/servers/:id/port отвечал 404/409 — расходясь с задокументированным контрактом /v1/me (deployment.galaxyApp), где ни одно V1-действие жизненного цикла к galaxy-приложению не применяется.

Стало

Оба вызова для galaxy-приложения отвечают 400 с error.code = "GALAXY_APP_USE_GALAXY_ROUTE" и ничего не меняют. Настраивайте авто-сон приложения через маршрут галактики; порт у galaxy-приложения закреплён за хостом и не задаётся. Для обычных (standalone) серверов поведение /sleep и /port не изменилось.

FIX-0727-4: galaxy-приложение восстанавливается после обрыва туннеля хоста

Было

Если у galaxy-хоста обрывался защищённый туннель (сервер в статусе RUNNING, но связь потеряна), развёртывание и выполнение команд galaxy-приложения возвращали 502 GALAXY_HOST_UNREACHABLE, а запрос логов — пустой ответ с подсказкой. Хост оставался недостижим до ручного ремонта: повтор того же запроса упирался в ту же ошибку сколь угодно долго.

Стало

Платформа теперь сама восстанавливает туннель хоста в фоне, не задерживая ответ. Повтор того же запроса проходит, как только хост переподключается (обычно в течение минуты). Параллельные развёртывания/команды на один общий хост не запускают дублирующее восстановление.

Влияние на интеграторов

Код ошибки и форма ответа не изменились — 502 GALAXY_HOST_UNREACHABLE (для развёртывания и выполнения команд) по-прежнему помечен как повторяемый, а логи по-прежнему отдают пустой список с подсказкой. Изменилось только то, что теперь повтор приводит к успеху, а не к вечной ошибке. Продолжайте повторять запрос по своей обычной политике на этот код и на пустой ответ логов.

Затронутые эндпоинты: POST /v1/infra/servers/:id/deploy, POST /v1/infra/servers/:id/exec, GET /v1/infra/servers/:id/logs

NEW-0727-5: предупреждение, когда changelog деплоя некуда опубликовать

Ответ POST /v1/infra/servers/:id/deploy теперь возвращает запись в warnings[], если в запросе передан changelog, а деплой не создал новую версию исходников. Заметка о релизе привязана к версии, поэтому без неё текст никуда не сохраняется и в ленту канала приложения в мессенджере Bitrix24 не уходит.

Причина видна в поле source того же ответа: хранилище исходников выключено (feature-disabled-platform или feature-disabled-portal), сохранение не удалось (save-failed) либо загруженные байты совпали с предыдущей версией. Раньше такой деплой отвечал обычным успехом, и узнать, что заметка потерялась, было нечем. Предупреждение приходит и в JSON-режиме, и в событии done при ?stream=true.

FIX-0727-6: Список bizproc-templates без явного select возвращает все поля, включая id

Было

GET /v1/bizproc-templates и POST /v1/bizproc-templates/search без явного select возвращали только поле documentType. Без id клиент не мог выполнить последующие update/delete — список был бесполезен без второго запроса с явным select.

Стало

Оба вызова без select возвращают полный набор объявленных полей записи (id, moduleId, entity, documentType, autoExecute, name, description, modified, isModified, userId). Явный select работает как прежде. Форма запроса не изменилась.

NEW-0727-7: загрузка файла в документ Базы знаний возвращает assetMarkdown сразу

Загрузка файла через POST /v1/note/documents/{documentId}/files раньше отдавала только { id }, поэтому за готовым блоком для вставки в документ приходилось идти вторым запросом в GET /v1/note/documents/{documentId}/files/{id} либо собирать разметку [[image fileId=N]] руками.

Теперь ответ несёт весь объект файла — id, documentId, name, size, mimeType, assetType, assetMarkdown — то есть ту же форму, что отдаёт GET. Загрузили картинку, взяли assetMarkdown из ответа, вставили в текст документа и вызвали PATCH — второй запрос больше не нужен.

Изменение аддитивное: поле id осталось на месте и с тем же значением, поэтому клиент, который читает только его, продолжает работать без правок. Если конкретный портал вернёт объект без assetMarkdown, лишних полей платформа не придумывает — в ответе будет то, что пришло от Битрикс24.

FIX-0727-8: границы `ttlSeconds` у токенов доступа в машинной схеме совпали с поведением

Было

Схема OpenAPI для POST /v1/infra/servers/{id}/access-tokens обещала ttlSeconds в диапазоне от 60 секунд до 30 суток. Платформа же с самого начала принимала от 300 секунд до 315 360 000 (десять лет) и отклоняла всё за этими пределами кодом 400 INVALID_TTL. Поэтому клиент или генератор клиентских библиотек, взявший минимум прямо из схемы, получал жёсткий отказ на значении, которое схема сама и предлагала, а вариант «Бессрочно» из интерфейса выглядел недоступным через API. Текстовая документация всё это время была верна — расходилась только машинная схема.

Стало

Схема берёт границы и значение по умолчанию из тех же констант, которыми проверяется запрос, поэтому разойтись им больше нечем: минимум 300, максимум 315 360 000, по умолчанию 86 400.

Влияние на интеграторов

Поведение эндпоинта не менялось — менялось только то, что о нём написано в машинной схеме. Если вы генерировали клиента по OpenAPI и он валидировал ttlSeconds на своей стороне, перегенерируйте его: прежний клиент отклонял бы корректные значения больше 30 суток и разрешал бы заведомо отказные меньше 300 секунд.

NEW-0727-9: Приложение в плейсменте может авто-ресайзить свой iframe

Приложение, встроенное в плейсмент, теперь может сообщать платформе высоту своего контента, и платформа растит iframe под неё — раньше высоту фиксировал Битрикс24 и высокий контент обрезался. Приложение постит сообщение родительскому окну: window.parent.postMessage({ type: 'vibe:resize', height: <пиксели> }, '*'). Принимается тип vibe:resize или vibe:setHeight с числовым полем height; targetOrigin должен быть '*' — браузер сверяет его с непосредственным родителем окна. Пересчитывайте высоту при изменении контента, например через ResizeObserver. Полное описание и рекомендации — раздел «Авто-высота iframe» руководства по рантайму приложения.

Функция активируется на стороне платформы по аккаунтам Битрикс24; если ресайз пока не срабатывает, для вашего аккаунта она ещё не активна.

FIX-0727-10: offset в списках считается по записям, а не по страницам

Было

Битрикс24 отдаёт списки страницами по 50 и трактует смещение как номер страницы, а не как число записей. Мы передавали offset как есть, поэтому он молча округлялся вниз до кратного 50: offset=0, offset=7 и offset=49 возвращали одну и ту же первую страницу — без ошибки и без предупреждения. Обход выборки шагом меньше 50 записей зацикливался на первой странице, а шаг ровно в 50 работал и создавал впечатление, что параметр исправен.

Стало

offset считается по записям на generic-списках: GET /v1/{entity}, POST /v1/{entity}/search, POST /v1/batch (действие list) и GET /v1/{entity}/{id}/activities. offset=7 начинает выборку с 8-й записи. Смещение и limit независимы: ?limit=2&offset=51 вернёт ровно две записи, начиная с 52-й. Битрикс24 по-прежнему отдаёт страницами по 50 — Вайбкод запрашивает страницу, покрывающую нужную позицию, и отбрасывает лишнее начало; цена — не более одной дополнительной страницы у Битрикс24 на запрос.

Заодно исправлены два следствия. meta.hasMore учитывает смещение: раньше у сущностей, чей список Битрикс24 отдаёт под именованным ключом — сделки, контакты, компании, лиды, задачи, заказы, товары, счета и другие, всего два десятка, — он сравнивал только длину страницы с общим количеством и оставался true на последней странице при ненулевом offset. GET /v1/{entity}/{id}/activities теперь уважает limit: метод Битрикс24 не принимает ограничение выборки, поэтому раньше приходила вся страница целиком независимо от запрошенного значения.

Если выборка на запрошенной позиции оказалась пустой из-за фильтрации на стороне Битрикс24, ответ несёт meta.warnings с кодом OFFSET_BEYOND_FETCHED_PAGE — раньше это выглядело как пустой список без объяснения.

Для глубокой навигации по большим выборкам курсор по ключу (filter[>id] с order[id]=asc) по-прежнему надёжнее смещения: он не зависит от глубины и устойчив к параллельным изменениям.

Влияние на интеграторов

Менять ничего не нужно: смещение, кратное 50, работает как раньше, и код, который его так и использовал, продолжит работать без правок.

Два момента стоит проверить. Первый — GET /v1/{entity}/{id}/activities с явным limit: раньше приходила вся страница (до 50 записей) независимо от значения, теперь придёт ровно запрошенное количество. Если код полагался на то, что за один вызов вернётся больше запрошенного, увеличьте limit или пройдите выборку постранично. Второй — обход выборки шагом меньше 50: раньше он зацикливался на первой странице, теперь идёт вперёд, поэтому цикл, который «выручал» лишний выход по счётчику, начнёт возвращать новые записи.

Остаточные исключения, где смещение НЕ построчное. Во-первых, четыре сущности generic-слоя, у которых метод Битрикс24 округляет смещение до границы страницы, а расширить выборку сверх запрошенного размера нельзя, не рискуя потерять записи: calendar-events, calendar-sections, telephony-lines, workgroups. Смещение там осталось прежним. Во-вторых, отдельные эндпоинты с собственными обработчиками, которые в это изменение не входили: /v1/warehouses, /v1/bookings, /v1/posts, /v1/requisite-links, /v1/lists, /v1/timeline-logs и история стадий в /v1/crm-extras. Их поведение не изменилось.

У четырёх сущностей generic-слоя выше смещение осталось прежним, но meta.hasMore у них стало точнее: раньше на последней странице при ненулевом смещении он мог сказать «больше нет», хотя записи оставались.

FIX-0727-11: статус закрытого тикета обратной связи больше не показывается как «в работе»

Было

Тикет, попавший под детекцию probe-кампании, показывался автору со статусом NEW независимо от того, что с ним реально происходило. Фильтр списка работает по настоящему статусу, поэтому закрытый тикет одновременно попадал во вкладку «Решено» и рисовался бейджем «в работе» — один и тот же тикет противоречил сам себе. Затронуты GET /v1/feedback и GET /v1/feedback/{id}.

Стало

Маскируется только то состояние, для которого маска и заводилась: авто-архив. Закрытый тикет отдаёт RESOLVED, тикет в работе — свой реальный статус, а авто-архив по-прежнему приходит как NEW. Ключи с доступом к обратной связи (management, скоуп vibe:feedback) как и раньше видят настоящий статус.

Влияние на интеграторов

Клиент, который читал status и ожидал NEW у такого тикета, теперь получит его фактический статус — это и есть исправление. Дополнительно: у тикета, отмеченного детекцией и уже закрытого, отзыв (PATCH {"status":"WITHDRAWN"}) и ответ автора теперь отклоняются с 409 FEEDBACK_CLOSED, как у любого закрытого тикета; раньше они проходили, потому что гейт сверялся с замаскированным статусом.

FIX-0727-12: Connect-ключи: сохранённые права авторитетны — vibe:ai / vibe:search больше не добавляются автоматически

Было

Ключ, выданный через VibeCode Connect, при каждом запросе автоматически получал платформенные права vibe:ai и vibe:search, даже если они не запрашивались и не были согласованы. Такой ключ мог обращаться к AI-эндпоинтам (/v1/chat/completions, /v1/ai/*) и поиску (/v1/search), и расход шёл со счёта аккаунта, к которому привязан портал.

Стало

Сохранённые на ключе права теперь авторитетны — платформа не расширяет их автоматически. Ключ, выданный через Connect, обращается к AI- и поиск-эндпоинтам только если соответствующее право реально присутствует в ключе; иначе ответ 403. Ротация ключа сохраняет этот признак. Обычные ключи, созданные в кабинете, поведения не меняют.

FIX-0727-13: Приложения: производный ключ и синхронизация прав не выдают больше, чем есть у вызывающего ключа

Было

Вызов POST /v1/apps авторитетным ключом (выданным через VibeCode Connect или производным от него) минтил парный ключ приложения с полным набором платформенных прав по умолчанию (vibe:infra, vibe:ai, vibe:search, vibe:storage) — даже если у самого вызывающего ключа этих прав не было. Так же PATCH /v1/apps/:id мог записать в объявление приложения vibe:*-право, которого у ключа нет. В обоих случаях производный ключ получал возможность обращаться к AI и поиску за счёт аккаунта, к которому привязан портал.

Стало

Производный ключ получает ровно те платформенные права, что реально есть у вызывающего ключа. Если у авторитетного ключа права нет, запрос с ним в теле возвращает 403 SCOPE_GRANT_REQUIRES_CONSENT, а список несогласованных прав приходит в error.details.unconsented. Права, которые у ключа есть (например согласованный vibe:storage), проходят как раньше. Срок жизни производного ключа наследуется от вызывающего. Синхронизация прав приложения на парные ключи больше никогда не добавляет vibe:* авторитетному ключу — сужение прав при этом по-прежнему применяется. Обычные ключи, созданные в кабинете, поведения не меняют: платформенные права им по-прежнему выдаются по умолчанию.

FIX-0727-14: таймаут вызова Битрикс24 стал управляемым, внутренний повтор после таймаута отменён

Было

Платформа всегда обрывала HTTP-вызов к Битрикс24 на 15-й секунде, а для методов чтения после обрыва делала одну внутреннюю повторную попытку — итого до ~30 секунд до ответа 503 BITRIX_TIMEOUT. Повтор при этом запускал второе параллельное выполнение того же вызова на портале: обрыв соединения не останавливает работу Битрикс24 над запросом.

Стало

  • Лимит времени одного вызова к Битрикс24 настраивается платформой (по умолчанию прежние 15 секунд). На порталах, где Битрикс24 отвечает медленно, платформа может поднять лимит — запросы, которые раньше стабильно завершались 503 BITRIX_TIMEOUT, доживают до реального ответа и возвращают данные.
  • Внутренняя повторная попытка после таймаута отменена для всех методов. 503 BITRIX_TIMEOUT на чтениях приходит примерно вдвое быстрее (~15 секунд вместо ~30), и запрос больше не выполняется на портале дважды. Повторы по 429 (rate limit) не тронуты.
  • Текст ошибки Bitrix24 did not respond within 15s подставляет фактический лимит (например, within 60s) — не опирайтесь на константу в тексте.

2026-07-25

NEW-0725-1: deploy: необязательное поле changelog

POST /v1/infra/servers/:id/deploy принимает новое необязательное поле changelog — обычный текст до 2000 символов с описанием, что изменилось в этой версии. При выходе новой версии приложения текст публикуется подписчикам в ленту канала приложения в мессенджере Bitrix24; если поле не передано, в ленту уходит только номер версии. Поле доступно и в JSON-, и в multipart-режиме деплоя. Прежние вызовы деплоя работают без изменений.

NEW-0725-2: вызовы моделей по короткоживущему токену партнёрской системы

Было

Ручки AI-роутера /v1/chat/completions, /v1/embeddings, /v1/audio/transcriptions и /v1/models принимали только обычный ключ платформы.

Стало

Те же ручки (и их /v1/ai/...-алиасы) дополнительно принимают короткоживущий токен нового типа. Его получает партнёрская система Битрикс24 по своему подписанному каналу; токен привязан к порталу и конкретному сотруднику, живёт один час и допущен ровно к этим восьми маршрутам — на любом другом пути ответ SCOPE_FORBIDDEN. Расход по такому токену считает сама платформа и списывает синхронно в AI-квоту портала, поэтому при исчерпании квоты вызов отбивается ещё до обращения к модели. Доступны только модели, включённые в программу квоты — перечень отдаёт /v1/models под этим же токеном.

Коды отказа, которые теперь может вернуть этот маршрут: TOKEN_INVALID, SCOPE_FORBIDDEN, MODEL_NOT_IN_QUOTA_PROGRAM, CREDENTIAL_NOT_PLATFORM, ACCOUNT_FROZEN, PORTAL_DELETED, PORTAL_BLOCKED, PORTAL_SUSPENDED. Конверт прежний: success и error с полями code и message.

Поведение обычных ключей платформы не изменилось: без токена нового типа ответы прежние, байт в байт.

2026-07-24

FIX-0724-1: дела: нераспознанное имя поля фильтра теперь отклоняется с 400 UNKNOWN_FILTER_FIELD

Было

GET /v1/activities и POST /v1/activities/search молча отбрасывали неизвестный ключ фильтра: запрос возвращал 200 success с фильтром, урезанным до пустого — то есть отдавал весь (owner-scoped или вообще весь) набор дел. Например {"filter":{"ownerTypeId":2,"ownerId":3,"bogusField":123}} игнорировал bogusField и возвращал все дела родительской сделки. Это расходилось с документацией и с поведением других сущностей CRM (компании, счета), где такой фильтр уже отклонялся.

Стало

Имя поля фильтра, которого нет в схеме дела — и которое не является пользовательским полем UF_*, ключом id или спец-токеном — теперь отклоняется до вызова Bitrix24 с 400 UNKNOWN_FILTER_FIELD и списком доступных полей, как уже делают companies/quotes/contacts. Полный список фильтруемых полей возвращает GET /v1/activities/fields.

Влияние на интеграторов

Фильтрация по реальным полям дела (в camelCase или в родном ВЕРХНЕМ регистре Bitrix24), по полям UF_*, операторы (>=, <, ! и т.п.), диапазоны и AND/NOT работают как прежде. Если вы полагались на молчаливый сброс нераспознанного ключа — уберите его из фильтра.

NEW-0724-2: /fields бизнес-процессных действий и роботов отдаёт названия и описания полей

Было

GET /v1/bizproc-activities/fields и GET /v1/bizproc-robots/fields описывали каждое поле только типом и флагом readonly, без человекочитаемых подписей.

Стало

По каждому из 12 полей теперь приходят label и description по-русски, что упрощает построение форм и подсказок. Типы полей не изменились.

FIX-0724-3: Скачивание своих файлов из хранилища больше не возвращает 403

Было

GET /v1/storage/objects/:key мог вернуть 403 при обращении к объекту, которым владелец ключа законно владеет, но который физически лежит под префиксом другого «семейства» хранилища (например, файлы сервера, видимые в списке разработчика). Объект показывался в списке, но скачать его, получить presigned-ссылку или сделать HEAD не удавалось.

Стало

Область доступа временных ключей учитывает фактическое семейство объекта (портал при этом остаётся привязан к контексту вызывающего), поэтому скачивание (?download), потоковая отдача (?inline) и HEAD для собственных объектов работают независимо от семейства. Проверка владения не изменилась — по чужому объекту по-прежнему приходит 404.

FIX-0724-4: GET /v1/workflows учитывает параметр limit

Было

GET /v1/workflows принимал limit, но молча его игнорировал — всегда возвращалась целая страница запущенных бизнес-процессов (до 50), сколько бы ни запросили.

Стало

limit уважается: в ответе не больше запрошенного числа записей. Значения больше 50 набираются постранично (потолок — 500); meta.total по-прежнему показывает общее число запущенных процессов.

FIX-0724-5: деплой: приложение в оборачивающей папке архива больше не падает с ENOENT package.json

Было

Деплой (POST /v1/infra/servers/:id/deploy) на standalone-сервер архива, в котором проект завёрнут в единственную папку верхнего уровня (например, myapp/package.json вместо package.json в корне), падал на шаге установки:

npm error enoent Could not read package.json ... open '/opt/app/package.json'

Загруженный архив оставался соседом распакованного содержимого, поэтому авто-выравнивание единственной оборачивающей папки не срабатывало (в корне оказывалось две записи — архив и папка), и package.json оставался вложенным.

Стало

Загруженный архив удаляется до шага выравнивания, поэтому единственная оборачивающая папка «схлопывается», package.json оказывается в корне деплоя, и установка проходит штатно.

Влияние на интеграторов

Действий не требуется. Плоские архивы (файлы в корне архива) работают как прежде; для гарантии можно паковать плоско: tar -czf build.tar.gz -C <папка_проекта> ..

FIX-0724-6: эндпоинты /v1/users* перестали возвращать 403 и 500 на ключах с доступом user

Было

На ключе только для чтения (READONLY) с доступом user вызов GET /v1/users/me возвращал 403 WRITE_BLOCKED_READONLY_KEY, хотя это ридовый эндпоинт. Отдельно GET /v1/users, GET /v1/users/:id, POST /v1/users/search и GET /v1/users/fields возвращали 500 INTERNAL_ERROR на порталах, где у одного из пользовательских полей (UF_*) пустое определение.

Стало

GET /v1/users/me работает на ключе только для чтения. Остальные /v1/users* возвращают данные и пропускают поле с пустым определением вместо падения.

Влияние на интеграторов

Действий не требуется — прежние вызовы продолжают работать, а ранее падавшие сценарии теперь отвечают корректно.

NEW-0724-7: доставка callback bizproc-активити и роботов на Black Hole-приложение

Регистрация bizproc-активити или робота с handler, указывающим на ваш деплой-сервер Black Hole, теперь приводит к надёжной доставке callback выполнения (с токеном события, блоком авторизации, кодом и свойствами) в приложение. Раньше такой callback мог не дойти: онлайн-события Битрикс24 не повторяются, а спящий или просыпающийся сервер терял вызов. Платформа перехватывает handler при регистрации, ставит вызов в устойчивую очередь и повторяет доставку с будильником сервера и откатами.

Затрагивает POST /v1/bizproc-activities и POST /v1/bizproc-robots (а также их изменение). Новый код ошибки SERVER_APP_MISMATCH (400): сервер Black Hole за указанным handler должен принадлежать тому же приложению, что регистрирует активити. Регистрация такого handler через /v1/batch не поддерживается — используйте одиночный запрос (BIZPROC_CALLBACK_BATCH_UNSUPPORTED). Кроме того, POST /v1/bizproc-robots теперь заранее требует code, name и handler — при их отсутствии возвращается 400 MISSING_REQUIRED_FIELDS вместо сырой ошибки Битрикс24 (как уже было у активити).

Возможность раскатывается постепенно и включается по аккаунтам: до включения на вашем аккаунте регистрация проходит как прежде, без управляемой доставки. После включения меняется поведение batch-регистрации: попытка зарегистрировать BH-handler через /v1/batch начинает отклоняться (BIZPROC_CALLBACK_BATCH_UNSUPPORTED) — переведите такие регистрации на одиночный POST /v1/bizproc-activities или /v1/bizproc-robots.

2026-07-23

NEW-0723-1: библиотека чертежей приложений — ТЗ по API-ключу

Готовые технические задания популярных приложений теперь доступны по ключу: GET /v1/app/blueprints/:slug?locale=ru|en возвращает сырой markdown ТЗ (Content-Type: text/markdown). Эндпоинт требует Authorization: Bearer <ключ>; неизвестный или скрытый чертёж — 404 BLUEPRINT_NOT_FOUND. Ссылку на ТЗ несёт копируемый «Промт для AI» при создании ключа — AI-агент скачивает ТЗ тем же ключом. Прежний анонимный путь /api/public/blueprints/:slug.md удалён.

NEW-0723-2: исходники удалённого сервера: доступ, уборка и честный ответ на вычищенные байты

Исходники переживают сервер — это давняя гарантия платформы, но добраться до них через API было нельзя: весь набор /v1/infra/servers/:id/sources* отвечал 404 на удалённый сервер, поэтому владелец не мог ни посмотреть свои версии, ни снять с них тег, ни удалить. Для версии с тегом published или manual это был тупик: снятие тега — единственный разрешённый способ обойти 409 PROTECTED_BY_TAG, а именно оно и было недоступно.

Теперь на удалённом сервере работают чтение и уборка: список версий, метаданные версии, скачивание, tag, PATCH, DELETE и cleanup. Сохранение новых версий (POST /sources) по-прежнему отвечает 404 — мёртвый сервер новых депозитов не принимает.

Чтобы удалённый сервер вообще можно было найти, GET /v1/infra/servers принимает ?includeDeleted=true. По умолчанию выдача не меняется. В каждой строке появилось поле deletedAt (null у живых серверов).

Отдельно: версия, чьи байты уже вычищены из хранилища, теперь отвечает 410 с кодом SOURCE_VERSION_BYTES_PURGED вместо 404. Разница существенная — 404 утверждал, что версии нет, тогда как запись о ней жива, а восстановление другое: перезалить архив, а не искать его в другом месте. Код приходит на скачивании и на деплое по {"source": {"versionId": "vN"}}.

Затронутые эндпоинты: GET /v1/infra/servers, POST /v1/infra/servers/:id/deploy, GET /v1/infra/servers/:id/sources, GET /v1/infra/servers/:id/sources/:versionId/download — контракт исходников описан на странице Хранилище исходников

FIX-0723-3: деплой galaxy-приложения: обрыв туннеля при сборке больше не маскируется под «host unreachable»

Было

Если во время сборки на galaxy-хосте моргал туннель и здорового контейнера этого деплоя в итоге не оказывалось (сборку прервало, контейнер не поднялся, канал exec был занят или приложение крашилось), деплой (POST /v1/infra/servers/:id/deploy) возвращал 502 GALAXY_HOST_UNREACHABLE с советом «повторите, когда хост переподключится». Хост при этом часто был доступен — совет вводил в заблуждение, а вызывающая сторона не видела реальную причину (например, что её приложение падает при старте).

Стало

Если после моргания туннеля хост доступен, но деплой не довёл приложение до рабочего состояния, деплой возвращает новый код 502 GALAXY_DEPLOY_INTERRUPTED — «хост доступен, но деплой прервался до старта приложения: отправьте тот же деплой повторно; если приложение раз за разом не стартует, сначала почините его (команду запуска, порт, зависимости, переменные окружения или лимит памяти)». Это по-прежнему повторяемая ошибка — слот цел, удалять и пересоздавать его не нужно. Код GALAXY_HOST_UNREACHABLE теперь остаётся только для действительно недоступного хоста (ни одна попытка проверки до него не достучалась). Если приложение действительно крашится в цикле, это надёжно выявляется уже на повторном деплое обычной проверкой живости (код GALAXY_APP_START_FAILED).

NEW-0723-4: подсказка в ошибке таймаута шага деплоя

Ошибка DEPLOY_TIMEOUT у POST /v1/infra/servers/:id/deploy теперь несёт структурированное поле error.hint (reason, recovery, recoveryAction), привязанное к зависшему шагу (error.step). Для команд пользователя (install, preStart) подсказка объясняет, что команда не завершилась в отведённое время, и советует сделать её неинтерактивной и завершающейся, а долгоживущие сервисы запускать из команды start или в фоне (docker compose up -d). Для шага установки рантайма (runtime — платформенный шаг, а не команда пользователя) и прочих служебных шагов подсказка указывает на возможный обрыв туннеля и на POST /v1/infra/servers/:id/repair. Поле аддитивное: прежние error.code, error.message и error.step не изменились, менять интеграцию не нужно; подсказка приходит и в JSON-режиме, и в SSE-событии error.

FIX-0723-5: ошибка шага деплоя показывает реальную причину, а не безобидное предупреждение

Было

При падении шага деплоя поле data.steps[].stderr (и, как следствие, error.message) могло нести только безобидное предупреждение из одного потока, теряя реальную причину сбоя.

Стало

Оба потока возвращаются вместе, с метками stderr: и stdout:; реальная причина больше не скрывается. Форма ответа и имя поля не изменились.

FIX-0723-6: сортировка списка по неуникальному полю больше не теряет записи на второй странице

Было

Запрос списка или поиск с сортировкой по неуникальному полю (например по датовому begindate) при выборке больше 50 записей мог молча вернуть меньше записей, чем есть: на границе страницы часть записей с одинаковым значением поля сортировки терялась. Ответ приходил с кодом 200, без признака неполноты. Затронуты сущности на основе CRM smart-process — сделки, лиды, контакты, компании, предложения, счета и элементы смарт-процессов /v1/items/{entityTypeId} — на путях GET /v1/{entity}, POST /v1/{entity}/search, в подвызовах POST /v1/batch и per-entity POST /v1/{entity}/batch. Тот же класс нестабильности затрагивал и числовую агрегацию (POST /v1/{entity}/aggregate с sum/avg/min/max/groupBy): выборка записей для агрегата шла без порядка, поэтому на объёмах больше 50 записей часть строк могла теряться и искажать результат.

Стало

К сортировке автоматически добавляется вторичный ключ по id — порядок становится строго определённым, и постраничная выборка не теряет и не дублирует записи независимо от поля сортировки. Пользовательская сортировка остаётся основным ключом; записи с одинаковым значением поля упорядочиваются по id по возрастанию. Менять запросы не нужно.

FIX-0723-7: переоткрытие тикета комментарием больше не оставляет штамп решения

Было

Комментарий команды через POST /v1/feedback/:id/comments, возвращающий тикет из RESOLVED или WITHDRAWN обратно в активный статус (NEW, REVIEWING, AWAITING_USER, NEEDS_REVIEW), не сбрасывал resolvedAt и resolvedBy. Они «зависали» от прошлого закрытия, и на чтении (GET /v1/feedback/:id, GET /v1/feedback) переоткрытый тикет выглядел одновременно активным и решённым.

Стало

Такой комментарий очищает resolvedAt и resolvedBy — на чтении активный тикет больше не несёт даты решения. resolution при этом не очищается: он отражает текст самого комментария. Комментарий, ставящий RESOLVED, по-прежнему проставляет штамп; переход в ARCHIVED и обычный переход между активными статусами штамп не трогают. Поведение согласовано с уже действовавшим сбросом на PATCH /v1/feedback/:id.

FIX-0723-8: фильтр списков statuses / departments / storages / currencies / products больше не игнорируется молча

Было

GET /v1/statuses, /v1/departments, /v1/storages, /v1/currencies, /v1/products работают через legacy-методы Битрикс24 (crm.status.list, department.get, disk.storage.getlist, crm.currency.list, crm.product.list), которые молча игнорируют нефильтруемые ключи и операторы. Неизвестное или неподдерживаемое поле фильтра — например filter[system] у статусов, filter[module] у хранилищ или filter[price] у товаров — а также операторы $gt / $contains / $ne возвращали 200 со всей таблицей. Клиент получал полный набор вместо ожидаемого подмножества — тихий отказ с неверными данными.

Стало

Для этих сущностей фильтр проверяется до вызова Битрикс24: разрешены только те поля, которые метод действительно фильтрует (проверено вживую). Остальные поля, операторы и пустые множества возвращают 400 UNSUPPORTED_FILTER с перечнем фильтруемых полей. Разрешённые поля по сущностям: statuses — id, entityId, statusId, name, sort, semantics, categoryId; departments — id, name, parentId, headId; storages — id, name, code, entityType, entityId; products — id, name, code, xmlId, active, sectionId, sort. crm.currency.list не фильтрует ничего — любой filter у /v1/currencies возвращает 400 с подсказкой отфильтровать на стороне клиента.

BC-0723-9: POST /v1/apps больше не возвращает поля prefix и suffix в ответе на создание

Поддержка старого формата до: 21.07.2026

Было

Ответ POST /v1/apps на создание приложения содержал два значения на vibe_app_ — короткий prefix и полный rawKey. Короткий prefix ошибочно принимали за ключ, и запрос с ним возвращал 401.

Стало

Ответ на создание содержит одно значение vibe_app_ — рабочий rawKey. Поля prefix и suffix остаются в GET /v1/apps и GET /v1/apps/:id для отображения маскированного ключа.

Что делать интеграторам

Используйте поле rawKey из ответа на создание как X-Api-Key. Маскированный префикс, если он нужен, берите из GET /v1/apps или GET /v1/apps/:id вместо ответа на создание.

FIX-0723-10: POST /v1/batch сохраняет телефон и почту при создании и обновлении лидов и контактов

Было

Через общий POST /v1/batch значения телефона и почты (мультиполя) при создании или обновлении лида либо контакта терялись. Вызов возвращал успех, но поле не сохранялось. Те же данные через одиночный POST /v1/leads или PATCH /v1/contacts/:id и через POST /v1/{entity}/batch сохранялись корректно.

Стало

Общий POST /v1/batch сериализует мультиполя так же, как одиночные вызовы. Телефон и почта сохраняются при создании и обновлении.

FIX-0723-11: Поиск и research больше не отвечают 402 INSUFFICIENT_BALANCE при положительном балансе

Было

POST /v1/search и POST /v1/research с платформенным движком (bitrix-search) на персональном ключе могли вернуть 402 INSUFFICIENT_BALANCE даже при достаточном балансе Vibe на счёте портала. Предварительная проверка баланса искала счёт по владельцу ключа, а счёт с недавних пор один на весь портал — и не находился.

Стало

Проверка и списание баланса всегда идут по счёту портала. При положительном балансе запрос выполняется и списывается корректно; 402 возвращается только при реальной нехватке средств. Форма запроса и ответа не изменилась.

FIX-0723-12: POST /v1/apps честнее сообщает о необходимости подписки Маркетплейса на cloud-shared пути

Было

При создании приложения через cloud-shared путь выпуска ключа (единая cloud↔box-модель, раскатка по кольцу порталов) на портале без активной подписки «BitrixGPT + Маркетплейс» POST /v1/apps возвращал непрозрачный 502 CONNECTOR_APP_INSTALL_FAILED без указания причины.

Стало

Отказ по подписке теперь классифицируется заранее: до обращения к коннектору POST /v1/apps проверяет авторитетное состояние подписки портала и, если оно отсутствует, сразу возвращает 403 с кодом B24_MARKET_SUBSCRIPTION_REQUIRED (или B24_MARKET_TRIAL_USED, если демо уже использован), понятным сообщением и ссылкой на оформление в error.details.upgradeUrl. Если состояние не авторитетно, тот же результат срабатывает при явном отказе коннектора по подписке. Прочие отказы cloud-shared выпуска (модуль не установлен, доступ запрещён портал-админом, прочие ошибки) классифицируются как прежде.

Влияние на интеграторов

Менять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал 502 CONNECTOR_APP_INSTALL_FAILED при создании приложения, стоит дополнительно ловить 403 B24_MARKET_SUBSCRIPTION_REQUIRED / B24_MARKET_TRIAL_USED и подсказывать пользователю оформить подписку на портале.

FIX-0723-13: запуск сервера не отвечает ошибкой, если машина уже работает

Было

POST /v1/infra/servers/:id/start для сервера в состоянии error вызывал запуск машины у облака и любой отказ отдавал как 502 PROVIDER_ERROR. Если машина к этому моменту уже работала — например, её подняло автоматическое восстановление после вытеснения, — облако отвечало отказом «машина уже в состоянии RUNNING», и вызов возвращал ошибку на операции, которая фактически удалась. Клиент видел 502 и не мог отличить это от настоящего сбоя.

Стало

Такой отказ распознаётся как идемпотентный успех: если машина уже работает или находится в переходном состоянии, вызов возвращает 200 и сервер переходит в provisioning, как при обычном запуске. Настоящие отказы — недостаточно прав, исчерпана квота, машина не найдена — по-прежнему возвращают 502 PROVIDER_ERROR.

Это тот же критерий идемпотентности, который уже применяли запуск агента и внутренний путь пробуждения сервера.

BC-0723-14: форма deployment.standalone.requiredFields.create в /v1/me стала объектом + документирует slug поля name

Поддержка старого формата до: 22.01.2027

Было

В ответе GET /v1/me per-kind под-блок deployment.standalone.requiredFields.create был массивом ["provider", "name", "plan", "region"] — формат name не указывался; у соседнего deployment.galaxyApp.requiredFields.create поле name говорило лишь «required». Имя с кириллицей или заглавными буквами при POST /v1/infra/servers отклонялось с 400 INVALID_REQUEST, но self-discovery об этом ограничении молчал.

Стало

deployment.standalone.requiredFields.create теперь объект (как соседний deployment.galaxyApp.requiredFields.create), и в обоих под-блоках name несёт формат: slug из строчных латинских букв по маске ^[a-z][a-z0-9-]*$, длина 2–63 символа. Человекочитаемую подпись кладите в необязательное поле displayName.

Что делать интеграторам

Плоский deployment.requiredFields["POST /v1/infra/servers"] (массив ["provider","name","plan","region"]) НЕ изменился — если вы читаете его, делать ничего не нужно, набор обязательных полей тот же. Если же ваш код парсил per-kind под-блок deployment.standalone.requiredFields.create как массив (.forEach / .includes("name") / .length / [0]), перейдите на чтение объекта: ключи — имена полей (provider/name/plan/region), значения — их описания.

NEW-0723-15: GET /v1/contacts/fields получил label и description для всех полей

В ответе GET /v1/contacts/fields теперь у всех 28 статических полей контакта есть человекочитаемые label и description. Раньше базовые поля (name, lastName, typeId и другие) приходили только с type и readonly, без описания смысла. Метки приходят по-русски. Семантику поля можно получить программно из ответа, без сверки со статической документацией. Кроме того, GET /v1/openapi.json публикует эти метки и описания (по-английски) как title и description в схемах Contact и ContactInput.

NEW-0723-16: smart-processes: поля relations и linkedUserFields во входной схеме

Поля relations (связи с сущностями CRM — parent/child, например привязка смарт-процесса к сделкам) и linkedUserFields теперь объявлены во входной схеме и видны в GET /v1/smart-processes/fields. Их можно передавать в POST /v1/smart-processes и PATCH /v1/smart-processes/:entityTypeId, чтобы связать смарт-процесс с другими сущностями CRM и вывести его в пользовательских полях. Фильтрация и сортировка по этим полям не поддерживаются — это вложенные структуры записи, а не поля выборки.

FIX-0723-17: bizproc-activities и bizproc-robots: тип documentType в /fields исправлен на array

Было

GET /v1/bizproc-activities/fields и GET /v1/bizproc-robots/fields показывали для documentType тип object, тогда как поле — массив из трёх элементов ([moduleId, entity, documentType]), как уже было объявлено у bizproc-templates.

Стало

Тип documentType в /fields теперь array у всех трёх сущностей — согласованно с реальным контрактом.

FIX-0723-18: PATCH /v1/bizproc-templates возвращает id числом

Было

PATCH /v1/bizproc-templates/:id возвращал data.id строкой ("1215"), тогда как POST возвращает число (1215). Клиент, сравнивавший id из ответа создания с ответом обновления, получал ложное несовпадение.

Стало

Ответ PATCH возвращает data.id числом (1215) — так же, как POST.

FIX-0723-19: userfields: тип label в схеме создания исправлен на string

Было

OpenAPI-схема POST /v1/userfields/{entity} объявляла label как object. B24 crm.<entity>.userfield.add принимает LABEL только строкой, поэтому SDK, сгенерированный по спеке (где label — объект), отправлял неверный тип и получал ошибку. OpenAPI-спека — публичный контракт: клиенты генерируют по ней SDK, и у тех, у кого тип был «объект», клиент был сломан.

Стало

label в схеме создания объявлен как string (подпись на языке портала по умолчанию). Мультиязычные подписи задаются через editFormLabel / listColumnLabel / listFilterLabel (PATCH после создания). Рантайм не менялся — правка только генерируемой спеки.

FIX-0723-20: sleep-now для galaxy-приложения теперь отвечает 400 — управляйте им со страницы «Галактики»

Было

POST /v1/infra/servers/:id/sleep-now для приложения, размещённого в галактике (GALAXY_APP), усыплял контейнер и отвечал 200. Это расходилось с сессионным маршрутом, который такое приложение уже отклонял, и могло рассинхронизировать состояние контейнера с хостом.

Стало

Тот же вызов для galaxy-приложения отвечает 400 с error.code = "GALAXY_APP_USE_GALAXY_ROUTE" и не меняет состояние: контейнер остаётся RUNNING. Управляйте жизненным циклом приложения через маршруты галактики. Для обычных (standalone) серверов поведение sleep-now не изменилось.

FIX-0723-21: комментарий задачи больше не отдаётся под чужой задачей

Было

На старых порталах GET /v1/tasks/:taskId/comments/:id возвращал 200 и сам комментарий, даже когда комментарий не принадлежал задаче :taskId: один и тот же комментарий отдавался под любой задачей, а поле taskId в ответе было простым эхом пути.

Стало

Перед выдачей комментарий сверяется с задачей из пути. Если комментарий не принадлежит :taskId, эндпоинт отвечает 404 с кодом NOT_FOUND и сообщением «Comment not found». Поле taskId в ответе теперь совпадает с реальной родительской задачей. Запросы комментария по его настоящей задаче работают как прежде.

FIX-0723-22: тип компании — единое поле typeId, не companyType

Было

Имя поля «тип компании» различалось на слоях. POST /v1/companies с полем companyType молча игнорировал тип — компания создавалась с типом по умолчанию; сохранить тип можно было только полем typeId. Чтение (GET, поиск) всегда возвращало тип в поле typeId. Фильтр же принимал companyType, но не typeId.

Стало

Тип компании — единое поле typeId во всех операциях: создание и изменение, чтение и поиск, фильтр (filter[typeId]) и группировка (groupBy: typeId). Значения прежние — CUSTOMER, SUPPLIER, COMPETITOR (список: GET /v1/statuses?filter[entityId]=COMPANY_TYPE). Поле теперь описано в GET /v1/companies/fields.

Влияние на интеграторов

Указывайте тип полем typeId. Чтение не меняется — тип всегда приходил в typeId. На создании и изменении companyType больше не описан (он и раньше не сохранял значение). В фильтре и группировке теперь работает typeId, а companyType возвращает 400 (UNKNOWN_FILTER_FIELD в фильтре, INVALID_AGGREGATION_FIELD в группировке) — замените имя на typeId.

2026-07-22

BC-0722-1: иконка сервера отдаётся как PNG

Поддержка старого формата до: 21.07.2026

Иконка сервера теперь отдаётся как PNG 256×256 (Content-Type: image/png) — платформа рендерит её из вашего загруженного SVG. Загрузка (POST /v1/infra/servers/:id/icon) по-прежнему принимает только SVG и теперь может вернуть 400 ICON_RASTERIZE_FAILED, если файл не удаётся растеризовать в PNG.

NEW-0722-2: пробуждение по расписанию (wake-schedules) доступно на всех порталах

CRUD для окон пробуждения — GET|POST /v1/infra/servers/:id/wake-schedules, PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId — теперь доступен на всех порталах для отдельных серверов (kind: "STANDALONE"): запрос больше не отвечает 403 WAKE_SCHEDULE_DISABLED. Платформа поднимает спящий сервер к заданному моменту по cron-выражению, дальше запуск задачи делает собственный cron внутри уже поднятой машины. Galaxy-приложения (kind: "GALAXY_APP") пока не участвуют в раскрытии и по-прежнему отвечают 403 WAKE_SCHEDULE_GALAXY_DISABLED. Ответ GET /v1/infra/servers/:id и список серверов теперь содержат аддитивные поля nextScheduledWakeAt (время ближайшего пробуждения, ISO 8601 или null) и wakeScheduleCapable — прежние поля не меняются.

NEW-0722-3: 409 SERVER_NOT_READY при деплое несёт признак повторяемости

POST /v1/infra/servers/:id/deploy в ответе 409 SERVER_NOT_READY, когда на сервере уже идёт восстановление подключения (запущенное параллельным деплоем или ремонтом), теперь дополнительно возвращает поля error.retryable: true и error.retryAfter (секунды) и заголовок Retry-After. Это машинный сигнал: повтор запроса имеет смысл — подождите указанный интервал и повторите деплой. Прежние клиенты не затронуты: код и текст ошибки прежние, поля добавлены аддитивно.

NEW-0722-4: Новый эндпоинт активации триала Маркета для портала ключа

POST /v1/portals/:id/activate-market-trial активирует одноразовый триал Битрикс24 Маркета для портала, которому принадлежит вызывающий ключ. Раньше активация была доступна только из кабинета — у ключей API программного пути не было.

:id обязан совпадать с порталом ключа, иначе 403 PORTAL_MISMATCH. Тело не требуется. Лимит — 3 запроса в час на портал.

Ответ при успехе: { "success": true, "data": { "status": "activated", "trialEndsAt": "..." } } (или "status": "already_active", если триал/доступ уже есть). Ошибки: 403 WRITE_BLOCKED_READONLY_KEY (ключ только для чтения), 403 PURPOSE_KEY_FORBIDDEN (служебный ключ специального назначения не может активировать триал), 404 NOT_FOUND (портал не найден), 409 ALREADY_ACTIVATED (триал уже активирован), 409 TRIAL_ACTIVATION_UNAVAILABLE (триал недоступен для этого портала), 503 TRIAL_ACTIVATION_RETRY (временная ошибка, повторите позже).

FIX-0722-5: портал с активной подпиской Маркетплейса больше не получает ложный отказ `MARKETPLACE_REQUIRED`

Было

На портале с несколькими держателями ключа разработчика состояние подписки Маркетплейса могло не прочитаться: если первый опрошенный ключ отвечал данными о портале, но без блока подписки (урезанный набор прав), опрос на этом останавливался и до ключа, способного прочитать подписку, дело не доходило. Подписка оставалась неизвестной, и портал с реально оплаченной подпиской получал 402 MARKETPLACE_REQUIRED на POST /v1/infra/servers, а GET /v1/me отдавал capabilities.servers.create.available: false. Повторный вызов GET /v1/me?refresh=tariff отказ не снимал.

Стало

Опрос продолжается до ключа, который вернёт блок подписки. Порталу с оплаченной подпиской создание сервера разрешается, capabilities.servers.create.available становится true. Формат ответов не изменился, действий со стороны клиента не требуется. Состояние обновляется при очередном обновлении тарифа портала (не позже часа) либо сразу по GET /v1/me?refresh=tariff.

Влияние на интеграторов

Действий не требуется. Клиент, который ветвился на 402 при создании сервера, продолжает работать: на затронутых порталах этот ответ просто перестаёт приходить.

FIX-0722-6: поле `region` в ответах об инфраструктуре всегда возвращает идентификатор региона

Было

У сервера, созданного и запущенного на международном сегменте, GET /v1/infra/servers и GET /v1/infra/servers/:id могли вернуть в поле region внутренний идентификатор зоны размещения, а не идентификатор региона из каталога. Значение не совпадало ни с одним id из GET /v1/infra/providers/:providerId/regions, поэтому сопоставить сервер с регионом каталога по этому полю было нельзя, и оно раскрывало детали внутреннего размещения.

Стало

region всегда содержит нейтральный идентификатор региона из того же пространства имён, что и каталог, — например bc-eu-central. Значение совпадает с id соответствующей записи GET /v1/infra/providers/:providerId/regions, поэтому сервер сопоставляется с регионом каталога напрямую. Зона размещения — внутренняя деталь: при создании сервера указывается регион, конкретную зону внутри него платформа выбирает сама.

Что делать интеграторам

Ничего, если вы передаёте в region значение, взятое из каталога регионов, — этот сценарий не менялся. Если ваш код сравнивал region из ответа о сервере со строкой, полученной ранее из ответа о сервере, а не из каталога, — сравнение теперь надёжно: оба конца в одном пространстве имён. На вход по-прежнему принимаются и идентификаторы из прежнего каталога.

2026-07-21

FIX-0721-1: имя и описание встроенного поискового движка, имя облачного провайдера

Публичное имя платформенного поискового движка приведено к продуктовому: GET /v1/search/providers и /v1/me возвращают Bitrix24 AI Search в поле name (латинские локали). Идентификатор провайдера bitrix-search не менялся — клиентам ничего делать не нужно.

Из описания того же провайдера снято упоминание цитирования источников: возможность зависит от движка, привязанного к инстансу, и объявляется машинно в capabilities.output.citations того же ответа.

GET /v1/infra/providers на международном сегменте возвращает в поле name бренд Bitrix24 Cloud вместо Bitrix Cloud. Идентификатор провайдера bitrix-cloud не менялся.

Было

"name": "Bitrix AI Search" · "description": "Platform AI search with agentic mode and source citations" · "name": "Bitrix Cloud"

Стало

"name": "Bitrix24 AI Search" · "description": "Platform AI search with agentic mode" · "name": "Bitrix24 Cloud"

FIX-0721-2: создание шаблона бизнес-процесса теперь принимает файл шаблона

Было

POST /v1/bizproc-templates отвечал 422 Incorrect field TEMPLATE_DATA! при любом теле — создать шаблон было невозможно: поле с содержимым файла .bpt не входило в схему сущности и до Битрикс24 не доходило.

Стало

Поле templateData (файл .bpt в виде массива [имя файла, содержимое в base64]) принимается и передаётся в Битрикс24, шаблон создаётся. Поле обязательно на создание: без него запрос отклоняется с 400 MISSING_REQUIRED_FIELDS до обращения к Битрикс24 (раньше приходила сырая ошибка Incorrect field TEMPLATE_DATA!). Оно доступно на запись и в описании полей GET /v1/bizproc-templates/fields, но не возвращается при чтении.

NEW-0721-3: Сводный реестр исходников — GET /v1/me/sources

Новый эндпоинт GET /v1/me/sources — программный аналог кабинетной страницы «Исходники приложений». Возвращает снапшоты исходников по всем серверам и приложениям, которыми владеет ключ (а ключ администратора аккаунта — по всему аккаунту), с пагинацией (page/limit/search) и стандартным конвертом { success, data, total, page, limit }. Каждая строка несёт указатель для перехода вглубь — listEndpoint и latestDownloadEndpoint — плюс reachableViaApi и, для строк-серверов, blackholeStatus. В отличие от GET /v1/infra/servers, который ограничен серверами вызывающего ключа, этот реестр охватывает и сервер на другом ключе того же владельца.

Ответы server-scoped эндпоинтов исходников (POST /v1/infra/servers/:id/sources и соседние list/download/tag/cleanup) теперь описаны в документации; поле versions[].serverContext ({ serverId, serverName, serverDisplayName, linkedApp }) закреплено в контракте.

NEW-0721-4: необязательное поле error.b24Code в ответах 422 BITRIX_ERROR

В ответах 422 BITRIX_ERROR появилось необязательное поле error.b24Code — сырой код ошибки Битрикс24 для программной обработки (например, PERIOD_REQUIRED, INVALID_FILTER). Изменение аддитивно: прежние клиенты, разбирающие только error.code и error.message, не затронуты.

NEW-0721-5: Статистика Открытых линий — 6 методов дашборда

Новый раздел API для дашбордов контакт-центра: POST /v1/openlines/stats (агрегаты за период), GET /v1/openlines/operators (real-time нагрузка операторов), POST /v1/openlines/sessions/search, POST /v1/openlines/sessions/stats, POST /v1/openlines/sessions/transfers, POST /v1/openlines/ratings/search. Требуется скоуп imopenlines и право тарифа report_open_lines (иначе 403 B24_TARIFF_RESTRICTION).

В процессе раскатки — методы выходят в обновлении Битрикс24 imopenlines 26.700.0 и доступны не на всех порталах. Пока обновление не приехало на портал, методы возвращают 422 METHOD_NOT_YET_AVAILABLE с целевой версией в ответе — это признак раскатки, а не ошибка интеграции.

FIX-0721-6: тарифный отказ Битрикс24 отдаётся как 403 B24_TARIFF_RESTRICTION на всех эндпоинтах

Было

Отказ Битрикс24 по тарифному праву приходил как 422 BITRIX_ERROR с непрозрачным сообщением — отличить его от прочих ошибок Битрикс24 программно было нельзя.

Стало

Такой отказ отдаётся как 403 с кодом B24_TARIFF_RESTRICTION. Правило общее для всего V1 API, а не только для Открытых линий: любой эндпоинт, вызвавший метод Битрикс24, недоступный на тарифе портала, теперь отвечает этим кодом.

Влияние на интеграторов

Клиенты с обычной обработкой ошибок продолжают работать без изменений — отказ остаётся ошибкой, просто становится точнее. Если вы отдельно ветвились на 422 для тарифных отказов, перенесите ветку на 403 и error.code === 'B24_TARIFF_RESTRICTION'. Этот код не означает сбой интеграции: возможность не входит в тариф портала Битрикс24, и повторять запрос бессмысленно до смены тарифа.

FIX-0721-7: Версионные рантаймы деплоя ставят заявленную версию на Ubuntu 24.04

Было

Рантайм node20 устанавливал Node.js 18 (в репозиториях Ubuntu 24.04 нет Node 20), а python311 и RAG-рантаймы (node20-rag, python311-rag) падали на шаге установки — нужных пакетов в дистрибутиве нет. В ответе GET /v1/infra/runtimes поле packages показывало postgresql-14, хотя ставилась PostgreSQL 16.

Стало

node20 ставит Node.js 20 (с проверкой мажорной версии), python311 — Python 3.11, RAG-рантаймы — PostgreSQL 16 с расширением pgvector в базе приложения. Поле packages отражает фактическую версию (postgresql-16). Публичные идентификаторы рантаймов и формат запроса /deploy не изменились.

FIX-0721-8: статус ремонта сервера корректен при опросе

Было

Опрос GET /v1/infra/servers/:id/repair-status при многоузловом бэкенде мог кратковременно вернуть idle, даже когда ремонт ещё шёл, — если запрос попадал на другой обслуживающий узел, чем тот, что выполняет ремонт. Клиент, опрашивающий статус в цикле, мог из-за этого ошибочно решить, что ремонт завершился, ещё до его старта.

Стало

Эндпоинт надёжно возвращает реальный прогресс ремонта (running / done / failed) независимо от того, на какой узел попал опрос.

Влияние на интеграторов

Форма ответа не изменилась, действий со стороны клиента не требуется.

FIX-0721-9: приложение на standalone-сервере больше не работает с правами администратора

Было

Приложение, задеплоенное на standalone Black Hole сервер, запускалось с правами администратора и без изоляции. Любая уязвимость в самом приложении (например, выполнение произвольного кода) сразу давала полный контроль над всей виртуальной машиной: доступ к ключам подключения сервера, к служебным настройкам и к системным файлам.

Стало

Приложение работает под выделенной непривилегированной учётной записью и видит только свой каталог (extractTo, по умолчанию /opt/app), которым владеет. Системные каталоги защищены от записи, повышение привилегий запрещено. Порт ниже 1024 по-прежнему работает — платформа выдаёт для него отдельное разрешение.

Команды деплоя (install, preStart) и /exec по-прежнему выполняются с правами администратора — здесь ничего не изменилось, sudo не нужен.

Ничего менять не требуется: если приложению для запуска действительно нужны права администратора, деплой автоматически возвращает прежний режим, завершается успешно и добавляет предупреждение с причиной. Чтобы сразу пропустить эту попытку (актуально для nginx в качестве команды запуска, MySQL через системный сокет и запуска через Docker), передайте "hardening": "off" в теле деплоя.

В data.steps[] появились два новых значения step: service_user — передача каталога деплоя непривилегированной учётной записи, и hardening — предупреждение о возврате приложения к прежнему режиму. Клиентам, которые разбирают шаги по имени, стоит их учесть.

BC-0721-10: битый кандидат в image_url больше не роняет весь запрос

Поддержка старого формата до: 21.01.2027

Было

Массив content мог нести несколько частей image_url. Если хотя бы одна из них содержала не изображение — например HTML-страницу с ошибкой, закодированную в Base64 и объявленную как image/png, — платформа передавала её модели как есть. Модель не могла её декодировать, и весь запрос завершался ошибкой 502 с кодом ai_provider_unavailable, даже когда остальные изображения были корректны.

Отдельно: части с неподдерживаемым MIME-типом, повреждённым Base64 или превышением лимита 20 МиБ отклонялись ответом 400 invalid_image_payload — тоже на весь запрос целиком.

Стало

Перед отправкой модели платформа проверяет фактическое содержимое каждой части image_url по сигнатуре байтов, а не по заявленному MIME-типу. Часть, содержимое которой является веб-ответом (HTML, XML, JSON, ответ HTTP) либо не декодируется, заменяется на своей позиции текстовой заглушкой [image unavailable: <причина>]. Остальные изображения обрабатываются как обычно, запрос завершается успешно.

Позиции частей сохраняются: длина массива content не меняется, поэтому нумерация кандидатов на стороне клиента остаётся верной.

Отклонённые части видны в ответе — в поле warnings появляется запись с кодом IMAGE_CONTENT_REJECTED, а в заголовках X-Image-Parts-Rejected с их количеством. Для потоковых ответов заголовок приходит вместе с началом потока.

Ответом 400 теперь завершаются только структурные ошибки: отсутствующее поле url, строка, не являющаяся ни URL, ни data-URI, неподдерживаемая схема и http:// в production.

Что делать интеграторам

Если ваш код полагался на 400 invalid_image_payload как на признак того, что изображение не принято, — читайте вместо этого warnings или заголовок X-Image-Parts-Rejected. Запрос теперь завершается успешно, и молчаливой потери изображения не происходит: факт замены всегда отражён в ответе.

Дополнительно изменилось поведение при отказе модели. Раньше любой не-2xx ответ провайдера приходил как 502, теперь статус отражает причину:

  • ответ модели 400 или 422 → 400 с кодом ai_provider_rejected. Повторять такой запрос без изменений бесполезно;
  • превышение лимита на стороне модели (429) → 429 с заголовком Retry-After. Повторить нужно, выдержав указанную задержку. В потоковом ответе заголовок невозможен, поэтому задержка приходит полем retryAfter в кадре ошибки;
  • таймаут на стороне модели (408) → 503 с кодом ai_provider_timeout.

Ответы 401, 403 и 5xx по-прежнему приходят как 502. Изменение затрагивает POST /v1/chat/completions и POST /v1/embeddings.

Ошибки со стороны модели теперь дополнительно несут поле providerStatusCode — исходный HTTP-статус ответа модели. По нему 429 от модели отличается от 429 собственного лимита платформы (у последнего поля нет): код rate_limit_exceeded у обоих одинаковый, чтобы SDK ретраили единообразно, а различитель — это новое необязательное поле.

Также в data-URI теперь допускаются параметры между типом и ;base64 — data:image/jpeg;name=photo.jpg;base64,... больше не отклоняется.

2026-07-20

NEW-0720-1: Ответы приложений теперь возвращают статус публикации

Ответы раздела приложений — список, данные приложения, создание, публикация и снятие с публикации — теперь несут два новых поля: catalogStatus (PRIVATE / PUBLISHED / UNPUBLISHED) и publishedAt (дата публикации, ISO 8601, или null). Раньше прочитать статус публикации через V1 было нельзя — приходилось угадывать по массиву placements, что ненадёжно: снятое с публикации приложение может сохранить ранее привязанные коды, а PRIVATE и UNPUBLISHED по placements неразличимы. Поля добавлены аддитивно — прежние вызовы работают без изменений.

FIX-0720-2: переименование чата больше не отвечает ложным успехом тому, кто не участник

Было

PATCH /v1/chats/:chatId, вызванный от имени администратора портала, который не состоит в чате, возвращал { "success": true, "data": true }, хотя название чата не менялось: Битрикс24 отвечал на такой вызов ложным успехом. Отличить его от настоящего переименования по ответу было нельзя, и интегратор считал операцию выполненной. Вызывающий при этом не мог даже прочитать этот чат.

Стало

Перед переименованием проверяется, что вызывающий состоит в чате. Если нет — ответ 404 CHAT_NOT_FOUND_OR_NO_ACCESS, тот же, что и для любого другого не-участника, и попытки переименования не происходит. Ложный успех больше не выдаётся. Переименование участником, у которого есть права, работает без изменений.

NEW-0720-3: стриминг chat-completions обрывает зависший ответ апстрима явной ошибкой

Если при стриминге (POST /v1/chat/completions с stream: true) апстрим-модель отдала заголовки, но затем замолчала в середине ответа и не присылает новых данных дольше окна ожидания, прокси теперь прерывает вызов и присылает в поток терминальный кадр ошибки перед data: [DONE]:

data: {"error":{"code":"stream_idle_timeout","type":"server_error","retryable":true,"retryAfter":<секунды>}}

Раньше такой вызов висел бесконечно (агент оставался в состоянии «receiving stream response»). Ошибка повторяемая — прочитайте поток до конца ([DONE]) и повторите запрос с учётом retryAfter. Обычные (не зависшие) стримы и «думающие» модели, которые непрерывно присылают токены рассуждений, не затронуты.

NEW-0720-4: tasks: новое поле timeSpentInLogs

Поле timeSpentInLogs (фактически затраченное время в секундах, сумма записей учёта времени) теперь задекларировано в схеме задач — доступно в select, фильтре и сортировке GET /v1/tasks и POST /v1/tasks/search, и присутствует в GET /v1/tasks/fields.

Раньше поле возвращалось, только если в select были указаны ОБА написания сразу (timeSpentInLogs и TIME_SPENT_IN_LOGS); теперь достаточно любого одного. Поле только для чтения — фиксируется через эндпоинт учёта времени, не через обновление задачи.

FIX-0720-5: GET /v1/files/:id?include=folder теперь возвращает папку

Было

GET /v1/files/:id с ?include=folder отвечал 200, но без блока _included, хотя GET /v1/files/fields объявлял folder как includable — включение молча не срабатывало.

Стало

?include=folder прикрепляет папку в _included.folder, как и обещает /fields.

Влияние на интеграторов

Действий не требуется. Клиенты, читавшие _included.folder, теперь получают объект вместо его отсутствия.

FIX-0720-6: /v1/me: блок infra стал точным по лимиту серверов, идентификатору провайдера и разбивке

Было

GET /v1/me в блоке infra возвращал limits.max: 3 независимо от реально применяемого лимита серверов на ключ; providers мог отдавать внутренний идентификатор провайдера (расходясь с GET /v1/infra/providers) и с возможными дублями; limits.breakdown относил виртуальные машины управляемых ботов к direct вместо bots.

Стало

limits.max отражает реально применяемый лимит серверов на ключ; providers отдаёт публичный идентификатор провайдера, согласованный с GET /v1/infra/providers, без дублей; limits.breakdown учитывает машины ботов в bots. Интеграцию менять не нужно — значения просто стали корректными.

FIX-0720-7: deal-categories: неизвестные поля фильтра отклоняются, сортировка по id учитывает направление

Было

GET /v1/deal-categories с фильтром по неизвестному полю молча возвращал ВСЮ таблицу воронок с 200 — Bitrix24 игнорирует неизвестные ключи фильтра legacy-метода и отдаёт весь список. А сортировка ?sort=id&order=desc игнорировала направление и всегда возвращала один и тот же порядок.

Стало

Фильтр по неизвестному или неподдерживаемому полю (а также операторные префиксы >/>=/!/… и операторные объекты) отклоняется до вызова Bitrix24 с 400 UNSUPPORTED_FILTER; в сообщении перечислены фильтруемые поля (id, name, sort). Фильтр точным совпадением и $in по этим полям работают как прежде. Сортировка ?sort=id теперь корректно учитывает asc/desc.

FIX-0720-8: openline-configs: нераспознанные поля в теле записи больше не пропадают молча

Было

POST /v1/openline-configs (и PATCH) молча игнорировал нераспознанные поля тела — Bitrix24 отбрасывает неизвестные ключи метода imopenlines.config.*. Тело из одних только неизвестных полей при этом создавало конфигурацию со значениями по умолчанию и отвечало 200.

Стало

Если в теле НЕТ ни одного известного поля — запрос отклоняется с 400 VALIDATION_ERROR до вызова Bitrix24, и в сообщении перечислены нераспознанные поля. Если известное поле есть, но часть полей нераспознана — запись выполняется как прежде, а в ответе возвращается meta.warnings с перечнем проигнорированных полей (раньше они исчезали без следа). Поля только для чтения (id, queue, dateCreate и другие) в теле записи теперь отклоняются с 400 READONLY_FIELD — раньше они проходили как «известные» и могли привести к созданию конфигурации со значениями по умолчанию.

FIX-0720-9: GET /v1/{entity}/fields сигналит о неполных метаданных при сбое Bitrix24

Было

Если запрос динамических полей к Bitrix24 (*.fields) падал (rate-limit, QUERY_LIMIT_EXCEEDED, таймаут очереди), эндпоинт молча отдавал 200 только со статическими полями схемы — у многих из них нет человекочитаемой метки (label). Ответ выглядел полным, клиент не мог отличить его от корректного и строил недетерминированные маппинги полей.

Стало

При сбое запроса полей ответ по-прежнему 200 со статическими полями, но несёт meta.warnings: [{ "code": "fields_partial", "message": "..." }] — клиент видит, что набор полей неполный, и может повторить запрос. Формат предупреждения — объект { code, message }, тот же канал и та же форма, что у meta.warnings в list/search, поэтому один разбор по warning.code работает на всех эндпоинтах. Сбой теперь также логируется на стороне Vibe.

2026-07-19

BC-0719-1: Cowork/Code: тарифная линейка переименована — Free / Pro / Max / Ultra, цена Ultra снижена

Поддержка старого формата до: 18.07.2026

Было

Поле tier в ответах GET /v1/cowork/state и GET /v1/cowork/me (а также recommendation.upgrade.nextTier и каталог tiers[]) принимало значения FREE, START, PRO, MAX. Тариф MAX (×20) стоил 40 000 Ꝟ/мес.

Стало

Линейка переименована со сдвигом: START → PRO, PRO → MAX, MAX → ULTRA; набор значений теперь FREE, PRO, MAX, ULTRA. Множители тарифов не изменились: PRO ×1 (база), MAX ×5, ULTRA ×20; у FREE — 5% от PRO. Цена ULTRA (бывший MAX, ×20 объёма) снижена с 40 000 до 20 000 Ꝟ/мес. Объёмы окон откалиброваны по фактическому использованию: 5-часовое окно выросло вдвое на всех тарифах, месячные объёмы уменьшены; в течение текущего оплаченного периода лимиты подписки не меняются — новые значения применяются со следующего продления. Переименование применяется атомарно в момент релиза: значение START больше не возвращается, добавилось значение ULTRA. Клиенты, ветвящиеся по строковым значениям tier / nextTier, должны обновить маппинг с учётом сдвига смысла (PRO теперь база, MAX — средний тариф); клиенты, отображающие серверные multiplier / feeVibes как есть, продолжают работать без изменений.

FIX-0719-2: цены тарифов Cowork/Code на международной версии приведены к долларовой шкале

Было

На международной версии платформы каталог тарифов в GET /v1/cowork/state отдавал цены в масштабе российской версии: feeVibes 2000 / 10000 / 20000 за Pro / Max / Ultra. При курсе 1 Vibe credit = 1 доллар это читалось как 2000–20000 долларов в месяц.

Стало

Каталог тарифов на международной версии отдаёт долларовую сетку: Pro — feeVibes: 20, Max — 100, Ultra — 200 в месяц; квоты трёх окон масштабированы согласованно, поэтому ёмкость тарифа в запросах не изменилась. Российская версия не затронута.

Влияние на интеграторов

Если ваш клиент читает tiers[].feeVibes из GET /v1/cowork/state на международной версии — отображаемые значения уменьшились в 100 раз и теперь совпадают с реально списываемой ценой активации. Ничего менять в коде не нужно.

2026-07-18

FIX-0718-1: деплой переиспользует сервер приложения вместо создания дубликата

Было

POST /v1/infra/servers c source всегда создавал новый сервер, даже если у приложения, которому принадлежит ключ, уже был сервер — на счёт заводился второй, простаивающий. А POST /v1/infra/servers/:id/deploy отвечал WRONG_KEY, если ключ вызова отличался от того, которым сервер был создан (например, у приложения есть личный ключ и ключ авторизации).

Стало

Если ключ вызова принадлежит приложению, у которого уже есть живой сервер, POST /v1/infra/servers возвращает этот сервер с полем reused: true вместо создания нового. Если в том же запросе передан source, а переиспользуемый сервер — это приложение галактики (kind: "GALAXY_APP"), у которого ещё нет работающего контейнера (ни разу не деплоилось или прошлый деплой упал), исходный код сразу разворачивается в его собственный сервер (ответ содержит reused: true и deploying: true, а статус сервера на время сборки — provisioning) — так же, как при обычном create с source: опрашивайте GET /v1/infra/servers/:id до статуса running, второй вызов деплоя не нужен. Если же приложение галактики уже работает, ответ содержит reused: true и next: "deploy" — разверните исходный код отдельным вызовом POST /v1/infra/servers/:id/deploy (так живой контейнер не затрагивается на время сборки). Для обычного сервера или запроса без source ответ содержит reused: true и next: "deploy" — разверните исходный код отдельным вызовом POST /v1/infra/servers/:id/deploy. POST /v1/infra/servers/:id/deploy теперь принимает любой ключ этого же приложения и деплоит в его сервер.

Влияние на интеграторов

Ничего менять не нужно. Дубликаты серверов больше не создаются. Раньше one-shot create с source на уже существующий сервер приложения возвращал next: "deploy" и терял переданный source — теперь исходный код разворачивается сразу. И деплой, и чтение статуса (GET /v1/infra/servers/:id) сервера приложения работают под любым ключом этого приложения — независимо от того, каким из них вы вызываете API.

FIX-0718-2: DELETE /lock снимает зависший лок и на удалённом сервере

Было

DELETE /v1/infra/servers/:id/lock возвращал 404 NOT_FOUND, если сервер был удалён — даже когда лок операции остался в памяти платформы и продолжал держать сервер. Из-за этого сценарий «предыдущий сервер удалён, лок завис, следующий деплой падает с EXEC_BUSY» не имел выхода: снять такой лок через API было нельзя.

Стало

DELETE /lock снимает зависший лок и на удалённом сервере — при условии, что он всё ещё принадлежит вашему API-ключу (владение остаётся единственной проверкой; лок не хранит данных и не держит облачных ресурсов). Успешный вызов возвращает 200 с data.released: true. 404 NOT_FOUND теперь означает только «сервер не существует или принадлежит другому ключу».

NEW-0718-3: коды ошибок коннектора при установке приложения теперь возможны и на облачных порталах (поэтапная раскатка)

POST /v1/apps на облачном портале теперь тоже может устанавливать приложение через модуль vibecodeconnector и, соответственно, возвращать те же коды ошибок коннектора, что раньше были возможны только на коробочных порталах: 403 CONNECTOR_APP_INSTALL_FORBIDDEN (администратор портала Битрикс24 запретил пользователю установку приложений), 409 CONNECTOR_MODULE_NOT_INSTALLED (модуль vibecodeconnector не установлен на портале) и 502 CONNECTOR_APP_INSTALL_FAILED (прочие сбои установки). Изменение аддитивное: ответ при успешной установке не изменился, а раскатка идёт поэтапно — на большинстве облачных порталов путь установки пока прежний. Клиентам, которые уже обрабатывают эти коды на коробочных порталах, менять ничего не нужно; клиентам, которые их не обрабатывали, стоит добавить обработку.

2026-07-17

FIX-0717-1: деплой на спящий galaxy-хост отвечает раньше клиентских таймаутов

Было

POST /v1/infra/servers/:id/deploy на спящем общем хосте удерживал соединение открытым до ~6,5 минут, пока хост просыпался. HTTP-клиенты с типовым таймаутом ожидания заголовков (~300 секунд — дефолт Node fetch) обрывали соединение раньше ответа платформы: деплой выглядел как сетевой сбой fetch failed без кода ошибки и рекомендаций. Неудачное пробуждение к тому же возвращало хост в сон, и каждый повтор начинал загрузку хоста заново.

Стало

Платформа будит хост в фоне и ждёт подключения не дольше ~4 минут. Хост успел подключиться — деплой выполняется одним вызовом, как раньше. Не успел — сразу возвращается повторяемый 502 GALAXY_HOST_UNREACHABLE с hint (повторить тот же запрос через 1–2 минуты, слот не удалять), а хост продолжает просыпаться в фоне — повторный деплой подхватывает уже идущую загрузку вместо новой. В deployment.galaxyApp._rules и deployment.standalone._rules (GET /v1/me) добавлена рекомендация держать таймаут HTTP-клиента не ниже 690 секунд — строго выше платформенного окна в 660 секунд.

Влияние на интеграторов

Изменений в запросах не требуется. Если деплой на спящий galaxy-хост раньше завершался у вас сетевой ошибкой без ответа платформы — теперь придёт либо успех, либо 502 с инструкцией повторить.

NEW-0717-2: поиск сотрудников на сервере подсказывает причину пустого списка

Эндпоинт GET /v1/infra/servers/:id/b24-users теперь возвращает дополнительное поле hint, когда список пуст из-за отсутствия доступа к Битрикс24 — приложение ещё не авторизовано на портале или ключ отозван. Прежние вызовы работают без изменений: поле аддитивное и отсутствует при успешной выдаче.

NEW-0717-3: справочники полей каталога сообщают о nullable-полях

Справочники GET /v1/catalog-prices/fields, GET /v1/catalog-sections/fields и GET /v1/catalog-products/fields теперь добавляют ключ "nullable": true полям, которые могут вернуть null: у цен это quantityFrom, quantityTo и extraId, у разделов — iblockSectionId, xmlId, code и description, у товаров — iblockSectionId, code, weight, purchasingPrice, purchasingCurrency, quantity и quantityReserved. Тот же признак приходит в data.entities[].fieldsDetailed ответа GET /v1/guide, который читается ключом OAuth-приложения без сессии, а в машинной спеке GET /v1/openapi.json такие поля описаны union-типом вида ["number", "null"]. Клиент, который строит типизированную модель по справочнику, теперь получает верную nullability и не падает на первом же null. Набор полей, их типы и значения в ответах не изменились.

NEW-0717-4: EXEC_BUSY подсказывает, через сколько повторить

Ответ 409 EXEC_BUSY (другая операция держит блокировку сервера) на POST /v1/infra/servers/:id/exec и POST /v1/infra/servers/:id/deploy теперь несёт retry-подсказку: HTTP-заголовок ответа Retry-After (в секундах) и два новых поля в теле ошибки — retryable: true и retryAfter (в секундах). Значение retryAfter — короткий интервал опроса (повторяйте с ним, пока не пройдёт), а не полное время до автоматического снятия блокировки; полный верхний предел по-прежнему в error.hint.autoExpiresInSeconds. Изменение аддитивное: код, поле message и hint не меняются, клиенты, читающие error.code, продолжают работать без правок.

FIX-0717-5: деплой galaxy-приложения со слишком большим архивом отдаёт 413, а не «хост недоступен»

Было

POST /v1/infra/servers/:id/deploy с source.content, превышающим лимит загрузки, возвращал 502 GALAXY_HOST_UNREACHABLE — с текстом про недоступность хоста и советом «повторить, когда хост переподключится». Хост при этом был полностью доступен, а повтор того же архива давал тот же результат: интегратор оказывался в бесконечном цикле бесполезных попыток.

Стало

Тот же случай возвращает 413 GALAXY_UPLOAD_TOO_LARGE со структурированным error.hint. Причина детерминирована (архив слишком большой), а не транзиентна, поэтому повтор без изменений не поможет. hint подсказывает уменьшить архив — исключить node_modules, .git и артефакты сборки (зависимости платформа ставит на хосте). У galaxy-приложения источник — только встроенный source.content (source.url отклоняется с 400 GALAXY_DEPLOY_CONTENT_ONLY), поэтому уменьшить архив — единственный способ восстановления. Отдельный смежный случай: тело запроса, превышающее жёсткий внешний лимит платформы (500 МБ на base64-тело ≈ ~375 МБ бинарного архива), теперь отклоняется на краю кодированным 413 PAYLOAD_TOO_LARGE (на Vibe-REST /v1/-маршрутах — deploy/upload/create; OpenAI-совместимые AI-роуты отдают ошибку в своём конверте) вместо сырого HTML — раньше клиент получал недекодируемый ответ.

Влияние на интеграторов

Ничего менять не нужно: успешные деплои не затронуты. Клиенты, которые ветвились на коде ошибки для этого сбоя, теперь видят честный 413 GALAXY_UPLOAD_TOO_LARGE вместо вводящего в заблуждение 502 GALAXY_HOST_UNREACHABLE — последний остаётся для настоящего обрыва туннеля во время сборки.

NEW-0717-6: displayName и description в деплое задают карточку в каталоге Битрикс24

POST /v1/infra/servers/:id/deploy принял два необязательных поля тела — displayName и description. POST /v1/infra/servers (создание сервера) принял необязательный description. Значения становятся именем и описанием карточки приложения в каталоге Битрикс24. Если деплой прошёл без displayName и description, ответ содержит warnings: string[] с подсказкой задать их; одношаговое создание galaxy-приложения с source (тело POST /v1/infra/servers с полем source) тоже возвращает warnings в ответе 201, когда поля не заданы. Обратная совместимость сохранена — запросы без новых полей продолжают работать как раньше.

FIX-0717-7: деплой галактик отдаёт 503 при временной перегрузке базы

Было

При кратковременном исчерпании пула соединений с базой во время деплоя приложения в галактику POST /v1/infra/servers/:id/deploy возвращал общий 502 GALAXY_APP_DEPLOY_FAILED — тот же код, что и настоящий провал сборки. Клиент не мог отличить временную перегрузку от терминальной ошибки и часто читал ответ как окончательный.

Стало

Временная перегрузка базы теперь отдаётся как 503 POOL_EXHAUSTED с заголовком Retry-After (число секунд для повторной попытки). Настоящий провал сборки по-прежнему 502 GALAXY_APP_DEPLOY_FAILED.

Влияние на интеграторов

Ничего менять не нужно. Если ваш клиент повторяет запросы, теперь на 503 он получает явный сигнал бэкоффа через Retry-After вместо непрозрачного 502.

FIX-0717-8: фильтр и сортировка списка комментариев задачи работают на обеих карточках

Было

Запрос GET /v1/tasks/:taskId/comments с параметром filter или с сортировкой не по ID на портале с новой карточкой задачи возвращал 200 и пустой список, даже когда комментарии в задаче были. Ошибки не приходило, поэтому отличить «под фильтр ничего не подошло» от «фильтр не сработал» было нельзя.

Стало

Такой запрос возвращает подошедшие комментарии. Фильтр и сортировка работают по полям ID, AUTHOR_ID и POST_DATE, перед именем поля в фильтре допустим префикс !, >, >=, < или <=. Фильтр по AUTHOR_NAME и сортировка по AUTHOR_NAME или AUTHOR_EMAIL на новой карточке отвечают 400 с кодом UNSUPPORTED_FILTER_FIELD или UNSUPPORTED_SORT_FIELD и указывают на AUTHOR_ID, на старой карточке эти поля по-прежнему принимаются. Добавлен параметр offset — он учитывается на новой карточке при запросе с filter или сортировкой не по ID, на остальных путях чтения игнорируется. Код INVALID_FILTER теперь приходит ещё и тогда, когда filter — скаляр или пустой массив вместо объекта (0, false, "", []), значение поля ID или AUTHOR_ID не число, значение POST_DATE не разбирается как дата, либо значение поля — объект или массив вместо скаляра. В meta добавлено поле truncated со значением true — просмотрено предельное окно истории, и часть комментариев осталась за его границей.

Влияние на интеграторов

Менять ничего не нужно: запрос, который раньше отдавал пустой список, начинает отдавать данные. Учтите три границы. Первая — фильтр по AUTHOR_NAME и сортировка по AUTHOR_NAME или AUTHOR_EMAIL на портале с новой карточкой вместо пустого 200 теперь отвечают 400, переведите такой запрос на AUTHOR_ID, идентификатор сотрудника по имени даёт GET /v1/users. Вторая — meta.total на запросе с фильтром к новой карточке считает подошедшие комментарии в пределах просмотренного окна, а не во всей истории задачи, и при meta.truncated: true это неполное число. Третья — значение POST_DATE на новой карточке сравнивается с createdAt в UTC, поэтому результат на границе суток может отличаться от выборки на старой карточке. На порталах со старой карточкой поведение не изменилось.

2026-07-16

BC-0716-1: json_object на реасонинг-модели восстанавливает JSON, тело 422 уточнено

Поддержка старого формата до: 15.01.2027

Было

Запрос POST /v1/chat/completions с response_format типа json_object на модели с рассуждением (например bitrix/bitrixgpt-5.5-agent) стабильно возвращал 422 structured_output_truncated, даже когда модель завершилась сама (finish_reason: "stop") и положила готовый валидный JSON в служебный канал reasoning_content — ответ терялся. В теле любой такой ошибки присутствовали поля error.suggestedMaxTokens и error.param, а текст утверждал «finish_reason=length» независимо от реальной причины остановки.

Стало

Если модель на json_object завершилась сама и валидный JSON лежит в reasoning_content, платформа восстанавливает его и возвращает 200 с этим JSON в content (в потоковом режиме — чанком content перед терминальным чанком с finish_reason). Тело 422 стало правдивым: error.suggestedMaxTokens и error.param присутствуют только при реальной обрезке (finish_reason: "length"); при завершении по любой другой причине (stop и т.д.) эти поля опущены, а текст называет фактический finish_reason. Поведение json_schema не изменилось — там строгая схема проверяется на стороне модели.

Что делать интеграторам

Ничего, если вы просто обрабатываете 422 по error.code. Если ваш код безусловно читает error.suggestedMaxTokens или error.param на ошибке structured_output_truncated — сделайте чтение опциональным: при finish_reason !== "length" этих полей теперь нет. Потоковым клиентам с response_format — собирать content по всем дельтам до data: [DONE].

FIX-0716-2: env, отправленный файлом в multipart-деплое, больше не игнорируется молча

Было

При multipart/form-data в POST /v1/infra/servers/:id/deploy поле env, отправленное как файл или Blob, молча игнорировалось — деплой завершался успехом, но приложение стартовало без переменных окружения.

Стало

Такой запрос возвращает 400 с кодом VALIDATION_ERROR и подсказкой отправлять env текстовым полем с JSON-строкой.

Влияние на интеграторов

Корректный способ (текстовое поле env со значением вида {"KEY":"value"}) не затронут. Кто отправлял env файлом или Blob — теперь получает явную ошибку вместо ложного успеха.

FIX-0716-3: деплой крупных архивов через source.content больше не падает с Gateway HTTP 413

Было

На части порталов POST /v1/infra/servers/:id/deploy с source.content (base64-архив) падал на шаге download с { "code": "DEPLOY_FAILED", "message": "Gateway HTTP 413", "step": "download" }, если base64-тело превышало ~1 МБ (примерно 768 КБ исходного tar.gz) — вопреки заявленному лимиту 500 МБ. Обходом была загрузка через source.url.

Стало

Лимит 500 МБ на inline-загрузку (source.content и multipart) действует на всех порталах. source.url продолжает работать как раньше.

BC-0716-4: POST /v1/infra/servers отклоняет неизвестные поля в теле

Поддержка старого формата до: 15.01.2027

Было

Неизвестное поле в теле запроса молча игнорировалось. Запрос с deployMode: "STANDALONE" (несуществующее поле) возвращал 201 и создавал galaxy-приложение вместо ожидаемого выделенного сервера — правильное поле называется placement: "dedicated".

Стало

POST /v1/infra/servers отклоняет тело с неизвестным полем ошибкой 400 UNKNOWN_PARAM. В details.unknownFields перечислены лишние поля, в details.suggestions — подсказка правильного имени (deployMode → placement), в details.validParams — полный список допустимых полей.

Что делать интеграторам

Убрать из тела поля, которых нет в списке параметров создания, либо исправить опечатку по подсказке details.suggestions. Модель размещения задаётся полем placement (auto по умолчанию, dedicated — отдельная виртуальная машина).

NEW-0716-5: /v1/sites/fields описывает допустимые значения поля type

GET /v1/sites/fields теперь возвращает у поля type перечень допустимых значений в type.enum с подписями: PAGE (лендинг), STORE (интернет-магазин), KNOWLEDGE (база знаний 2.0), а также VIBE (сайт из конструктора) и SMN (связка с модулем «Управление сайтом»). Значения VIBE и SMN встречаются только в ответах и доступны только для чтения — создать сайт такого типа через API нельзя.

NEW-0716-6: Избранное и закрепление задачи без прав на редактирование

Добавлены четыре ручки для «личных» действий над задачей, которые в Битрикс24 разрешены при доступе только на чтение (не на редактирование): POST /v1/tasks/:taskId/favorite добавляет задачу в избранное, DELETE /v1/tasks/:taskId/favorite убирает из избранного, POST /v1/tasks/:taskId/pin закрепляет задачу в списке задач текущего пользователя, DELETE /v1/tasks/:taskId/pin открепляет. Раньше единственным способом изменить задачу был PATCH /v1/tasks/:id, который требует прав на редактирование и возвращал «Нет доступа к редактированию задачи», из-за чего разрешённые пользователю действия были недоступны.

NEW-0716-7: предупреждение о вытесняемом тарифе в ответах пробуждения по расписанию

Ответы POST /v1/infra/servers/:id/wake-schedules и PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId теперь дополнительно несут два поля верхнего уровня: preemptibleAdvisoryCode и preemptibleAdvisory. Если сервер работает на вытесняемом тарифе, preemptibleAdvisoryCode равен "PREEMPTIBLE_BEST_EFFORT", а preemptibleAdvisory — краткое английское пояснение того же факта: подъём такого сервера к моменту окна не гарантирован, окно может быть пропущено при нехватке свободной ёмкости. Для сервера на невытесняемом тарифе оба поля — null. Предупреждение не блокирует создание или обновление окна — это тот же неблокирующий паттерн, что уже используют поля tzWarning/tzWarningCode. Старые интеграции, не читающие новые поля, продолжают работать без изменений.

2026-07-14

NEW-0714-1: OpenAPI-спека: валидность 3.1, семантика полей и срез по скоупу

Было

Машинная спека GET /v1/openapi.json содержала 3.0-ключ nullable (невалиден в 3.1), не объявляла path-параметр {entityTypeId} на batch/aggregate/fields/products, не несла описаний/допустимых значений/примеров полей, описывала только ключи vibe_app_ и отдавалась одним монолитом.

Стало

Спека валидна по OpenAPI 3.1: nullable-поля используют union-тип ["<тип>","null"], все path-параметры объявлены. У свойств появились title/description, допустимые значения (x-enumValues + расшифровка в описании) и примеры. Секьюрити-схемы называют все три вида ключей (vibe_api_/vibe_app_/vibe_live_), у каждой операции есть x-required-scope, а в корне — каталог x-scopes. Добавлены корневые tags/externalDocs, блок webhooks для бот-событий и anyOf для мультиполей. GET /v1/openapi.json?scope=<scope> (например ?scope=crm) отдаёт срез спеки по одному скоупу, чтобы он помещался в контекст агента. Указатели на спеку добавлены в /v1/me и /v1/guide. Массовый перенос curl-примеров по каждой операции — отдельная последующая работа.

FIX-0714-2: Общая 5xx-ошибка на AI-эндпоинтах теперь в OpenAI-совместимом конверте

AI-эндпоинты (/v1/ai/*, /v1/models, /v1/chat/*, /v1/audio/*) документированы с OpenAI-совместимым форматом ошибок. При исчерпании пула соединений это уже соблюдалось, но общая (непредвиденная) 5xx-ошибка на этих эндпоинтах отдавала обычный V1-конверт.

Было

Общая 5xx-ошибка на AI-эндпоинте: {"success": false, "error": {"code": "...", "message": "..."}} — не тот формат, что SDK-клиенты OpenAI ожидают для этих путей.

Стало

Тот же случай теперь отдаёт {"error": {"message": "...", "type": "...", "code": "..."}} — единый конверт для всех ошибок AI-эндпоинтов, включая общие 5xx.

Влияние на интеграторов

Клиенты на OpenAI SDK, уже читающие error.type/error.code (штатный путь для этих эндпоинтов), не заметят изменения. Код, который на AI-эндпоинтах ожидал success/error.code в верхнем уровне именно на общих 5xx, должен переключиться на error.type/error.code.

FIX-0714-3: `versions` в списке версий исходников приложения ограничен 500 записями

GET /v1/apps/:id/sources возвращал весь список версий без ограничения — для приложений с очень длинной историей это была неограниченная выборка.

Было

data.versions — весь список версий без ограничения по размеру; data.totalVersions всегда равнялся data.versions.length.

Стало

data.versions содержит не более 500 последних версий (по savedAt, убывание). data.totalVersions и data.totalSizeBytes по-прежнему считаются по полной выборке — точность агрегатов не зависит от кэпа.

Влияние на интеграторов

Для приложений с историей до 500 версий поведение не меняется. Для приложений с историей больше 500 версий data.versions.length теперь может быть меньше data.totalVersions — код, полагавшийся на их равенство, должен ориентироваться на data.totalVersions/data.totalSizeBytes для агрегатов и не считать data.versions полным списком.

FIX-0714-4: Телефония: `userId`/`duration` теперь по-настоящему валидируются, а не только на truthy

POST /v1/calls/register, /v1/calls/:callId/show, /v1/calls/:callId/hide, /v1/calls/:callId/finish принимали userId (и duration у finish) без проверки типа/формы — любое truthy-значение (например, строка "abc" или объект) проходило в Битрикс24 и падало там уже непрозрачной ошибкой апстрима.

Было

{"userId": "abc"} (или любое другое truthy, не являющееся положительным целым) проходил валидацию и уходил в Битрикс24; ошибка Required: userId (number) появлялась только на полностью пустом/falsy значении.

Стало

userId принимается как положительное целое число либо как числовая строка ("42"), иначе — чистый 400 MISSING_PARAMS с уточнённым текстом Required: userId (positive integer) (для register — плюс phoneNumber). duration у finish аналогично — неотрицательное число либо числовая строка, иначе 400.

Влияние на интеграторов

Корректные вызовы (userId — число или числовая строка) не меняются. Вызовы с ранее «проходившим» некорректным userId/duration (не число, не числовая строка) теперь получают явный 400 вместо непрозрачной ошибки со стороны Битрикс24.

FIX-0714-5: Создание комментария к несуществующей задаче — понятная ошибка вместо ложного успеха

POST /v1/tasks/:taskId/comments и его пакетный вариант POST /v1/tasks/:taskId/comments/batch (action: create) обращаются к Битрикс24 для создания комментария на старой карточке задачи. Если задача не существует или недоступна ключу, Битрикс24 не создаёт комментарий и не возвращает идентификатор.

Было

Оба эндпоинта отвечали успехом с пустым идентификатором — одиночный вызов 201 {"success": true, "data": {"id": null}}, пакетный — элементом {"success": true, "id": null}. Комментарий не создавался, но интегратор не мог отличить это от штатного случая.

Стало

Одиночный вызов возвращает 404 TASK_NOT_FOUND. Пакетный вариант помечает соответствующий элемент как {"success": false, "error": "TASK_NOT_FOUND"}, не отменяя обработку остальных элементов пакета. Отдельный, не связанный с этой ошибкой случай сохраняется: на новой карточке задачи комментарий уходит в чат, и если система не смогла восстановить его id отдельным поиском, ответ по-прежнему success: true с id: null — комментарий в этом случае реально создан.

Влияние на интеграторов

Код, проверяющий data.id / data[i].id на null как признак ошибки, продолжит работать без изменений и получит более точный код ошибки. Код, полагавшийся на молчаливый успех с id: null при недоступной задаче, должен обрабатывать 404 / TASK_NOT_FOUND явно.

FIX-0714-6: Лимит запросов `/v1/search`, `/v1/research`, `/v1/batch` теперь считается на портал

Было

Лимиты (/v1/search 60/мин, /v1/research 20/мин, /v1/batch 30/мин) фактически считались по IP-адресу, а не по порталу. Портал с несколькими API-ключами (или несколько порталов за одним общим egress-IP) мог получить больше документированного лимита, а само ограничение обходилось ротацией IP.

Стало

Лимит считается на портал: все API-ключи одного портала делят единый бакет (60 / 20 / 30 запросов в минуту соответственно). Документированный лимит на тенант теперь применяется корректно и не обходится ни числом ключей, ни сменой IP.

Влияние на интеграторов

Если ваш портал распределял нагрузку на /v1/search / /v1/research / /v1/batch между несколькими API-ключами, суммарный предел теперь — единый лимит портала, а не сумма по ключам. При достижении предела возвращается 429 с заголовком Retry-After (как и раньше).

FIX-0714-7: Календарь: рабочий batch-delete секций, чистые ошибки update и без утечки sync-полей

Было

  • POST /v1/calendar-sections/batch с action: "delete" не имел канала для type/ownerId, которые требует calendar.section.delete — каждый элемент падал на обеих платформах.
  • PATCH /v1/calendar-sections/{id} без type/ownerId/name уходил в Битрикс24 и возвращал сырой 422 с именем внутреннего метода.
  • GET /v1/calendar-sections отдавал недокументированные сырые поля Битрикс24 GAPI_CALENDAR_ID, CAL_DAV_CON, SYNC_TOKEN, PAGE_TOKEN, EXTERNAL_TYPE (три из них — токены синхронизации).
  • GET /v1/calendar-events и GET /v1/calendar-events/{id} отдавали внутреннее поле attendeesEntityList — схема пыталась его вырезать, но не срабатывала из-за несовпадения регистра ключа.

Стало

  • Batch-delete секций читает type/ownerId из тела рядом с ids и прокидывает их в каждую команду удаления. Отсутствие любого — чистый 400 MISSING_REQUIRED_PARAMS до вызова Битрикс24.
  • Partial-update секций требует якорные type/ownerId/name (у секций нет get-by-id для подстановки) — чистый 400, без сырого 422 и без утечки имени метода.
  • Оба календарных read-пути убирают перечисленные внутренние/sync-поля из ответа.

Затронутые эндпоинты:

FIX-0714-8: PATCH catalog-product-properties снова работает (был неустранимый catch-22)

Было

Обновить свойство товара было невозможно ни при каком теле: PATCH без iblockId → 422 («Required fields: iblockId» — B24 требует его на каждом update), а PATCH с iblockId → 400 READONLY_FIELD (поле только для создания). Итог — весь глагол UPDATE мёртв: ни одно поле нельзя было изменить после создания.

Стало

iblockId теперь подставляется автоматически из существующей записи (pre-fetch, как у catalog-sections), так что PATCH {name:"…"} доходит до B24 с нужным iblockId и возвращает 200. iblockId в теле по-прежнему не нужен (и по-прежнему отклоняется как read-only, если его прислать) — его сохраняет сам сервис.

Влияние на интеграторов

Если раньше ваш PATCH свойства товара всегда падал 422/400 — теперь шлите только изменяемые поля (PATCH {name:"…"}), iblockId передавать не надо.

FIX-0714-9: Валидация entityTypeId: мусорные формы → 400 вместо тихого усечения

Было

Пять поверхностей парсили entityTypeId (селектор типа smart-process / динамической сущности) снисходительно — неякорным parseInt или коэрсирующим Number(), — и мусорная форма молча превращалась в ДРУГОЙ (в рамках портала) тип сущности:

  • Путевой entityTypeId (/v1/items/:entityTypeId/..., /v1/categories/:entityTypeId/... — CRUD и /aggregate): GET /v1/items/1058abc усекался до 1058, 1e3 → 1, 1.5 → 1.
  • Глобальный POST /v1/batch: params.entityTypeId через Number() принимал дробные (1.5), hex ('0x10' → 16), переполнение до Infinity, а также массив [1058] → 1058.
  • /v1/items/:entityTypeId/userfields/*: собственный парсер — 2abc резолвил пользовательские поля типа 2.
  • POST /v1/smart-processes/batch: ids: ['1030abc'] усекался до 1030 — delete/update молча выполнялся против ЧУЖОГО реального типа; дробное число (1030.5) тоже проходило.
  • POST /v1/triggers/fire (entityType="item"): entityTypeId: '1038abc' → триггер автоматизации выстреливал по типу 1038.

Стало

Все пять поверхностей требуют каноничную форму положительного целого (/^[1-9]\d*$/ для строк, Number.isInteger для чисел): любая иная форма → 400 с прежним кодом ошибки поверхности (INVALID_DYNAMIC_PARAM / INVALID_ENTITY_TYPE_ID / BATCH_ITEM_VALIDATION / MISSING_PARAMS) ДО вызова Битрикс24. Aggregate использует общий с CRUD-маршрутами валидатор вместо инлайн-копии.

Влияние на интеграторов

Формы, которые раньше коэрсились в корректное значение и обслуживались — 007 → 7, %20-пробелы, +2 → 2, массив [1058] в batch — теперь отклоняются с 400: значение должно быть каноничным целым без префиксов, хвостов и ведущих нулей. Boolean в batch отклонялся и раньше (коэрсился в reserved-тип); меняется только код ошибки — теперь INVALID_DYNAMIC_PARAM. Корректные вызовы не меняются.

FIX-0714-10: Переоткрытие фидбека очищает поля решения

Было

PATCH /v1/feedback/:id (и админ-эндпоинт PATCH /api/platform/feedback/:id, через который переоткрывает админ-UI) при возврате тикета в активный статус (NEW, REVIEWING, AWAITING_USER, NEEDS_REVIEW) из RESOLVED/WITHDRAWN не сбрасывал resolvedAt, resolvedBy и resolution — они «зависали» от предыдущего закрытия, и переоткрытый тикет выглядел одновременно активным и решённым.

Стало

Возврат тикета из RESOLVED/WITHDRAWN в активный статус очищает resolvedAt, resolvedBy и resolution. RESOLVED/WITHDRAWN по-прежнему проставляют штамп решения; ARCHIVED не трогает поля (архивирование сохраняет историю решения). Обычный переход между активными статусами (например AWAITING_USER → REVIEWING) поля НЕ трогает — на активных тикетах resolution отражает последний комментарий команды. Явно переданный в том же запросе resolution имеет приоритет над очисткой.

Влияние на интеграторов

Если вы читали resolvedAt/resolution переоткрытого тикета и получали значения от прошлого закрытия — теперь они null для активного тикета.

FIX-0714-11: `INVALID_JSON_BODY` больше не цитирует внутренний текст парсера

Было

Шесть маршрутных групп (/api/billing/*, /v1/apps*, /v1/bots*, /v1/keys*, /v1/note*, /v1/infra/servers/* deploy/exec/upload) на битый JSON отвечали 400 с сообщением вида Invalid JSON: Unexpected token } in JSON at position 41 — сырой текст движка V8 (отпечаток рантайма, деталь реализации). Группа deploy/exec/upload при этом не ставила и код ошибки.

Стало

Все шесть отвечают единым статическим сообщением Request body is not valid JSON. — как /v1/<entities> (тот же класс закрывается там отдельным исправлением). На V1-поверхностях код — INVALID_JSON_BODY (deploy/exec/upload теперь тоже его ставит); статус 400 не изменился.

Влияние на интеграторов

Если ваш код парсил текст сообщения (например, вытаскивал позицию ошибки) — опирайтесь на код INVALID_JSON_BODY; позиция ошибки больше не сообщается.

FIX-0714-12: Смета: amount, currency и даты в ответе GET снова заполнены (были null)

Было

Чтение сметы (GET/list/search /v1/quotes) отдавало null для amount, currency, beginDate, closeDate — значения «протекали» только под сырыми ключами Битрикс24 (opportunity, currencyId, begindate, closedate). Запись работала правильно, но READ-проекция теряла все поля-алиасы: сумма сметы и валюта были 100% невидимы через задокументированный API.

Стало

READ-ветка теперь реверс-мапит объявленные алиасы (зеркало write-маппинга): opportunity → amount, currencyId → currency, begindate → beginDate, closedate → closeDate — с приведением типов. Сырые ключи Битрикс24 в ответе больше не появляются.

Влияние на интеграторов

Если вы читали amount/currency смет и получали null — теперь они заполнены. Код, читавший обходным путём сырой opportunity/currencyId из ответа, их там больше не найдёт — переключитесь на документированные amount/currency.

FIX-0714-13: Валидация записи: фантомный чек-лист → 404, мусорные типы и `:id` → 400

Было

  • POST /v1/tasks/:taskId/checklist на несуществующую задачу возвращал 201 с правдоподобным id, хотя пункт не создавался (его нельзя было получить GET-ом).
  • POST /v1/warehouses принимал нестроковые title/address (число, объект) и пересылал их в Битрикс24 с непредсказуемым результатом; POST /v1/doc-templates так же пропускал нестроковые name/region и нечисловой numeratorId.
  • Нечисловой :id в путях сущностей с типизированным числовым id (GET/PATCH/DELETE /v1/quotes/abc, /v1/deals/1.5, /v1/leads/1e3) уходил в Битрикс24 как есть — в ответ прилетала непрозрачная ошибка B24 вместо внятного кода. У smart-processes 12abc усекался parseInt-ом до 12 и попадал в ЧУЖОЙ тип.

Стало

  • Чек-лист: родительская задача проверяется до создания пункта; несуществующая (или недоступная ключу) задача → 404 TASK_NOT_FOUND.
  • Склады и шаблоны документов: значение не того типа → 400 INVALID_PARAMS без вызова Битрикс24 (числовая строка в numeratorId по-прежнему принимается).
  • Сущности с явно типизированным числовым id: не-канонично-целый :id → 400 INVALID_PARAMS до вызова Битрикс24 (id 0 — основная воронка сделок categories — остаётся валидным). Smart-processes сохраняют INVALID_ENTITY_TYPE_ID и теперь отклоняют 12abc на GET/PATCH/DELETE, а не усекают до 12. Сущности, у которых тип id не задекларирован в схеме, сохраняют прежнее сквозное поведение.

Затронутые эндпоинты: POST /v1/tasks/:taskId/checklist, POST /v1/warehouses, POST /v1/doc-templates + GET/PATCH/DELETE по сущностям с типизированным числовым id.

Влияние на интеграторов

Если ваш код опирался на фантомный 201 от чек-листа или отправлял мусорные значения «на авось» — теперь придёт явный 4xx с кодом. Корректные вызовы не меняются.

FIX-0714-14: Пять тихих false-success/hint-дефектов: честный ответ вместо мнимого успеха

Было

  • PATCH /v1/userfields/{entity}/{id} c label — тихий no-op на обновлении: ответ 200, но подпись поля не менялась (Битрикс24 crm.*.userfield.update игнорирует LABEL).
  • POST /v1/humanresources/nodes/{id} c parentId — ложный «перенос удался»: name применялся, parentId молча игнорировался, а в ответе эхом возвращался старый родитель.
  • POST /v1/chats/messages/bulk — псевдоним dialogId: "me" не разрешался в bulk-цикле (одиночные роуты его разрешают) → сообщение уходило не туда.
  • POST /v1/bots/{botId}/chats/{dialogId}/users — предохранитель USERS_NOT_ADDED был мёртвым кодом (не совпадал с формой ответа метода v2) → неудачное добавление проходило как успех.
  • Ошибки crm.item.list получали нерелевантную подсказку «Maximum 50 records…» даже когда причина была иной (например «entity type does not exist»).

Стало

  • label на обновлении разворачивается в реальные параметры EDIT_FORM_LABEL/LIST_COLUMN_LABEL/LIST_FILTER_LABEL — подпись действительно меняется.
  • parentId и type теперь только для создания: на PATCH они отклоняются с 400 (перенос — через POST /v1/humanresources/nodes/{id}/move), а не имитируют успех.
  • Bulk-чтение сообщений разрешает dialogId: "me" для каждого элемента, как и одиночные роуты.
  • Предохранитель добавления в бот-чат снова срабатывает: непринятые пользователи возвращаются в warning.USERS_NOT_ADDED.
  • Подсказки об известных ограничениях привязаны к релевантности сообщения ошибки, а не только к имени метода.

Затронутые эндпоинты:

FIX-0714-15: GET /v1/task-time теперь честно возвращает больше 50 записей при limit>50

Было

GET /v1/task-time с limit больше 50 возвращал только 50 записей, хотя meta.limit повторял запрошенное значение, а meta.hasMore мог вводить в заблуждение. Клиент, обходивший данные постранично с шагом больше 50, молча терял часть записей.

Стало

Запрос отдаёт до limit записей (максимум 500), собирая их постранично на стороне бэкенда; meta.total и meta.hasMore соответствуют фактически возвращённому окну. При limit больше 50 значение offset теперь указывает на правильную позицию, а не сдвигается на первые страницы.

NEW-0714-16: GET /v1/companies/fields и системные поля catalog-prices получили label и description

В ответе GET /v1/companies/fields теперь у всех полей компании есть человекочитаемые label и description. В GET /v1/catalog-prices/fields те же метки добавлены системным полям extraId, priceScale и timestampX. Метки приходят по-русски. Семантику поля можно получить программно из ответа, без сверки со статической документацией.

FIX-0714-17: /stop и /reboot различают отсутствующий сервер и неподходящий статус

Было

POST /v1/infra/servers/:id/stop и POST /v1/infra/servers/:id/reboot на сервере не в статусе running (например, спящем) отвечали плоским 404 NOT_FOUND с текстом «Running server not found» — по нему нельзя было понять, что сервер существует, и агент решал, что он удалён.

Стало

Оба маршрута ведут себя как /start и /wake: 404 SERVER_NOT_FOUND — только когда сервера с таким id действительно нет; 422 SERVER_WRONG_STATE — когда сервер есть, но не в статусе running. В error.currentState возвращается текущее состояние, в error.availableActions — доступные действия (wake/start/repair/delete).

FIX-0714-18: понятная ошибка на /fields у комментариев и учёта времени задач

Было

Запрос GET /v1/tasks/:taskId/comments/fields отвечал сбивающим с толку 400 INVALID_PARAMS с текстом «taskId and id must be positive integers» (спрашивали про поля — ответ про идентификатор), а GET /v1/tasks/:taskId/time/fields утекал сырой ошибкой Bitrix24 с внутренним PHP-методом и HTML-тегом.

Стало

Обе сущности распознают сегмент fields и возвращают понятный 400 WRONG_PATH: у них нет метода /fields (состав полей описан в справочнике /v1/guide), в тексте перечислены доступные маршруты. Нечисловой или дробный идентификатор в маршрутах по id теперь отклоняется как 400 INVALID_PARAMS до обращения к Bitrix24 — без утечки внутренней ошибки.

FIX-0714-19: Лимиты `/v1/ai/credentials*` считаются на портал; `CREDENTIAL_NOT_FOUND` подсказывает решение

Было

  • Лимиты BYOK-маршрутов (POST /v1/ai/credentials, /:id/test, /:id/fetch-models — 10/мин; /:id/models добавление/удаление — 30/мин) фактически считались по IP-адресу: перебор чужих ключей обходился ротацией IP, а арендаторы за одним общим egress-IP делили один бакет. Маршрут PATCH /:id (при передаче credentials он проверяет ключ у апстрима — тот же оракул, что /:id/test) не имел лимита вовсе.
  • 404 CREDENTIAL_NOT_FOUND от /v1/search и /v1/research содержал только слаг провайдера — без указания, как настроить ключ.

Стало

  • Лимит считается на портал: все API-ключи одного портала делят единый бакет; ротация IP и число ключей на предел не влияют. При превышении — 429 с Retry-After. Дополнительно PATCH /:id (проверяет ключ у апстрима, как /:id/test) раньше вовсе не имел лимита — теперь тоже 10/мин на портал.
  • В ответ CREDENTIAL_NOT_FOUND добавлено поле hint с точным рецептом: POST /v1/search/credentials {provider, apiKey}, список провайдеров — GET /v1/search/providers.

FIX-0714-20: PATCH bizproc-шаблонов, роботов и активностей через /v1 применяет изменения

Было

PATCH /v1/bizproc-templates/:id, /v1/bizproc-robots/:code и /v1/bizproc-activities/:code с полями метаданных (name, description, autoExecute) возвращали 422 BITRIX_ERROR "No fields to update." — обновить сущность было нельзя (то же и в пакетных запросах). Поле autoExecute, переданное числом, дополнительно отвергалось как Incorrect field AUTO_EXECUTE!.

Стало

Поля корректно применяются (в том числе autoExecute, переданное числом); эндпоинт подтверждает успех и возвращает id обновлённой сущности. Работают и одиночный PATCH, и пакетные запросы. Создание (POST) не изменилось.

FIX-0714-21: Транскрипция: проверка кошелька до вызова распознавания

Было

POST /v1/audio/transcriptions (и /v1/ai/audio/transcriptions) не проверял состояние кошелька перед вызовом: PREPAY-счёт, ушедший за овердрафт (но ещё не замороженный фоновой проверкой), всё равно запускал распознавание и уводил баланс глубже в минус. Чат и эмбеддинги отклоняли такой вызов заранее; полностью замороженный счёт и раньше блокировался глобально (ACCOUNT_FROZEN).

Стало

Как у чата и эмбеддингов: денежная проверка выполняется до обращения к Whisper. Превышенный овердрафт → 402 insufficient_balance, распознавание не запускается. BYOK-ключи (USER-scope) бесплатны — для них проверки нет и поведение не меняется.

FIX-0714-22: удаление приложения больше не блокируется галактическим хостом на его ключе

Было

DELETE /v1/apps/:id возвращал 409 APP_HAS_ACTIVE_SERVERS, если на ключе приложения оказывался галактический хост (общая инфраструктура портала). Снять хост через удаление приложения было нельзя, а объяснения в ответе не было.

Стало

Галактический хост исключён из проверки блокирующих серверов: он управляется на уровне портала, а не ключа, поэтому не должен мешать удалению приложения. Отдельные серверы приложения (в том числе контейнеры приложений) по-прежнему блокируют удаление с 409 APP_HAS_ACTIVE_SERVERS — их сначала нужно перепривязать к другому ключу.

FIX-0714-23: OpenAPI: тело per-entity батч-эндпоинтов теперь описано верно (action + items/ids/calls)

Было

Спека (GET /v1/openapi.json) описывала тело каждого пер-сущностного батча как {create:[], update:[], delete:[]}. Рантайм же (общий обработчик батчей) требует {action, items|ids|calls} и отвечает 400 INVALID_BATCH_ACTION на документированную форму. Клиент, сгенерированный из спеки (codegen / AI-агент), получал 100% отказ batch-write на всех ~45 пер-сущностных батч-эндпоинтах. Сама фича работает — врала только спека (глобальный POST /v1/batch, /v1/tasks/{taskId}/comments/batch и /v1/guide уже описывали правильную форму).

Стало

Генератор спеки выдаёт правильную форму: один action (create/update/delete/list/get/fields); create/update шлют items, delete — ids, чтения — calls. Совпадает с рантаймом и с глобальным /v1/batch.

Влияние на интеграторов

Если вы генерировали клиент из openapi.json и batch-write падал INVALID_BATCH_ACTION — перегенерируйте: тело теперь {action:"create", items:[…]} вместо {create:[…]}. Handwritten-клиенты, уже славшие {action,…}, не затронуты.

FIX-0714-24: Валюты: fullName, формат и число знаков теперь сохраняются при плоской записи

Было

POST/PATCH /v1/currencies с плоскими fullName, formatString, decimals, decPoint, thousandsSep возвращал успех, но значения молча терялись — Битрикс24 хранит их в структуре локализации по языкам (LANG), а API слал их плоско. Задокументированный обходной путь «передавайте сырой LANG» тоже не работал: LANG — поле только для чтения, запрос отклонялся.

Стало

API упаковывает плоские локализуемые поля в локализацию вашего языка (языка API-ключа) перед вызовом Битрикс24, поэтому плоская запись сохраняется и читается обратно (POST {fullName:"…",decimals:3} → GET вернёт их). Работает на всех путях записи: одиночном, POST /v1/currencies/batch и глобальном POST /v1/batch. Сырой LANG в теле по-прежнему отклоняется как read-only. Разные значения сразу для нескольких языков через API пока не задаются. Язык записи определяется локалью API-ключа (ru или en) и может не совпадать с языком отображения валюты на портале — на порталах с иной локалью (de/pl/ua…) правка ляжет под en.

Влияние на интеграторов

Если вы обходили баг сырым LANG (и упирались в 400 READONLY_FIELD) — уберите его и шлите плоские поля. Уже работавшие плоские запросы теперь ещё и сохраняют значения.

FIX-0714-25: Aggregate уважает обязательные фильтры; infra-валидация не течёт сырым Zod

Было

  • POST /v1/<entity>/aggregate с сущностью, требующей фильтр (напр. catalog-products требует iblockId), пропускал пустой запрос в Битрикс24 и возвращал сырой 422, тогда как GET-list/search на том же условии отдают чистый 400.
  • POST /v1/infra/servers/:id/{deploy,exec,upload,logs} при ошибке валидации тела клал в error.message многострочный JSON-сериализованный массив Zod-issues (сырой отпечаток валидатора).

Стало

  • Aggregate проверяет обязательные фильтры/параметры до вызова Битрикс24: нет обязательного фильтра — 400 MISSING_REQUIRED_FILTER (напр. catalog-products→iblockId); нет обязательного list-параметра — 400 MISSING_REQUIRED_PARAMS (напр. calendar-events→type,ownerId; humanresources-nodes→type). Как у list/search.
  • Infra-валидация форматирует ошибку компактно (поле: сообщение; …), как соседний infra.ts. Код (VALIDATION_ERROR) и статус 400 не изменились.

Влияние на интеграторов

Если вы ловили сырой 422 от aggregate без фильтра — теперь придёт 400 MISSING_REQUIRED_FILTER. Если парсили infra-error.message как JSON — теперь это плоская строка поле: сообщение.

FIX-0714-26: Batch: работает per-entity batch для items и создание папок через batch

Было

  • POST /v1/items/{entityTypeId}/batch возвращал 404 — per-entity batch-роут для сущностей с динамическим параметром (items, categories) монтировался по адресу без сегмента параметра (/v1/items/batch), поэтому документированный путь не находился, а entityTypeId не доходил до команды Битрикс24. Обходной путь — глобальный POST /v1/batch — работал.
  • POST /v1/folders/batch с action: "create" падал на каждом элементе с ERROR_ARGUMENT: batch-создание слало fields[...], тогда как disk.folder.addsubfolder ожидает родительскую папку верхним параметром id, а остальные поля — под data[...].

Стало

  • Per-entity batch-роут для items/categories монтируется с сегментом :{entityTypeId} и прокидывает валидированный entityTypeId (положительное целое; id выделенных API вроде deals=2 отклоняются с подсказкой — как в одиночных роутах) в каждую команду — для всех действий: create/update/delete и read-действий list/get/fields.
  • Batch-создание папок повторяет форму одиночного роута: id=<родитель>&data[...]. Пропущенный parentId — чистый per-item 400 до вызова Битрикс24.

Затронутые эндпоинты:

NEW-0714-27: восстановление зависшего exec-канала сервера

Новый эндпоинт POST /v1/infra/servers/:id/unstick принудительно освобождает залипший канал команд Black Hole-сервера, когда деплой или exec продолжает отвечать EXEC_BUSY («Another command is running») даже после DELETE /v1/infra/servers/:id/lock. Эндпоинт снимает блокировку на стороне платформы и разрывает туннель агента — при переподключении агент завершает зависшую команду и освобождает свой мьютекс. Перезагрузка сервера не выполняется.

Если в момент вызова на сервере ещё выполняется легитимная операция (деплой, exec, харден), эндпоинт по умолчанию отвечает 409 OPERATION_IN_PROGRESS и НЕ трогает её — расклинивать нужно только зависший канал. Повторите с ?force=true, если уверены, что канал команд действительно завис.

Ответ: { success: true, data: { backendLockReleased, agentBounced, reconnected } }. Коды ошибок: 404 SERVER_NOT_FOUND, 409 CONFLICT (восстановление уже идёт), 409 OPERATION_IN_PROGRESS (на сервере выполняется операция — повторите с ?force=true), 409 GALAXY_UNSTICK_UNSUPPORTED (для galaxy-хостов и galaxy-приложений не поддерживается), 502 GATEWAY_ERROR. Ошибка /exec (EXEC_BUSY) и провал /deploy (код DEPLOY_FAILED, сообщение «Another command is running») теперь несут подсказку hint с этим эндпоинтом.

NEW-0714-28: описание сервера в `PATCH /v1/infra/servers/:id`

Было

PATCH /v1/infra/servers/:id принимал только displayName. Поле description в контракте отсутствовало, а GET-ответы его не отдавали.

Стало

PATCH /v1/infra/servers/:id принимает опциональное поле description (строка, до 500 символов; пустая строка или null очищает описание; отсутствие поля оставляет текущее значение без изменений). Значение синхронизируется с карточкой приложения в каталоге. Поле description теперь возвращается в GET /v1/infra/servers, GET /v1/infra/servers/:id и в ответе PATCH. Старые запросы без description продолжают работать без изменений.

FIX-0714-29: деплой galaxy-приложения при обрыве связи во время сборки возвращает честную ошибку, а не ложный успех

Было

Если соединение с хостом обрывалось прямо во время сборки galaxy-приложения (частое явление под нагрузкой тяжёлой сборки), POST /v1/infra/servers/:id/deploy мог вернуть 200 со статусом running и пометкой [recovered] в buildLog, хотя новая версия так и не собралась и не поднялась — на хосте продолжал работать прежний контейнер. Повторный деплой упирался в тот же обрыв и снова рапортовал ложный успех.

Стало

Деплой считается восстановленным, только если он полностью завершился: под именем приложения запущен контейнер именно этой попытки, и деплой прошёл до конца. Если связь оборвалась во время сборки — или в любой момент до завершения деплоя — и новая версия не поднялась полностью, эндпоинт возвращает retryable-ошибку 502 с кодом GALAXY_HOST_UNREACHABLE и подсказкой «повторите тот же деплой, не удаляйте сервер» — вместо ложного 200. Только если деплой завершился полностью и потерялся лишь финальный ответ, восстановление в 200 работает как прежде.

BC-0714-30: переименование приложения в каталоге доходит до Битрикс24, publish потерял menuTitle

Поддержка старого формата до: 14.01.2027

Было

PATCH /v1/apps/:id с полем title у приложения, добавленного в каталог, возвращал 200, но не менял ничего из того, что видит пользователь: карточка каталога и привязки мест встраивания на портале (пункт левого меню, вкладки CRM) оставались со старым именем. Отказа не было никогда — вызов всегда успешен.

У POST /v1/apps/:id/publish тело не проверялось, а заголовок места встраивания задавался отдельным полем menuTitle.

Стало

Для приложения в каталоге title — это одна операция над отображаемым именем: имя синхронизируется в карточку каталога (вместе с title пишется catalogTitle) и перепривязывается в места встраивания на портале. Поэтому вызов, который раньше всегда отдавал 200, теперь может честно отказать:

  • 400 NO_USER_TOKEN — приложение не авторизовано на портале, перепривязать места встраивания нечем;
  • 400 TITLE_TOO_LONG_FOR_CATALOG — имя приложения в каталоге ограничено 100 символами, тогда как title допускает 255;
  • 502 BITRIX_PARTIAL_REBIND — Битрикс24 отклонил привязку. Имя в этом случае не записывается, повтор того же запроса чинит состояние.

Тело POST /v1/apps/:id/publish теперь валидируется, а параметр menuTitle удалён: заголовок места встраивания всегда равен отображаемому имени приложения. Пустое тело по-прежнему работает — публикация берёт значения из записи приложения.

Что делать интеграторам

  • Убрать menuTitle из тела публикации: поле игнорируется, имя пункта меню задаётся через title и catalogTitle.
  • Держать имя приложения в каталоге в пределах 100 символов.
  • Обработать отказы при переименовании: на NO_USER_TOKEN авторизовать приложение на портале, на BITRIX_PARTIAL_REBIND повторить запрос.
  • Учесть побочный эффект: переименование через title теперь пишет и catalogTitle — у приложения в каталоге эти поля держатся синхронными.

NEW-0714-31: GET /v1/tasks/:taskId/comments/fields — схема полей комментариев задачи

У комментариев к задаче появился метод /fields, как у остальных сущностей: GET /v1/tasks/:taskId/comments/fields возвращает статическую схему из 5 полей (id, taskId, authorId, message, createdAt) с типом, признаком «только для чтения», названием и описанием по-русски. Метод не обращается к Битрикс24. Раньше этот путь возвращал 400 WRONG_PATH — состав полей был доступен только из статических доков.

FIX-0714-32: пер-сущностный batch с action list теперь применяет filter

Было

POST /v1/{entity}/batch с action: "list" игнорировал filter: имена полей не приводились к именам Битрикс24 (например statusId у лидов не превращался в stageId), а операторы $gt / $contains / $in и другие не работали. Вызов возвращал 200 со всей таблицей — тихий отказ с неверными данными. Глобальный POST /v1/batch и POST /v1/{entity}/search при этом фильтровали корректно.

Стало

Пер-сущностный batch пропускает filter через тот же транслятор, что search и глобальный /v1/batch. Псевдонимы полей и операторы ($gt, $gte, $lt, $lte, $ne, $contains, $in, $nin, префиксные >=, <=, ! и т.д.) применяются. Некорректный filter (неизвестное поле у сущности с полной схемой, неподдерживаемый оператор, префиксы @ / !@, логические токены $or / $and) теперь возвращает 400 с указанием индекса вызова, а не молча всю выборку — так же, как на одиночных эндпоинтах.

FIX-0714-33: авто-пагинация больше не дублирует записи на границах страниц

Было

Для методов Битрикс24 без поддержки сортировки (например список объектов хранилища) запись на границе страниц могла сместиться между запросами соседних страниц и попасть в обе — при limit > 50 в выдаче появлялся дубль, занимавший слот, и клиент обрабатывал одну и ту же запись дважды.

Стало

После склейки всех страниц выдача дедуплицируется по id (сохраняется первое вхождение). Для методов со стабильной сортировкой ничего не меняется (дублей нет — операция вхолостую).

FIX-0714-34: список полей шаблона реквизитов больше не пустеет; создание не возвращает чужую запись

Было

GET /v1/requisite-presets/:presetId/fields мог вернуть 200 с пустым data: [], хотя в шаблоне есть поля: метод Битрикс24 отдаёт result то массивом [{…}], то объектом-картой {"0":{…},"1":{…}}, а обработчик принимал только массив. При создании поля (POST …/fields) ответ мог содержать ЧУЖУЮ существующую запись — эхо созданной строки читалось по id из ответа add, а на некоторых порталах чтение по этому id возвращало другое поле.

Стало

Список нормализует обе формы ответа Битрикс24 (массив и объект-карту) — поля больше не теряются. Эхо созданной записи возвращается только если её fieldName совпадает с тем, что создавали; при любом расхождении ответ содержит { id } (сам объект не подставляется), чтобы клиент не получил постороннюю строку.

Влияние на интеграторов

Идентификатор поля шаблона (id) — это позиционный идентификатор Битрикс24: он может меняться после операций записи в шаблоне и на части порталов не является устойчивым ключом. Не кэшируйте id между изменениями шаблона — перечитывайте список полей перед get/update/delete по конкретному полю.

FIX-0714-35: привязка плейсмента для ранее созданных приложений больше не отклоняется по обработчику

Было

POST /v1/placements/bind мог вернуть 400 с кодом PLATFORM_HANDLER_UNRESOLVABLE для приложения, созданного до перехода на единый платформенный обработчик (у такого приложения в качестве обработчика сохранился его собственный технический адрес). Привязка отклонялась даже тогда, когда платформенный обработчик /v1/bitrix-handler был доступен, — приложение нельзя было опубликовать заново через API.

Стало

Привязка проходит: обработчик плейсмента регистрируется на платформенный /v1/bitrix-handler, а в ответе появляются handlerRewritten: true и requestedHandler с исходным значением. Код PLATFORM_HANDLER_UNRESOLVABLE теперь возвращается только когда платформенный обработчик действительно недоступен. POST /v1/placements/unbind снимает такой плейсмент по тому же адресу.

2026-07-13

FIX-0713-1: Платформенные scope ключа OAuth-приложения синхронизируются при создании и правке

Было

Ключ, выписанный вместе с OAuth-приложением через POST /v1/apps, не получал платформенные scope (vibe:infra, vibe:ai, vibe:search, vibe:storage) — в отличие от ключа, созданного в кабинете. Из-за этого POST /v1/infra/servers под таким ключом отвечал 403 INFRA_SCOPE_REQUIRED. Добавление vibe:infra в scope приложения через PATCH /v1/apps/:id меняло только приложение, но не парный ключ — эффекта на доступ не было.

Стало

Парный ключ при создании через POST /v1/apps получает те же платформенные scope по умолчанию, что и ключ из кабинета. Изменение vibe:*-scope через PATCH /v1/apps/:id (добавление И удаление) теперь синхронизируется в активные ключи приложения. Ключ в режиме «только чтение» (READONLY) при этом отклоняется с 403 WRITE_BLOCKED_READONLY_KEY на трёх write-операциях: создание сервера (POST /v1/infra/servers), изменение scope приложения (PATCH /v1/apps/:id) и выписка READWRITE-ключа (POST /v1/apps с mode: "READWRITE").

Влияние на интеграторов

Приложения, созданные через API, теперь могут управлять инфраструктурой без пересоздания. Ключ, у которого приложение уже объявляет vibe:infra, но сам ключ его лишён (старое расхождение), чинится одной правкой: снять vibe:infra из scope приложения и вернуть обратно через PATCH /v1/apps/:id — вторая правка синхронизирует ключ; либо пересоздать приложение. READONLY-ключ по-прежнему может создавать READONLY-приложение (mode: "READONLY").

FIX-0713-2: product-sections: неподдерживаемые фильтры возвращают 400, а не всю таблицу

Было

Операторы (>, <, !, %, $ne, $contains, $nin) и поля вне точного равенства (sort, неизвестные) в фильтре GET /v1/product-sections и POST /v1/product-sections/search молча игнорировались — возвращался код 200 и весь список без фильтра.

Стало

Такие фильтры отклоняются с 400 UNSUPPORTED_FILTER. Фильтруйте точным равенством или $in по id, name, xmlId, code, catalogId, sectionId. Сортировка (order/sort) не изменилась.

Влияние на интеграторов

Вызовы с операторами или filter[sort], ранее возвращавшие 200 с неотфильтрованными данными, теперь возвращают 400 — переключитесь на точное равенство или $in.

FIX-0713-3: причина неудачного первого провижининга сервера теперь видна

Было

Если сервер не поднимался при первом создании (вытесняемый тариф вытеснен, нет свободной ёмкости), он молча оказывался в статусе sleeping без объяснения причины. Клиент, опрашивающий GET /v1/infra/servers/:id, видел sleeping и не понимал, почему деплой заблокирован.

Стало

Такой сервер теперь переходит в status: "error" с заполненным provisionError (человекочитаемая причина) и новым полем provisionErrorCode — машиночитаемой категорией сбоя (PREEMPTIBLE_EVICTION / PROVISION_TIMEOUT / NO_CAPACITY / GENERIC). Поле provisionErrorCode добавлено в ответы GET /v1/infra/servers/:id и GET /v1/infra/servers рядом с provisionError (аддитивно, null, если ошибок не было).

Влияние на интеграторов

Ничего менять не нужно: error — уже существующий статус. Восстановление такого сервера — через POST /v1/infra/servers/:id/start или /repair (не /wake: на статусе error он вернёт 422; поле availableActions в ответе подсказывает доступное действие).

NEW-0713-4: POST и PATCH /v1/tasks/:taskId/time принимают createdDate

Необязательное поле createdDate теперь передаётся в POST /v1/tasks/:taskId/time и PATCH /v1/tasks/:taskId/time/:itemId — запись учёта времени ложится на указанную дату, а не на текущий момент (сценарий понедельничного добивания трека за прошлую неделю). Принимаются ISO 8601 со смещением, ISO без смещения и YYYY-MM-DD — значение уходит в CREATED_DATE без изменений. Если поле не передано, поведение прежнее — дата равна моменту создания.

FIX-0713-5: meta.hasMore перестаёт зависать в true при filter + offset

Было

При пагинации списка с фильтром (например GET /v1/deals с filter и offset) meta.hasMore оставался true на любом offset — даже далеко за пределами meta.total. Цикл while (meta.hasMore) { offset += limit } уходил в бесконечность.

Стало

Когда Bitrix24 игнорирует смещение за пределами отфильтрованного набора и отдаёт его целиком, meta.hasMore считается по окну запроса (offset + limit < meta.total), а не выставляется в true безусловно. При offset=0 ответ прежний; после того как окно перекрыло total, hasMore становится false. Затрагивает список и POST /search для всех сущностей.

FIX-0713-6: GET /v1/lists и /v1/lists/:iblockId/elements учитывают offset

Было

GET /v1/lists/:iblockId/elements и GET /v1/lists молча игнорировали offset — при ?limit=50&offset=50 возвращалась та же первая страница, и клиент, листающий через offset, никогда не доходил до строк 51 и дальше.

Стало

offset мапится в нативный для этих методов параметр start, так что пагинация через offset работает. Явный start сохраняет приоритет, если переданы оба.

NEW-0713-7: Флаг preserveEnv в деплое сохраняет .env при cleanDeploy

Тело POST /v1/infra/servers/:id/deploy приняло необязательный булев preserveEnv (по умолчанию false). Когда cleanDeploy: true (очищает каталог приложения вместе с файлом .env), а preserveEnv: true — существующий .env считывается до очистки и восстанавливается после неё, если в этом же запросе не передан env (переданный env имеет приоритет). Без флага повторный деплой с cleanDeploy мог поднять приложение без переменных окружения — на порту по умолчанию.

NEW-0713-8: у неудачной сборки galaxy-приложения появилась подсказка `buildHint`

Было

Провал сборки galaxy-приложения возвращал только GALAXY_APP_BUILD_FAILED (и GALAXY_APP_START_FAILED) c «хвостом» лога в buildLog — разобрать причину можно было лишь вручную. GET /v1/infra/servers/:id по ERROR-приложению отдавал короткий provisionError, но без готовой рекомендации.

Стало

Ответ теперь несёт разобранную причину. В теле ошибки POST /v1/infra/servers/:id/deploy (502) при классифицируемом провале дополнительно приходят error.category (машинная категория, например MODULE_NOT_FOUND, INSTALL_AUTH, RESOURCE) и error.buildHint — локализованная строка-рекомендация с конкретным следующим шагом. GET /v1/infra/servers/:id по ERROR-приложению добавляет то же поле data.buildHint. Поля аддитивные: если причина не распознана, они отсутствуют (buildHint — null), а provisionError/buildLog продолжают приходить как раньше — старые запросы не меняются.

FIX-0713-9: одновременные одинаковые сохранения исходников больше не создают дубль-версию

Было

Два одновременных сохранения одинаковых байтов на один сервер (POST /v1/infra/servers/:id/sources, а также автосохранение при деплое) при редком стечении таймингов создавали две байт-идентичные версии вместо одной — дедупликация по содержимому была best-effort.

Стало

Дедупликация детерминированная: одинаковые байты, отправленные одновременно, всегда сходятся на одну версию. На POST /v1/infra/servers/:id/sources оба ответа возвращают один и тот же versionId, проигравший запрос получает deduplicated: true; автосохранение при деплое сходится на ту же единственную версию (формат ответа деплоя не менялся).

2026-07-12

NEW-0712-1: Ответ при исчерпании AI-квоты подсказывает путь к пополнению

Ошибка 402 ai_quota_exhausted с reason: wallet_empty теперь дополнительно возвращает поля hint и topupUrl. hint — короткая подсказка на английском: месячная AI-квота и баланс портала исчерпаны, и администратор портала может пополнить баланс, чтобы возобновить работу. topupUrl — ссылка, которую администратор открывает для пополнения. Поля аддитивны: прежние поля ответа (reason, resetAt) не изменились, а ветки reason: breaker и reason: wallet_off их не несут. Так агент-клиент может передать человеку путь к пополнению. Ошибку возвращают вызовы моделей, см. POST /v1/chat/completions.

FIX-0712-2: `sleep-now` на сервере с давно прошедшим расчётным пробуждением теперь честно усыпляет

Было

Для сервера с включённым расписанием пробуждения вызов POST /v1/infra/servers/:id/sleep-now мог навсегда возвращать { data: { slept: false, reason: "WAKE_IMMINENT" } }, если денормализованное «следующее пробуждение» осталось в далёком прошлом (сервер разбудили вне планировщика, и штамп не пересчитался). Сервер никогда не засыпал и держал тариф включённым круглосуточно.

Стало

Штамп старше динамического окна («поле для манёвра» = margin + типовой lead) считается протухшим, а не «неминуемым»: sleep-now честно усыпляет сервер и отвечает { success: true }, после чего планировщик тихо перекатывает якорь к будущему окну без пробуждения. WAKE_IMMINENT остаётся штатным ответом только для действительно близкого пробуждения.

2026-07-11

FIX-0711-1: галактики: ошибка GALAXY_HOST_UNREACHABLE стала действенной — подсказка hint и точная причина в provisionError

Было

Когда хост галактики был временно недоступен, POST /v1/infra/servers/:id/deploy отвечал голым 502 с кодом GALAXY_HOST_UNREACHABLE, а слот, созданный одним вызовом POST /v1/infra/servers с source, переходил в статус ошибки с текстом «Deploy failed unexpectedly — please retry; details are in the server logs». Ни причина, ни путь восстановления не сообщались — клиенты удаляли слот и создавали новый, что не помогает: новый слот попадает на тот же хост.

Стало

Ответ 502 GALAXY_HOST_UNREACHABLE — при деплое, exec-команде и удалении (DELETE /v1/infra/servers/:id) — несёт структурную подсказку error.hint: состояние временное, слот и его данные целы, нужно повторить тот же запрос через 1–2 минуты, удалять слот не нужно. На пути «создание с source» поле provisionError теперь содержит настоящую причину («Galaxy host … became unreachable during build …») и тот же совет повторить деплой в существующий слот. Дополнительно: когда несколько серверов работают под одним OAuth-приложением, сохранённая версия исходников больше не теряется из-за конфликта нумерации версий — ни при автосохранении на деплое, ни при явном сохранении через POST /v1/infra/servers/:id/sources.

Влияние на интеграторов

Изменение аддитивное: коды и статусы ответов не менялись, добавилось поле error.hint и уточнился текст provisionError. Обновлять клиентов не нужно. AI-агентам стоит читать hint.recovery — там прямо сказано, что делать.

NEW-0711-2: отдельный код ошибки, когда на портале не установлен модуль Vibecode Connector

Выписка ключа приложения через модуль-коннектор (POST /v1/apps) теперь при отсутствии на портале модуля vibecodeconnector возвращает 409 с кодом CONNECTOR_MODULE_NOT_INSTALLED и понятным сообщением «установите модуль», вместо прежнего непрозрачного 502 CONNECTOR_APP_INSTALL_FAILED. Это законное, постоянное состояние (особенно для коробки), а не временный сбой — повторять запрос бессмысленно, нужно установить модуль на портале. Остальные коды выписки не изменились.

FIX-0711-3: pacing в GET /v1/ai/quota теперь может заполняться платформой по умолчанию (поэтапная раскатка)

Платформа теперь умеет включать пейсинг (равномерное расходование AI-квоты) по умолчанию для портала — без действий администратора. Раскатка поэтапная (пилотные порталы → все порталы): пока портал не попал под платформенный default-on, поле data.pacing в ответе GET /v1/ai/quota остаётся null, как и раньше. Когда портал под default-on: mode: "ignore" — информационный режим, active: false — лимит окна не отклоняет запросы (отказ 429 ai_pacing_limited невозможен). Форма ответа не изменилась; интеграторам, уже обрабатывающим data.pacing как опциональное поле, ничего менять не нужно.

2026-07-10

NEW-0710-1: управление окнами пробуждения по расписанию (wake-schedules)

Новый CRUD-контракт на серверах Black Hole: GET/POST /v1/infra/servers/:id/wake-schedules, PATCH/DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId. Позволяет объявить одно или несколько повторяющихся окон пробуждения (cronExpr + обязательная IANA-таймзона timezone, необязательные label/lead/enabled) — платформа будит спящий сервер к нужному моменту, дальше запуск задачи делает собственный cron внутри VM.

Раскатывается постепенно и пока не работает на всех порталах — до включения на конкретном портале запрос отвечает 403 с кодом WAKE_SCHEDULE_DISABLED. Доступно только для серверов в режиме BLACKHOLE (иначе 400 BLACKHOLE_ONLY) и пока не поддержано для галактик (400 GALAXY_NOT_SUPPORTED на хосте и вложенном приложении — появится позже). Минимальный интервал между срабатываниями и лимит окон на сервер (50) заданы платформой; нарушение отвечает 400 CADENCE_TOO_LOW и 403 WAKE_SCHEDULE_LIMIT соответственно. Ответ создания/обновления дополнительно несёт поле tzWarning — предупреждение о том, что таймзона в VM могла разойтись с таймзоной окна, если сервер не переразвёртывался. PATCH заменяет окно целиком (PUT-семантика): не переданные необязательные поля сбрасываются к значениям по умолчанию — enabled→true, label/lead→пусто. Передавайте полный объект окна при обновлении.

Затронутые эндпоинты: GET|POST /v1/infra/servers/:id/wake-schedules, PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId.

NEW-0710-2: машиночитаемый код tz-предупреждения в ответах wake-schedules (`tzWarningCode`)

Ответ создания/обновления окна пробуждения (POST/PATCH /v1/infra/servers/:id/wake-schedules[/:scheduleId]) теперь дополнительно несёт поле tzWarningCode рядом с существующим текстовым tzWarning — "SINGLE_ZONE" / "MULTI_ZONE" / null (когда после мутации у сервера не осталось включённых окон). Значение — машиночитаемый эквивалент того же предупреждения, чтобы клиент мог локализовать текст сам вместо отображения английской строки tzWarning как есть. Поле аддитивное, tzWarning не меняется и остаётся для обратной совместимости.

Затронутые эндпоинты: POST|PATCH /v1/infra/servers/:id/wake-schedules[/:scheduleId].

FIX-0710-3: wake-schedules: расписание запрещено на серверах «Всегда онлайн»

POST/PATCH /v1/infra/servers/:id/wake-schedules теперь отклоняют создание или обновление окна пробуждения на сервере в режиме «Всегда онлайн» (24/7) — ответ 400 с кодом ALWAYS_ON_CONFLICT. Такой сервер работает на невытесняемом тарифе и не уходит в авто-сон, поэтому расписание пробуждения тихо нарушило бы оплаченную гарантию постоянной доступности. Обычные засыпающие серверы Black Hole и вытесняемые агенты/боты не затронуты. Отключите «Всегда онлайн», чтобы объявлять окна пробуждения.

Затронутые эндпоинты: POST /v1/infra/servers/:id/wake-schedules, PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId.

FIX-0710-4: wake-schedules теперь можно объявлять на вложенных приложениях galaxy

Было

POST/PATCH /v1/infra/servers/:id/wake-schedules отвечал 400 GALAXY_NOT_SUPPORTED для любого сервера семейства galaxy — как для самого хоста, так и для вложенных приложений (kind=GALAXY_APP).

Стало

Вложенные приложения galaxy (kind=GALAXY_APP) теперь принимаются — окно пробуждения можно объявить на конкретном приложении, и платформа разбудит его (и при необходимости — хост) к нужному моменту. Сам хост galaxy по-прежнему отвечает 400 GALAXY_NOT_SUPPORTED: расписание объявляется на приложениях, а не на хосте.

Затронутые эндпоинты: POST|GET /v1/infra/servers/:id/wake-schedules, PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId.

FIX-0710-5: sleep-now откладывает засыпание, когда пробуждение по расписанию близко

Было

POST /v1/infra/servers/:id/sleep-now всегда усыплял сервер немедленно, даже если ближайшее пробуждение по расписанию наступало через минуту — сервер сразу же просыпался обратно.

Стало

Если у сервера есть включённое расписание пробуждения и ближайшее пробуждение наступит в пределах защитного окна, вызов не усыпляет сервер и отвечает 200 с телом { "success": true, "data": { "slept": false, "reason": "WAKE_IMMINENT" } }. В остальных случаях сервер усыпляется, а планировщик разбудит его в следующее окно.

Влияние на интеграторов

Блок data с полем slept: false приходит только при отказе усыпить — проверяйте его наличие, если полагаетесь на то, что после вызова сервер обязательно засыпает. При успешном усыплении ответ состоит из одного success: true. Старые вызовы серверов без расписания работают без изменений.

FIX-0710-6: не-стриминговые chat/completions и embeddings больше не обрываются на 360 секундах

Было

Не-стриминговый (stream:false) запрос к POST /v1/chat/completions (и POST /v1/embeddings), генерация которого длилась дольше ~2 минут, стабильно обрывался на ~360 секундах с {"code":"ai_provider_unavailable","message":"This operation was aborted"} — независимо от таймаута клиента. При этом на обречённую генерацию тратилось тройное количество вычислений.

Стало

Такой запрос обрабатывается в рамках единого бюджета ~850 секунд (одна попытка на весь бюджет, без утроения нагрузки). Если генерация всё же не укладывается в бюджет, возвращается осмысленный 503 с кодом ai_provider_timeout, локализованным userMessage и подсказкой (уменьшить объём запроса либо использовать потоковый режим stream:true) — без заголовка Retry-After (таймаут не транзиентный). Обрыв соединения клиентом теперь немедленно отменяет генерацию на стороне провайдера. Стриминговый режим (stream:true) этим лимитом не затрагивался.

Было

GET /v1/requisite-presets/:presetId/fields игнорировал sort/order и любые filter[...] — всегда возвращал полный список в порядке Битрикс24. GET /v1/requisite-links и POST /v1/requisite-links/search принимали только простое равенство, а операторы ($gte, > и т.п.), sort/order и неизвестные поля молча пропускались — в результате приходила вся таблица.

Стало

Оба списка честно применяют sort/order (в том числе форму order[поле]=asc|desc) и filter. У requisite-links работают операторы сравнения ($gt/$gte/$lt/$lte, $in/$nin, префиксы >=/>). Неизвестное поле фильтра или сортировки теперь возвращает 400 (UNKNOWN_FILTER_FIELD / UNKNOWN_SORT_FIELD), логический оператор верхнего уровня ($or/$and) — 400 INVALID_FILTER_OPERATOR, а фильтр по entityId без entityTypeId — 400 MISSING_ENTITY_TYPE_ID вместо сырого «Access denied».

Влияние на интеграторов

Запросы по документированным полям продолжают работать и теперь действительно сортируются/фильтруются. Если раньше вы полагались на молчаливое игнорирование неизвестного параметра, теперь он вернёт 400 — уберите опечатку или используйте поле из ответа.

FIX-0710-8: Сортировка банковских реквизитов по id учитывает направление

Было

Запрос списка банковских реквизитов (GET /v1/bank-details) с сортировкой по id по убыванию (?sort=-id) возвращал записи по возрастанию — направление сортировки молча игнорировалось.

Стало

?sort=-id (и ?sort=id) сортирует по идентификатору в запрошенном направлении.

Влияние на интеграторов

Изменений в коде не требуется — запросы, полагавшиеся на сортировку по id, теперь возвращают ожидаемый порядок.

NEW-0710-9: GET /v1/quotes/fields — объявлены ~26 полей предложения с человеческими названиями

Схема сущности «предложения» (quotes) пополнена ~26 полями, которые Битрикс24 возвращал, но которые не были объявлены: quoteNumber, updatedBy, lastActivityBy, lastActivityTime, content, terms, leadId, storageTypeId, storageElementIds, personTypeId, webformId, lastCommunicationTime, contactIds, contacts, locationId, taxValue, actualDate, mycompanyId, utmSource/utmMedium/utmCampaign/utmContent/utmTerm, lastCommunicationCallTime/lastCommunicationEmailTime/lastCommunicationImolTime/lastCommunicationWebformTime. Теперь они видны в GET /v1/quotes/fields с читаемыми названиями (вместо служебных STORAGE_TYPE_ID/UTM_SOURCE), а их значения приводятся к типам (числа, даты в ISO) в ответах list/get. Названиями снабжены также ранее необъявленные stageId, opened, closed.

FIX-0710-10: PATCH без единого записываемого поля отклоняется явной ошибкой

Было

PATCH /v1/{entity}/:id с пустым телом — или с телом, в котором нет ни одного распознанного записываемого поля (например из-за опечатки в имени поля) — доходил до Битрикс24, который молча игнорировал такой запрос и отвечал успехом. Обёртка возвращала 200 с неизменённым объектом, и клиент считал, что правка применилась, хотя на деле ничего не менялось — риск тихой рассинхронизации, особенно у AI-агентов. Это касалось всех трёх поверхностей обновления: одиночного PATCH /v1/{entity}/:id, пакетного POST /v1/{entity}/batch и общего POST /v1/batch.

Стало

Обновление с пустым телом возвращает 400 EMPTY_UPDATE_BODY (в пакетных вызовах — ошибку элемента) на всех трёх поверхностях, до обращения к Битрикс24. Для /v1/catalog-products/:id добавлена строгая проверка: PATCH, в котором нет ни одного распознанного записываемого поля, возвращает 400 NO_RECOGNIZED_UPDATE_FIELDS. Осмысленное обновление всегда несёт хотя бы одно поле — передайте распознаваемое поле (пользовательские свойства propertyNNN и поля UF_* тоже принимаются). Сущности, у которых уже была проверка полей, ведут себя как прежде.

Дополнительно у товаров каталога в GET /v1/catalog-products/fields появились и стали доступны для явного select, фильтра и сортировки поля из контракта обновления товара — code, xmlId, sort, vatId, height, length, width, previewText, detailText и другие (раньше фильтр по ним отвечал 400 UNKNOWN_FILTER_FIELD). Надёжно фильтровать и сортировать стоит по индексируемым полям (code, xmlId, sort, vatId, размеры); по полнотекстовым (previewText, detailText) Битрикс24 фильтр может игнорировать. Ответы списков без явного select не изменились.

FIX-0710-11: leads, companies, quotes — поля дат в /fields теперь createdTime и updatedTime

Было

GET /v1/leads/fields, GET /v1/companies/fields и GET /v1/quotes/fields объявляли поля createdAt и updatedAt, хотя в ответах list/get/search Bitrix24 всегда возвращал createdTime и updatedTime — прочитать значение по имени createdAt/updatedAt было нельзя. Фильтр и сортировка при этом принимали имена createdAt/updatedAt.

Стало

/fields этих сущностей объявляют реальные ключи createdTime и updatedTime — как у contacts, invoices и items. Ключи в теле ответов те же (createdTime/updatedTime приходили всегда), но теперь значение нормализуется к ISO-8601 в UTC (2026-04-15T07:00:00.000Z) — раньше приходило в исходном формате Bitrix24 со смещением портала (2026-04-15T10:00:00+03:00). Момент времени тот же, меняется только представление.

Влияние на интеграторов

Читайте даты из createdTime и updatedTime (ключи не менялись). Если вы сравниваете строку даты побайтово или кэшируете по ней — учтите переход +03:00 → Z (тот же момент времени). В фильтре и сортировке используйте createdTime/updatedTime; прежние createdAt/updatedAt теперь возвращают 400 UNKNOWN_FILTER_FIELD (фильтр) и 400 UNKNOWN_SORT_FIELD (сортировка). На запись createdAt/updatedAt больше не отклоняются как readonly — они игнорируются как неизвестные поля (как у contacts/invoices/items); задать дату создания/изменения по-прежнему нельзя.

FIX-0710-12: сон-настройки: флип в «Всегда онлайн» теперь запрещён при активных окнах пробуждения

PATCH /v1/infra/servers/:id/sleep теперь отклоняет установку sleepAfterMinutes: null («Всегда онлайн», 24/7) на сервере, у которого есть включённые окна расписания пробуждения — ответ 400 с кодом ALWAYS_ON_CONFLICT. Это обратное направление уже существующего гейта: раньше запрещалось создавать окно пробуждения на сервере «Всегда онлайн», теперь симметрично запрещён и обратный переход — иначе сервер продолжал бы засыпать по расписанию, тихо нарушая оплаченную гарантию постоянной доступности. Удалите или отключите окна пробуждения, либо оставьте таймаут сна вместо «Никогда», чтобы включить «Всегда онлайн».

Затронутые эндпоинты: PATCH /v1/infra/servers/:id/sleep.

NEW-0710-13: placement.bind на коробочном Битрикс24 отдаёт понятный SESSION_REQUIRES_ADMIN для не-администратора

На коробочном (self-hosted) Битрикс24 привязка плейсмента через путь developer-key требует, чтобы пользователь ключа был администратором аккаунта. Раньше запрос не-администратора возвращал глухой 502 BITRIX_UNAVAILABLE.

Теперь POST /v1/placements/bind распознаёт отказ доступа со стороны Битрикс24 и возвращает 403 SESSION_REQUIRES_ADMIN с подсказкой: выполните привязку под учётной записью администратора аккаунта либо попросите администратора выдать эти права. Требование заранее видно в GET /v1/me — блок placements.bindPrerequisite для коробочных аккаунтов теперь включает код SESSION_REQUIRES_ADMIN.

FIX-0710-14: capabilities в GET /v1/me отражают режим только для чтения (READONLY)

Было

Для ключа в режиме READONLY GET /v1/me отдавал capabilities.managedBots.create, agents.create, servers.create и apps.* со значением available: true, хотя любая операция записи блокируется с 403 WRITE_BLOCKED_READONLY_KEY. Агент видел «можно создать» и упирался в отказ.

Стало

Для READONLY-ключа эти write-способности возвращаются как available: false с reason: "WRITE_BLOCKED_READONLY_KEY" и подсказкой переключить ключ в режим чтение+запись. Способности только для чтения и AI Router не меняются. Для ключей в режиме READWRITE ответ прежний.

FIX-0710-15: автор обращения без скоупа vibe:feedback снова может отвечать на своё обращение

Было

POST /v1/feedback/:id/comments отклонял автора обращения с 403 FEEDBACK_SCOPE_REQUIRED, если у ключа не было скоупа vibe:feedback — хотя ветка автора была задокументирована. Автор не мог ответить на своё же обращение в статусе AWAITING_USER, и обращение зависало.

Стало

Проверка на автора выполняется до скоуп-гейта: автор, отвечающий тем же ключом, которым создал обращение, попадает в авторскую ветку (authorType=USER, правило мяча AWAITING_USER → NEEDS_REVIEW) даже без скоупа vibe:feedback. Ключ, не являющийся автором и не имеющий скоупа, по-прежнему получает 403 FEEDBACK_SCOPE_REQUIRED.

NEW-0710-16: Пейсинг AI-квоты: поле pacing в ответе и код ошибки 429 ai_pacing_limited

Ответ GET /v1/ai/quota дополнен полем pacing — состоянием равномерного расходования месячной AI-квоты (сглаживание пиков через суточный и недельный лимит поверх общего месячного лимита; включается администратором портала в кабинете /ai). Значение null, если пейсинг выключен на платформе или не настроен для портала; иначе объект { mode, active, day, week }: mode — режим реакции на превышение (wallet/block/ignore), active — сработает ли превышение прямо сейчас в отказ (false в режиме наблюдения и всегда false при режиме ignore — окна считаются и только информируют, 429 не отдаётся), day и week — по { pctUsed, resetAt } в процентах от собственного лимита окна. Ответ по-прежнему кэшируется на 30 секунд, поэтому состояние пейсинга может отставать на этот срок.

При срабатывании суточного или недельного лимита запросы POST /v1/chat/completions, POST /v1/embeddings и POST /v1/audio/transcriptions могут вернуть 429 с телом { success: false, error: { code: "ai_pacing_limited", type: "rate_limit_exceeded", message, reason, overageDenied, resetAt, retryAfter } } и заголовком Retry-After. reason — какое окно пробито (day_window или week_window); overageDenied — причина отказа в платном превышении лимита (wallet_empty, breaker, wallet_off), либо null в режиме жёсткой блокировки. Повторять вызов раньше Retry-After/resetAt не имеет смысла — квота не станет доступнее за это время. По умолчанию пейсинг выключен — включается платформой.

FIX-0710-17: GET /v1/pages, /v1/sites и POST /v1/{pages,sites}/search теперь учитывают offset

Было

Запрос списка страниц или сайтов со смещением (GET /v1/pages?offset=50, POST /v1/pages/search с offset) возвращал ошибку 422 BITRIX_ERROR "Unknown parameter: start". Первая страница (без offset) работала.

Стало

offset для pages и sites обрабатывается корректно: возвращается запрошенное окно [offset, offset+limit), а meta.total и meta.hasMore считаются по числу строк. Менять клиентский код не нужно — вызовы без offset работают как раньше.

NEW-0710-18: /fields сущностей документов, каталогов, цен, телефонных линий, позиций корзины и лидов дополнены метаданными

GET /:entity/fields нескольких сущностей теперь несёт более полную метаданную схемы. У документов (GET /v1/documents/fields), каталогов (GET /v1/catalogs/fields), цен каталога (GET /v1/catalog-prices/fields) и телефонных линий по каждому полю добавлены человекочитаемые label и description по-русски.

У позиций корзины (GET /v1/basket-items/fields) поля weight, vatRate, measureCode, measureName, dimensions, productXmlId, catalogXmlId помечены флагом nullable — они могут прийти пустыми. У лидов (GET /v1/leads/fields) тем же флагом помечены secondName, sourceDescription, comments, а также объявлены ранее неописанные поля originatorId, dateClosed, lastCommunicationTime и метки utmSource/utmMedium/utmCampaign/utmContent/utmTerm — теперь по ним работают фильтр и сортировка, а dateClosed нормализуется к ISO-8601.

У сайтов лендингов объявлены измерения для группировки, поэтому POST /v1/sites/aggregate с groupBy (type, active, deleted, lang, tplId, domainId, createdById, modifiedById) больше не отвечает Available: .. У документов агрегация отключена (все числовые поля — идентификаторы): POST /v1/documents/aggregate возвращает 404.

Прежние вызовы работают без изменений — это дополнительные метаданные полей.

NEW-0710-19: Идемпотентное создание сервера — заголовок Idempotency-Key

POST /v1/infra/servers теперь принимает необязательный заголовок Idempotency-Key для создания отдельного сервера (standalone). Повторный запрос с тем же ключом — например, после потерянного ответа или разрыва сети — не создаёт второй сервер: он возвращает тот же самый сервер, что и первый запрос, со статусом 201 и заголовком ответа Idempotent-Replayed: true. Ключ — строка 1–255 символов из набора [A-Za-z0-9_.:-]; область действия — ваш API-ключ.

При повторе одноразовые SSH-учётные данные (ssh.privateKey / ssh.password) НЕ выдаются повторно — в теле ответа они null и добавлено поле note с пояснением. Сохраните учётные данные из ответа первого создания.

Новые коды ошибок: 400 INVALID_IDEMPOTENCY_KEY (ключ не проходит валидацию), 400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION (ключ вместе с graduateFrom для выделенного сервера не поддерживается), 409 IDEMPOTENCY_KEY_ALREADY_USED (ключ уже использован для сервера, который затем был удалён), 409 IDEMPOTENCY_CONCURRENT_RETRY (параллельный запрос с тем же ключом ещё выполняется — повторите чуть позже).

Заголовок учитывается только для standalone-серверов. На порталах с размещением в галактике корректный ключ игнорируется без ошибки, и защита от повторного создания на этот путь не распространяется.

Дополнительно: ответ создания теперь возвращает каноническое имя сервера (с суффиксом при разрешении конфликта имён), а не имя из запроса — при совпадении имён это ранее расходилось.

NEW-0710-20: локализованное сообщение при отказе переключения в режим OPEN

Ответы PATCH /v1/infra/servers/:id/mode с кодами OPEN_MODE_DISABLED (режим OPEN выключен на уровне платформы) и OPEN_MODE_NOT_ALLOWED (режим OPEN запрещён политикой портала) теперь дополнительно несут поле error.userMessage — локализованную человекочитаемую формулировку с подсказкой использовать Deploy API как штатную замену прямого SSH. Поле аддитивное: error.message (английская техническая строка), error.code и HTTP-статус не меняются. Совпадает по форме с уже существующим error.userMessage у отказа OPEN_MODE_REQUIRES_COMMERCIAL. Текст userMessage зависит от локали владельца ключа.

BC-0710-21: Поля конфигураций открытых линий приведены к camelCase и описаны в /fields

Поддержка старого формата до: 10.01.2027

Было

GET /v1/openline-configs, GET /v1/openline-configs/{id} и POST /v1/openline-configs/search возвращали большинство полей конфигурации в «родном» для Bitrix24 виде — в верхнем регистре через подчёркивание (CRM_CREATE, WELCOME_MESSAGE, QUEUE_TIME и другие). Справка GET /v1/openline-configs/fields описывала только 6 полей, поэтому остальные не были видны для программного обнаружения.

Стало

Все поля конфигурации приведены к единому camelCase (crmCreate, welcomeMessage, queueTime и так далее), а /fields описывает полный набор полей с человеческими названиями (label) и описаниями (description). Фильтрация и сортировка по новым camelCase-именам работают. При записи (create/update) по-прежнему принимаются оба регистра — прежние вызовы с верхним регистром в теле не ломаются.

Что делать интеграторам

Читать поля ответа по camelCase-именам: config.crmCreate вместо config.CRM_CREATE. Соответствие имён — прямая транслитерация из верхнего регистра в camelCase (WELCOME_BOT_ID → welcomeBotId, WORKTIME_TO → workTimeTo, LINE_NAME → name). Полный перечень новых имён — в справке /fields.

NEW-0710-22: Добавлен GET /v1/warehouses/fields — схема полей склада

Появился эндпоинт GET /v1/warehouses/fields, возвращающий схему 19 полей склада: для каждого поля — тип (type), признак «только для чтения» (readonly), человеческое название (label) и описание (description). Склады — кастомный роут (без сущностной схемы), поэтому раньше у них не было справки полей, которая есть у автогенерируемых сущностей. Ответ — { success: true, data: { fields: { … } } }. Требуется скоуп catalog.

FIX-0710-23: публикация приложения восстанавливается при рассинхроне плейсмента

Было

При публикации (POST /v1/apps/:id/publish) или обновлении плейсментов (PATCH /v1/apps/:id), если плейсмент был зарегистрирован на стороне Bitrix24, но отсутствовал в приложении (дрейф после снятия с публикации), привязка падала с ошибкой «Handler already binded» и плейсмент оставался несинхронизированным.

Стало

При такой ошибке платформа один раз снимает устаревшую привязку и повторяет её — плейсмент синхронизируется автоматически. Восстановление срабатывает только на подтверждённом конфликте, поэтому «живой» плейсмент никогда не снимается по ошибке; плейсменты, которым нужны непереносимые OPTIONS (чат-виджеты, фоновый обработчик), из авто-восстановления исключены и по-прежнему сообщают предупреждение.

FIX-0710-24: storage: sha256 объекта заполняется при прямой загрузке

Было

Поле sha256 в ответе на загрузку объекта хранилища всегда было null для пользовательских объектов, хотя схема описывала его как «вычисляется при загрузке».

Стало

При прямой загрузке (POST /v1/storage/objects/upload, файлы до 10 МБ) sha256 теперь содержит SHA-256-хэш содержимого объекта. По нему можно проверять целостность и находить дубликаты (одинаковое содержимое — одинаковый хэш). Для presigned- и multipart-загрузок байты идут напрямую в хранилище мимо платформы, поэтому там sha256 пока остаётся null.

FIX-0710-25: поле pacing.active в GET /v1/ai/quota больше не сообщает об активном лимите при нулевой квоте

Было

При включённом равномерном расходовании на портале с нулевой месячной квотой (жёсткая блокировка через переопределение monthlyVibes = 0 или ещё не инициализированный расход) поле data.pacing.active возвращало true — хотя отказ 429 ai_pacing_limited в этом состоянии структурно невозможен: запросы отклоняются месячным лимитом, а не оконным.

Стало

data.pacing.active возвращает true только когда превышение дневного или недельного окна действительно может привести к 429 ai_pacing_limited. При нулевой квоте поле честно отдаёт false. Форма ответа не изменилась; клиентам, строившим backoff-логику по active, действий не требуется — сигнал стал точнее.

2026-07-09

FIX-0709-1: транзиентная перегрузка БД теперь отдаёт 503 с Retry-After вместо 500

Было

В редкой форме кратковременной перегрузки БД (исчерпание коннектов) часть запросов (включая POST /v1/infra/servers) возвращала 500.

Стало

Такие запросы возвращают 503 с кодом POOL_EXHAUSTED и заголовком Retry-After. Ошибка транзиентная — повтори запрос с задержкой (backoff).

Влияние на интеграторов

Клиенты, повторяющие запросы при 5xx, теперь должны обрабатывать 503 и уважать Retry-After. POST /v1/infra/servers неидемпотентен — повтор может создать дубль сервера, поэтому повторяйте с backoff, а не немедленно.

FIX-0709-2: placements/bind честнее сообщает о необходимости подписки Маркетплейса

Было

При привязке плейсмента через ключ приложения на портале без активной подписки «BitrixGPT + Маркетплейс» Bitrix24 отвечал отказом доступа, а POST /v1/placements/bind возвращал непрозрачный 502 BITRIX_UNAVAILABLE без указания причины. Подписка требуется на пути через ключ разработчика независимо от коммерческого тарифа, но GET /v1/me не сообщал об этом предусловии заранее.

Стало

Отказ по подписке теперь классифицируется: POST /v1/placements/bind возвращает 403 с кодом B24_MARKET_SUBSCRIPTION_REQUIRED (или B24_MARKET_TRIAL_USED, если демо уже использован), понятным userMessage и ссылкой на оформление в details.upgradeUrl. У ключа приложения в GET /v1/me добавлен блок placements.bindPrerequisite — он заранее описывает предусловие Bitrix24 (путь через ключ разработчика требует активной подписки Маркетплейса, устаревший OAuth-путь — коммерческого тарифа) и перечисляет коды ошибок. Прочие отказы привязки (неизвестный clientId, устаревший embedding) по-прежнему возвращают 502 BITRIX_UNAVAILABLE.

Влияние на интеграторов

Менять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал 502 BITRIX_UNAVAILABLE при привязке, стоит дополнительно ловить 403 B24_MARKET_SUBSCRIPTION_REQUIRED / B24_MARKET_TRIAL_USED и подсказывать пользователю оформить подписку на портале.

FIX-0709-3: bindPrerequisite в GET /v1/me учитывает регион портала

Было

Блок placements.bindPrerequisite у ключа приложения описывал предусловие привязки одинаково для всех порталов: subscriptionRequired: true, рецепт «попросите администратора активировать подписку Маркетплейса» и список кодов с B24_MARKET_SUBSCRIPTION_REQUIRED / B24_MARKET_TRIAL_USED. На порталах с тарифной моделью доступа отказ привязки приходит с кодом INT_TARIFF_REQUIRED, а подписки там нет — предложенный рецепт был невыполним.

Стало

Блок зависит от региона портала. На порталах с подписочной моделью доступа он прежний. На порталах с тарифной моделью subscriptionRequired равен false, текст note описывает требование коммерческого тарифа Битрикс24, а errorCodes содержит INT_TARIFF_REQUIRED и BITRIX_UNAVAILABLE — только те коды, которые портал действительно может получить.

Влияние на интеграторов

Успешные вызовы не затронуты. Если вы читали errorCodes из bindPrerequisite как полный перечень, учтите, что теперь он сужен до достижимых на конкретном портале кодов. Сами коды и поведение POST /v1/placements/bind не менялись.

BC-0709-4: Поля CRM, задач и лендингов приведены к реальному контракту Битрикс24

Поддержка старого формата до: 09.01.2027

Было

GET /v1/deal-categories возвращал isLocked строкой "Y"/"N", а GET /v1/payments — paySystemIsCash строкой "Y"/"N". GET /v1/leads в каждом ответе отдавал служебное поле searchContent (внутренний полнотекстовый индекс Битрикс24). Поля paySystemXmlId, dateMarked, dateResponsibleId у платежей приходили как есть, без нормализации даты.

Стало

isLocked (воронки) и paySystemIsCash (платежи) теперь имеют тип boolean (true/false). searchContent в ответах GET /v1/leads больше не отдаётся. У платежей добавлены задекларированные поля paySystemXmlId (строка), dateMarked и dateResponsibleId (даты нормализованы в единый формат ISO с суффиксом Z). У задач добавлены changedBy/closedBy/statusChangedBy (только для чтения — попытка записать возвращает 400 READONLY_FIELD). GET /v1/currencies/fields отдаёт человекочитаемые label для полей и поле lang; GET /v1/deal-categories/fields — label для полей; GET /v1/sites/fields — флаг nullable у полей, которые могут прийти пустыми.

Что делать интеграторам

Читать isLocked и paySystemIsCash как boolean, а не сравнивать со строкой "Y". Если код опирался на поле searchContent у лидов — перестать: оно было служебным и не задокументированным.

FIX-0709-5: создание сущности с пустым телом отклоняется явной ошибкой

Было

Создание сущности с пустым телом (или вовсе без тела, но с заголовком Content-Type: application/json) доходило до Битрикс24 и молча создавало сущность со значениями по умолчанию — включая сделки, лиды, контакты, компании, счета и смарт-процессы. Повторный вызов или клиент без тела так плодил мусорные записи в CRM. Это касалось всех трёх поверхностей создания: одиночного POST /v1/{entity}, пакетного POST /v1/{entity}/batch и общего POST /v1/batch.

Стало

Такой запрос возвращает 400 EMPTY_CREATE_BODY (в пакетных вызовах — ошибку элемента) до обращения к Битрикс24, на всех трёх поверхностях. Осмысленное создание всегда несёт хотя бы одно поле — передайте нужные поля в теле запроса. Сущности, у которых уже есть проверка обязательных полей, ведут себя как прежде.

FIX-0709-6: снятие блокировки сервера принимает пустое тело JSON

Было

DELETE /v1/infra/servers/:id/lock с заголовком Content-Type: application/json и пустым телом возвращал 400 (пустое JSON-тело). Чтобы снять зависшую блокировку, приходилось слать явное {} — не зная этого, клиент упирался в тупик.

Стало

Пустое тело при этом заголовке принимается как {}; запрос без тела отрабатывает штатно и снимает блокировку. Явное {} по-прежнему работает.

FIX-0709-7: события портала теперь будят спящее galaxy-приложение

Было

Событие Битрикса, отправленное на подписку спящего galaxy-приложения, не будило его — доставка уходила в повторные попытки и после их исчерпания терялась.

Стало

Платформа будит спящее galaxy-приложение при доставке события и доставляет его после подъёма — как для обычного сервера.

NEW-0709-8: camelCase-ключи внутри communications при создании дела

POST /v1/activities теперь принимает вложенные ключи элементов communications в camelCase (type, value, entityTypeId, entityId) — единообразно с остальным API. Раньше вложенные ключи принимались только в ВЕРХНЕМ регистре Bitrix24 (TYPE, VALUE, ENTITY_TYPE_ID, ENTITY_ID), а camelCase-форма молча отбрасывалась — communications: [{ "type": …, "value": … }] возвращал 422 «COMMUNICATIONS is not defined or invalid», тогда как [{ "TYPE": …, "VALUE": … }] создавал дело. ВЕРХНИЙ регистр по-прежнему работает; если в одном объекте заданы обе формы одного ключа, приоритет у ВЕРХНЕГО регистра.

FIX-0709-9: снятие с публикации убирает вкладку по всем обработчикам приложения

Было

POST /v1/apps/:id/unpublish снимал placement только по обработчику, совпадающему с ожидаемым платформенным адресом. Если вкладка была привязана к техническому адресу самого приложения, Битрикс24 не находил её и не удалял — вкладка оставалась висеть в карточке CRM, и через API её уже нельзя было убрать.

Стало

Снятие с публикации теперь убирает placement по всем обработчикам приложения, включая привязанные к техническому адресу сервера — осиротевшая вкладка исчезает.

NEW-0709-10: Самоописание ключей в GET /v1/guide

Ответ GET /v1/guide дополнен блоком data.keysAuth. Он описывает два эндпоинта самоописания — GET /v1/me и GET /v1/guide — и содержит ссылки на документацию: контракт ответа каждого из них, режим доступа ключа и общее описание типов ключей.

Оба эндпоинта работают по одному заголовку X-Api-Key, токен сессии для них не нужен.

Поле аддитивное: существующие клиенты не затронуты.

Затронутые эндпоинты: GET /v1/guide

FIX-0709-11: GET /v1/{entity}/:id теперь учитывает ?select=

Проекция полей ?select= на чтении одной записи по id раньше игнорировалась: ответ всегда приходил со всеми полями, хотя /v1/me заявляет, что ?select= работает «на списке, чтении по id и POST /search». Теперь чтение по id проецирует ответ так же, как список и поиск, — приводя поведение в соответствие с задокументированным.

Было

GET /v1/leads/42?select=id,title возвращал полную запись (все поля).

Стало

GET /v1/leads/42?select=id,title возвращает только id и title. Поддерживаются формы через запятую (?select=id,title), массивом (?select[]=id&select[]=title) и с индексами (?select[0]=id&select[1]=title); id включается в ответ всегда. Неизвестное имя поля молча пропускается — это не ошибка. При одновременном ?select= и ?include= связанная сущность в ответе сохраняется. Индексная форма (?select[0]=…) раньше возвращала 500 и на списке GET /v1/{entity} — теперь тоже проецирует корректно.

FIX-0709-12: /fields сущностей заказов, позиций корзины, пресетов реквизитов и шаблонов документов приведены к реальному контракту B24

Было

GET /:entity/fields (и генерируемая по нему OpenAPI-схема) объявлял поля, которые Bitrix24 не возвращает: provider у шаблонов документов; reserved, sumPaid, dateBill, datePayBefore, datePaid, empPaidId, userEmail, userName на верхнем уровне заказа; module, fUserId, lid, dateRefresh, subscribe, reserved, reserveQuantity у позиций корзины; originatorId у пресетов реквизитов. Фильтрация и сортировка по этим полям молча не срабатывали. При этом реально приходящие поля не были объявлены: requisiteLink у заказа, type/properties/reservations у позиции корзины. Пресеты реквизитов принимали countryId/entityTypeId на обновление, где Bitrix24 их молча игнорирует.

Стало

Несуществующие поля убраны из /fields и OpenAPI. Реально приходящие поля объявлены: у заказа — requisiteLink (объект requisiteId/bankDetailId/mcRequisiteId/mcBankDetailId, только чтение); у позиции корзины — type, properties, reservations (только чтение). У пресетов реквизитов countryId и entityTypeId помечены как задаваемые только при создании: обновление возвращает 400 READONLY_FIELD вместо тихого игнорирования.

Влияние на интеграторов

Ответы list/get не меняются — убранные поля и так никогда не приходили. Если запрос фильтровал или сортировал по убранному полю, теперь он вернёт 400 — используйте реальные поля из /fields (например, даты и суммы оплаты у заказа лежат внутри массива payments, а не на верхнем уровне). Обновление countryId/entityTypeId у пресета реквизитов теперь явно отклоняется — задавайте эти поля только при создании.

Затронутые эндпоинты: GET /v1/orders/fields, GET /v1/basket-items/fields, GET /v1/requisite-presets/fields, GET /v1/doc-templates/fields

NEW-0709-13: GET /:entity/fields отдаёт человекочитаемые label и описания для сделок, лидов, счетов, дел, справочников и комментариев таймлайна

Ответ GET /v1/deals/fields, /v1/leads/fields, /v1/invoices/fields, /v1/activities/fields, /v1/statuses/fields и /v1/timelines/fields теперь несёт по каждому полю человекочитаемые label и description по-русски. У полей со служебными кодами добавлены словари enum: у сделок — stageSemanticId (P — в работе, S — успех, F — провал); у дел — typeId, direction, priority, status, notifyType и descriptionType. Прежние вызовы работают без изменений — это дополнительные поля метаданных, форма ответа не меняется.

FIX-0709-14: POST /v1/batch — единый формат ошибок под-вызовов и totals только для list/search

Было

Ошибка Bitrix24 внутри успешного 200-ответа POST /v1/batch (например, get несуществующего элемента) попадала в data.errors[<id>] в исходной форме Bitrix24 { error, error_description } — не в общем для V1 конверте { code, message }, который используют ошибки валидации и любой другой ответ API. Поле data.totals[<id>] при этом заполнялось для любого действия, включая get/create/update/delete, где одиночное число рядом с единственной записью не имеет смысла.

Стало

Ошибка под-вызова приводится к { code, message } (error → code, error_description → message), как у остальных ошибок. data.totals[<id>] заполняется только для действий list и search — там, где счётчик совпадений реально имеет смысл.

FIX-0709-15: GET /v1/openline-configs — нормализация пустых значений в ответе

Было

Ответы GET /v1/openline-configs и GET /v1/openline-configs/:id отдавали служебные поля в неудобных для клиента формах: KPI_FIRST_ANSWER_LIST, KPI_FURTHER_ANSWER_LIST, DEFAULT_OPERATOR_DATA приходили как null (на них падал .map/.length); AUTO_CLOSE_TEXT для незаданного значения приходил как "" в карточке и как null в списке; WORKTIME_HOLIDAYS/WORKTIME_DAYOFF для пустого набора приходили как [""] (массив с одной пустой строкой).

Стало

Списки нормализованы: null → []. AUTO_CLOSE_TEXT приведён к единому null для пустого значения и в списке, и в карточке. WORKTIME_HOLIDAYS/WORKTIME_DAYOFF для пустого набора приходят как []. Список и карточка теперь отдают одинаковую форму этих полей.

FIX-0709-16: GET /v1/users/fields отдаёт возможные значения (items) для UF-полей-перечислений

Было

Пользовательское поле-перечисление (UF_USR_* типа «список») приходило в GET /v1/users/fields как { "type": "string", "label": "…" } — без списка возможных значений. Причина: метод user.fields возвращает у UF-полей только подпись, без типа и вариантов, поэтому перечисление было неотличимо от строки.

Стало

Такое поле приходит с настоящим типом и списком вариантов: { "type": "enumeration", "label": "…", "items": [ { "ID": "…", "VALUE": "…", "DEF": "…", "XML_ID": "…" }, … ] }. Значения дочитываются из user.userfield.list — для этого у ключа должен быть скоуп user.userfield; если он не выдан, поле по-прежнему отдаётся с подписью, но без items (мягкая деградация). Заодно у остальных UF-полей (money, date и т. п.) в ответе появляется их настоящий тип вместо string.

FIX-0709-17: подсказка hint при пустой очереди событий появляется по факту устойчивой пустоты

Было

GET /v1/bots/:botId/events увеличивал счётчик пустых ответов ровно на каждый запрос, и поле hint появлялось строго после пятого подряд пустого ответа. Число в тексте подсказки совпадало с количеством сделанных запросов.

Стало

Счётчик пустых ответов обновляется периодически, а не на каждый запрос, поэтому hint появляется после устойчивой пустоты очереди — при рекомендованном интервале опроса 2–5 секунд спустя примерно пару минут непрерывно пустого опроса. Число N в тексте отражает количество зафиксированных периодов пустоты, а не точное количество сделанных запросов. Правило «доставлено событие → счётчик и подсказка сбрасываются» не изменилось.

Влияние на интеграторов

Менять код не нужно. Если вы опирались на появление hint строго на пятом запросе или трактовали N как точное число запросов — используйте persisted и наличие событий как основной сигнал, а hint как диагностическую подсказку.

2026-07-08

FIX-0708-1: GET /v1/doc-templates и POST /search честят order и offset

Было

Параметры order и offset на GET /v1/doc-templates и POST /v1/doc-templates/search молча игнорировались: список всегда возвращался в порядке возрастания по id, а offset не смещал окно выборки. Причина — метод Bitrix24 отдаёт шаблоны объектом с ключами-id, и заданный порядок терялся при разворачивании ответа.

Стало

Сортировка (order[поле]=asc|desc, в том числе по нескольким полям) и постраничная выборка (offset/limit) применяются на стороне Vibecode: набор шаблонов вытягивается полностью, сортируется и режется по запрошенному окну. total и hasMore считаются от фактически собранного набора.

Влияние на интеграторов

Тем, кто полагался на неявный порядок «по возрастанию id» при offset=0 без сортировки, менять ничего не нужно — это остаётся поведением по умолчанию. POST /v1/doc-templates/batch (batch-list) не затронут. Строковый порядок (name, region) — побайтовый, без учёта локали.

NEW-0708-2: GET /v1/apps/:id/sources — новое поле linkedServerSources

GET /v1/apps/:id/sources теперь дополнительно возвращает поле linkedServerSources — версии исходников, сохранённые под сервером (через POST /v1/infra/servers/:id/sources или авто-сохранение при деплое), сгруппированные по серверу, каждая со своим serverContext. Такие версии раньше не попадали в этот ответ, если сохранялись под личным ключом, — теперь они видны.

Поле аддитивное: versions, totalVersions, currentVersionId и totalSizeBytes не изменились и по-прежнему перечисляют только версии, привязанные к приложению. Рядом приходят linkedServerSourcesTruncated (признак усечения при очень большом числе версий) и linkedServerHint с указателем на GET /v1/infra/servers/:serverId/sources — авторитетный полный список и скачивание этих версий. Секция заполняется для автора приложения (личный ключ) и администратора портала; при вызове ключом OAuth-приложения она пуста, а при ?sha256=-пробе не вычисляется.

NEW-0708-3: поле preemptible в ответе списка тарифов серверов

Ответ GET /v1/infra/providers/:id/plans теперь формально описывает поле preemptible у каждого тарифа. Вытесняемый тариф дешевле, но облако принудительно перезапускает такую машину примерно раз в сутки — он не подходит для непрерывных 24/7-нагрузок. Для сервера, агента или бота, которые должны работать без перерывов, выбирайте невытесняемый тариф (preemptible равно false или отсутствует).

Поле уже отдавалось в ответе рантаймом — эта запись фиксирует его в OpenAPI и документации; менять существующие интеграции не требуется.

NEW-0708-4: GET /v1/models/:id теперь показывает цену преемника у снятых моделей и поле replaced_by

Для модели, снятой с публикации, запрос детали по идентификатору теперь возвращает поле replaced_by с идентификатором модели-преемника, на которую фактически уходят вызовы, а поле pricing показывает цену этого преемника — ту, по которой запрос и тарифицируется. Раньше pricing показывал собственную нулевую цену снятой строки, из-за чего модель выглядела бесплатной, хотя вызовы обслуживал платный преемник. Обычные модели и прежние вызовы не меняются.

FIX-0708-5: автопагинация списков сохраняет порядок строк при limit выше 550

Было

Списочные запросы с автопагинацией — GET /v1/{entity}?limit=… и POST /v1/{entity}/search — при limit выше ~550 возвращали строки с нарушенным порядком: внутренние страницы выдачи склеивались не в том порядке, в котором их отдал Битрикс24, поэтому параметр order на итоговом массиве не соблюдался. Если записей было больше, чем limit, обрезка окна могла выбросить строки из середины отсортированной выборки, оставив более поздние.

Стало

Страницы склеиваются строго в порядке выдачи Битрикс24: строки приходят в заказанной сортировке при любом limit, а обрезка по limit больше не выбрасывает строки из середины выборки из-за неверного порядка склейки.

Влияние на интеграторов

Менять ничего не нужно. Если вы пересортировывали большие выборки на своей стороне как обходной путь — это больше не требуется.

FIX-0708-6: автопагинация больше не теряет молча страницу при сбое пакетного подзапроса

Было

При limit > 50 список собирается пакетными подзапросами по 50 записей. Если Битрикс24 отклонял один подзапрос (чаще всего по лимиту запросов — QUERY_LIMIT_EXCEEDED), его страница молча выпадала из середины выборки: ответ оставался 200, в данных образовывалась необнаружимая дыра в 50 записей (например, записи 1–200 и 251–600 без 201–250), а meta.total и meta.hasMore выглядели непротиворечиво.

Стало

Для списков, обычного поиска, пакетных подвызовов и агрегаций результат — всегда непрерывный префикс выборки: записи после сбойной страницы отбрасываются, meta.hasMore остаётся true, и в ответе появляется meta.pageErrorSample { code, message } с причиной сбоя — по образцу meta.windowErrorSample оконного поиска. Поле добавлено в ответы списков (например GET /v1/deals), в POST /v1/{entity}/search (например сделки), в meta подвызовов POST /v1/batch и в data.meta агрегаций POST /v1/{entity}/aggregate (там оно объясняет, почему recordsProcessed меньше totalRecords). В оконном поиске (широкий диапазон дат) набор собирается из окон, поэтому при потере страницы внутри одного окна ответ может недосчитаться хвоста этого окна — признак неполноты там именно meta.pageErrorSample, а не meta.hasMore. Во всех случаях поле появляется, только если итоговая страница действительно короче limit: полный ответ ложным сигналом не помечается. Неполный ответ не кэшируется: повторный запрос сразу идёт в Битрикс24.

Влияние на интеграторов

Менять клиентский код не нужно: выборки, которые раньше могли содержать незаметную дыру, теперь корректны, а причина недобора видна в meta.pageErrorSample. Дочитать остаток можно повторным запросом с offset, равным сумме исходного offset и числа полученных записей, — кроме оконного поиска по широкому диапазону дат (там offset не поддерживается: сузьте диапазон или повторите запрос позже).

BC-0708-7: структурированный вывод: обрезанный или пустой ответ теперь возвращает 422, а не пустой 200

Поддержка старого формата до: 08.07.2026

Было

POST /v1/chat/completions со response_format (json_object или json_schema) при обрыве генерации мог вернуть 200 с content: null (или обрезанной, непарсимой строкой JSON) и предупреждением, которое клиенты не замечали. Чаще всего это случалось на моделях рассуждения: фаза рассуждения расходовала весь бюджет max_tokens до того, как модель писала JSON. Ответ выглядел успешным, но разобрать его было нельзя.

Стало

Такой запрос возвращает 422 с code: "structured_output_truncated", полями finishReason, suggestedMaxTokens (рекомендованный увеличенный max_tokens для повтора) и param: "max_tokens". В потоковом режиме перед data: [DONE] приходит служебный кадр {"error":{"code":"structured_output_truncated"}} — читайте поток до [DONE]. Дополнительно: для бесплатных моделей рассуждения при слишком маленьком max_tokens платформа поднимает бюджет до безопасного минимума и помечает успешный ответ предупреждением MAX_TOKENS_RAISED. Обрезанная попытка по-прежнему расходует и тарифицирует токены.

Что делать интеграторам

Обрабатывайте 422 structured_output_truncated в ветке ошибок и повторяйте запрос с бóльшим max_tokens (можно взять значение из suggestedMaxTokens). Для строго-детерминированного JSON задавайте max_tokens с запасом или используйте обычную (не «thinking») модель.

2026-07-07

FIX-0707-1: smart-processes: linkedUserFields принимает Y/N и булевы значения

Было

POST /v1/smart-processes и PATCH /v1/smart-processes/:entityTypeId с linkedUserFields работали только когда значение флага было строго "true"/"false". Значение в конвенции "Y"/"N" (как у всех остальных полей смарт-процесса) или булево true/false молча игнорировалось: запрос возвращал success: true, но отображение в пользовательском поле не включалось.

Стало

Значения linkedUserFields нормализуются так же, как вложенный флаг relations[].isChildrenListEnabled: true/"Y"/"yes"/1 → включено, false/"N"/"no"/0 → выключено. Прежние вызовы с "true"/"false" продолжают работать без изменений.

Влияние на интеграторов

Ничего менять не нужно — вызовы, которые раньше «молча не срабатывали» с "Y", теперь применяются корректно.

BC-0707-2: Нормализация вложенных полей карточки заказа

Поддержка старого формата до: 06.01.2027

Было

GET /v1/orders/{id} отдавал вложенные массивы clients, payments, basketItems в сыром виде Bitrix24: булевы поля строками "Y" и "N" (payments[].paid, clients[].isPrimary, basketItems[].vatIncluded и другие), даты внутри payments и basketItems со смещением +03:00, а поле companyId со значением 0, когда компания не задана. Поле accountNumber при создании и обновлении молча игнорировалось.

Стало

Вложенные Y/N-поля приходят как boolean (true или false); вложенные даты нормализованы к UTC (оканчиваются на Z); companyId при отсутствии компании приходит null вместо 0; accountNumber стал полем только для чтения — попытка задать его при создании или обновлении возвращает 400 с кодом READONLY_FIELD.

Что делать интеграторам

Читать вложенные Y/N-поля как boolean, а не сравнивать со строкой "Y"; трактовать null вместо 0 как признак «компания не задана»; не передавать accountNumber в теле создания и обновления — номер присваивается автоматически.

FIX-0707-3: Спека /v1/openapi.json приведена к фактическому рантайму

Было

Машинная OpenAPI-спека генерировалась из статической метаданной сущностей и расходилась с реальными ответами: ни одно поле не помечено nullable, вложенные массивы карточки заказа типизировались как строка, у списочных методов не описаны filter и select, у операций объявлены только успешные коды и 403.

Стало

Спека теперь отражает контракт. Nullable-поля выводятся в форме type: ["<тип>", "null"]. Объектные и массив-объектов поля типизируются честно, включая вложенные clients, payments, basketItems, propertyValues у GET /v1/orders/{id}. На списочных методах описаны query-параметры filter и select. Операции несут стандартные коды ошибок 400, 401, 404, 422 в едином конверте { success:false, error:{ code, message } }. Схемы *Input объявляют обязательные при создании поля. Дополнительно GET /v1/orders/fields отдаёт clients как массив вместо object. SDK, сгенерированный из спеки, получает корректную типизацию.

BC-0707-4: /search: авто-оконный поиск при полном отказе отдаёт настоящую ошибку Bitrix24

Поддержка старого формата до: 07.09.2026

Было

Любой неуспешный авто-оконный POST /v1/{entity}/search возвращал 502 { "error": { "code": "WINDOWED_SEARCH_FAILED" } } с общим советом «добавьте autoWindow:false».

Стало

Ответ совпадает с тем же запросом на узком диапазоне — реальный код и сообщение: отклонённое поле фильтра/сортировки → 400 UNKNOWN_FILTER_FIELD / 400 INVALID_PARAMS; нет прав → 403; лимит запросов / перегрузка очереди → 429 + Retry-After; таймаут → 503; недоступность Bitrix24 → 502 BITRIX_UNAVAILABLE. Частичный отказ окон (статус 200) теперь несёт meta.windowErrorSample { code, message }.

Что делать интеграторам

Если вы ветвились на error.code === "WINDOWED_SEARCH_FAILED" (например, чтобы повторить с autoWindow:false) — ветвитесь на реальные коды. Обходной путь autoWindow:false остался; он полезен там, где действительно помогает (подсказка 429 QUEUE_TIMEOUT называет его). Ответ полного отказа больше не несёт блок meta (autoWindowed/windowCount/windowErrors) — сигнал теперь в самом коде/сообщении ошибки; meta.windowErrorSample остаётся на частичном отказе (статус 200).

FIX-0707-5: списки на портале без модуля стабильно отдают 409, а не 429

Было

На портале, где модуль «Универсальные списки» не включён, вызовы /v1/lists отдавали понятный 409 LISTS_MODULE_NOT_ENABLED только для первых нескольких запросов. После этого встроенная защита от циклов ошибок срабатывала и все последующие вызовы возвращали 429 ERROR_LOOP_DETECTED — реальная причина (модуль не подключён) переставала быть видна.

Стало

Сигнал «метод недоступен на портале» больше не учитывается защитой от циклов, поэтому вызовы lists.* на портале без модуля стабильно возвращают 409 LISTS_MODULE_NOT_ENABLED при любом числе повторов. Ответ остаётся действенным: подключите модуль на портале и повторите запрос.

NEW-0707-6: Подсказка error.hint на 400 при создании сервера без source и без provider/plan/region

POST /v1/infra/servers при 400 INVALID_REQUEST из-за отсутствующих provider/plan/region (и отсутствующего source) на портале с galaxy-размещением теперь дополнительно возвращает объект error.hint с полями reason (почему запрос отклонён на этом портале), recovery (рекомендованный one-shot путь и рабочая двухшаговая альтернатива) и example (готовый скелет тела one-shot запроса). Поля error.code и error.message не изменились — подсказка строго аддитивна; на порталах без galaxy-размещения ответ прежний, без hint.

Подсказка также возвращается в ветке 400 RUNTIME_PARAM_REMOVED (создание с runtime, но без source, на портале с galaxy-размещением), а тело с placement: "dedicated" получает отдельный вариант подсказки — под выделенный сервер, с сохранением намерения и добавлением недостающего tuple provider/plan/region, без увода в galaxy-контейнер.

FIX-0707-7: Galaxy-чек-лист деплоя в /v1/me приведён к фактическому контракту

Было: шаг 2 чек-листа deployment.galaxyApp.checklist предписывал POST /v1/infra/servers { name } без source и без provider/plan/region — такой вызов всегда завершался 400 INVALID_REQUEST. Правило CREATE не объясняло, что для двухшагового пути обязателен полный набор provider/plan/region, а newAppPlacement.note обещала выделенный standalone-VM там, где создание возвращает galaxy-слот с next: "deploy". Favicon-гайд направлял в этот же неработающий порядок; окно уборки недеплоенного слота указывалось как «~12-20 мин» при фактических ~20-25.

Стало: рекомендованный путь — один вызов POST /v1/infra/servers { name, source, runtime, start } (provider/plan/region опускаются). Двухшаговый путь описан правдиво: создание без source требует полный provider/plan/region (значения для galaxy информационны — приложение наследует хост), на galaxy-размещении возвращает слот с next: "deploy"; недеплоенный слот убирается в ERROR после ~20 мин (проверка каждые 5 мин). Favicon: основной путь — собственный /icon.svg в архиве (id не нужен); платформенный URL — альтернатива через two-step или re-deploy. Шаг опроса статуса получил ветку status=error → provisionError/buildLog → re-deploy.

Влияние на интеграторов: агенты, следующие чек-листу, деплоят с первого вызова. Поведение эндпоинтов не менялось — обновлены только тексты /v1/me и описание в /v1/openapi.json; существующие интеграции продолжают работать без изменений.

NEW-0707-8: AI-квота компании доступна через API

Новый эндпоинт GET /v1/ai/quota возвращает состояние месячной AI-квоты портала: процент израсходованного лимита (pctUsed, честное значение — при перерасходе больше 100), признак исчерпания (exhausted), дату сброса (resetAt, скользящее 30-дневное окно) и разбивку по моделям — количество запросов, токены и долю месячного лимита на каждую модель (byModel[].pctOfLimit). Абсолютные значения лимита в Вайбах не раскрываются — только проценты, как в кабинете. Требуется скоуп vibe:ai.

2026-07-06

NEW-0706-1: Поля pricing.perCall и pricing.perMinute в каталоге моделей

Ответы GET /v1/models и GET /v1/models/{model} дополнены необязательными полями в объекте pricing: perCall — стоимость одного вызова в Вайбах, perMinute — стоимость одной минуты аудио в Вайбах (для моделей распознавания речи). Поля появляются только у моделей, для которых соответствующая базовая цена больше нуля; у остальных моделей объект pricing не меняется — существующие запросы работают без изменений.

NEW-0706-2: Новый код ошибки 402 ai_quota_exhausted на AI-эндпоинтах

При включённом контроле месячной AI-квоты портала запросы POST /v1/chat/completions, POST /v1/embeddings и POST /v1/audio/transcriptions могут вернуть 402 с телом { success: false, error: { code: "ai_quota_exhausted", type: "insufficient_quota", reason, resetAt? } }. Поле reason различает три случая: breaker — сработал часовой предохранитель расходов сверх квоты, wallet_empty — квота исчерпана и на балансе портала нет средств, wallet_off — расход сверх квоты для портала недоступен. resetAt — момент, когда запросы снова начнут проходить (для wallet_off при полном отключении может отсутствовать). Пока квота портала не исчерпана, поведение эндпоинтов не меняется.

FIX-0706-3: Расход сверх AI-квоты списывается по базовой цене модели из каталога

Было

При активном контроле месячной AI-квоты портала запросы сверх квоты на POST /v1/chat/completions, POST /v1/embeddings и POST /v1/audio/transcriptions списывались с баланса портала по внутренним ставкам программы квот — со скидками непикового времени; итоговую цену нельзя было увидеть в каталоге моделей.

Стало

Расход сверх квоты списывается по базовой цене модели из публичного каталога — той же, что возвращается в поле pricing ответа GET /v1/models, включая новые perCall и perMinute для не-токенных моделей. Скидки непикового времени применяются только к списанию квоты, а не к денежному балансу. Расход в рамках квоты по-прежнему не списывается с баланса портала.

Влияние на интеграторов

Менять ничего не требуется. Стоимость работы сверх квоты теперь можно рассчитать заранее по каталожной цене модели.

NEW-0706-4: Модель эмбеддингов bitrix/embeddings доступна в API

Эндпоинт POST /v1/embeddings теперь обслуживается моделью bitrix/embeddings — преобразование текста в векторные представления для семантического поиска, кластеризации и retrieval (RAG). Модель бесплатная, тарификация только по входным токенам. Список моделей с поддержкой эмбеддингов — GET /v1/models.

FIX-0706-5: Деплой честно сообщает об ошибке, если новый билд не занял порт

Развёртывание через POST /v1/infra/servers/:id/deploy теперь проверяет, что порт держит именно новый сервис. Если предыдущий процесс продолжает слушать порт, а новый билд крэш-луп'ит с EADDRINUSE, деплой честно завершается ошибкой вместо ложного успеха; порт, занятый оставшимся процессом того же приложения, при возможности освобождается автоматически.

Было

Старая версия продолжала отвечать 200, деплой рапортовал успех, а новый билд так и не поднимался — без ошибки и без подсказки.

Стало

Шаг healthcheck возвращает ошибку с указанием EADDRINUSE и порта, а шаг stop_existing освобождает порт от оставшегося процесса приложения (или предупреждает и продолжает, если освободить нельзя).

NEW-0706-6: фильтр: оператор $nin (NOT IN) для исключения набора значений

Было

Отобрать записи, у которых поле НЕ входит в набор значений, было нельзя: оператор $in (IN) поддерживался, а обратного не было. Родные префиксы Битрикс24 @ (IN) и !@ (NOT IN) в имени поля ({ "!@categoryId": [1, 3] }) не транслировались — такой фильтр по сделкам возвращал 400 UNKNOWN_FILTER_FIELD.

Стало

Добавлен оператор $nin: { "filter": { "categoryId": { "$nin": [1, 3] } } } вернёт записи со всеми значениями, кроме перечисленных (NOT IN). Симметричен $in. Родные префиксы Битрикс24 @ / !@ в имени поля по-прежнему не поддерживаются, но теперь дают понятный 400 INVALID_FILTER_FIELD с подсказкой перейти на $in / $nin — вместо невнятной ошибки или молча проигнорированного (и потому возвращавшего весь набор) фильтра.

BC-0706-7: отдельный код ошибки для слишком длинной команды exec

Поддержка старого формата до: 06.01.2027

Было

Команда длиннее 10 000 символов у POST /v1/infra/servers/:id/exec отклонялась общим кодом VALIDATION_ERROR без указания причины и выхода.

Стало

Такой запрос возвращает 400 с отдельным кодом COMMAND_TOO_LONG и структурированным hint: большие данные и скрипты передаются через POST /v1/infra/servers/:id/upload, затем выполняются bash /путь/скрипт.sh. Остальные нарушения схемы по-прежнему возвращают VALIDATION_ERROR.

Что делать интеграторам

Если ваш клиент обрабатывает VALIDATION_ERROR этого эндпоинта как общий случай ошибки валидации — добавьте обработку кода COMMAND_TOO_LONG (или обрабатывайте любой 400 единообразно).

NEW-0706-8: подсказка в ошибке таймаута exec

Ошибка EXEC_TIMEOUT у POST /v1/infra/servers/:id/exec теперь несёт структурированное поле hint (reason / recovery / recoveryAction): почему процесс был остановлен (по истечении timeout процесс-группа завершается принудительно, без grace-паузы) и что делать — запустить длинную операцию фоновой задачей и следить за ней через GET /v1/infra/servers/:id/logs, поднять timeout до 600 секунд или использовать режим ?stream=true. Поле аддитивное: прежний формат code / message не изменился, подсказка приходит и в JSON-режиме, и в SSE-событии error.

2026-07-05

FIX-0705-1: Отправка сообщения в чат — понятная ошибка при пустом тексте

Текст сообщения передаётся в поле message. Раньше вызов POST /v1/chats/{dialogId}/messages с текстом под неизвестным именем поля (например {"text": "hi"}) молча отбрасывал это поле, и Битрикс24 возвращал 422 BITRIX_ERROR о пустом сообщении — хотя контент был передан.

Было

{"text": "hi"} → 422 BITRIX_ERROR о пустом сообщении, без указания причины.

Стало

Тот же вызов сразу возвращает 400 MESSAGE_REQUIRED и перечисляет нераспознанные поля, подсказывая поле message. Пустой текст по-прежнему допустим вместе с блоком attach (сообщение только с вложением).

Влияние на интеграторов

Корректные вызовы с полем message не меняются. Ошибка при неверном имени поля стала точной.

FIX-0705-2: Ключ в заголовке Authorization: Bearer — понятная ошибка вместо INVALID_SESSION

API-ключ передаётся в заголовке X-Api-Key. Раньше, если ключ по ошибке клали в Authorization: Bearer (это место — для сессионного токена OAuth-приложения), сервер возвращал 401 INVALID_SESSION, и интегратор искал проблему в OAuth-сессии, хотя причина была в неверном заголовке.

Было

Ключ vibe_app_* в Authorization: Bearer → 401 INVALID_SESSION.

Стало

Тот же запрос возвращает 401 WRONG_AUTH_SCHEME с подсказкой: ключ OAuth-приложения (vibe_app_*) передаётся в X-Api-Key, а Authorization: Bearer несёт сессионный токен (vibe_session_*) из POST /v1/oauth/token; клиенту, который умеет только Bearer, подойдёт личный ключ (vibe_api_*). Сессионные токены и личные ключи в Bearer не затронуты.

Влияние на интеграторов

Корректные вызовы с ключом в X-Api-Key и сессией в Authorization: Bearer не меняются.

FIX-0705-3: multipart/create отклоняет XSS-опасные типы содержимого для PUBLIC-объектов

Было

Для PUBLIC-объектов типы содержимого text/html, application/javascript, application/x-javascript и image/svg+xml отклонялись при прямой и presigned-загрузке, но не при инициализации многочастевой (multipart) загрузки. Вызов POST /v1/storage/objects/multipart/create с visibility = PUBLIC и таким типом создавал сессию, и после завершения объект отдавался встроенно в браузере.

Стало

POST /v1/storage/objects/multipart/create с visibility = PUBLIC и XSS-опасным типом содержимого возвращает 415 STORAGE_FORBIDDEN_CONTENT_TYPE — так же, как прямая и presigned-загрузка. Многочастевая сессия при этом не открывается. PRIVATE-объекты по-прежнему допускают любой тип содержимого.

Влияние на интеграторов

Поведение приведено к задокументированному в разделе «Хранилище»: XSS-опасные типы содержимого недопустимы для PUBLIC-объектов на всех путях загрузки. Чтобы загрузить такой файл многочастевой загрузкой, используйте visibility = PRIVATE либо безопасный тип содержимого.

FIX-0705-4: привязка плейсмента на технический адрес сервера теперь ведёт через платформенный обработчик

Было

POST /v1/placements/bind принимал handler, указывающий на технический Black Hole-адрес приложения (app-*.vibecode…), и регистрировал его в Битрикс24 как есть. Битрикс24 отправлял iframe плейсмента напрямую на этот адрес, минуя платформу: сессия не выпускалась, и на сервере с доступом «только для пользователей Битрикс24» открытие плейсмента зацикливало авторизацию (на публичном сервере приложение отдавало собственную ошибку 404).

Стало

Такой handler автоматически переписывается на платформенный обработчик приложения (/v1/bitrix-handler) — плейсмент открывается и авторизуется штатно. В ответе появляются handlerRewritten: true и requestedHandler с исходным значением. Внешние (не Black Hole) обработчики не изменяются. Если платформенный обработчик приложения не удаётся определить, привязка отклоняется с кодом PLATFORM_HANDLER_UNRESOLVABLE вместо регистрации нерабочего адреса. Дополнительно GET /v1/placements помечает уже неправильно привязанный обработчик: data.handlers[].misbound: true плюс текстовый warnings[].

Кроме того, если плейсмент уже зарегистрирован в Битрикс24, но отсутствует в списке приложения (рассинхрон — например, после снятия с публикации без отвязки в Битрикс24), привязка больше не завершается ошибкой «Handler already binded»: платформа снимает устаревшую привязку и повторяет запрос один раз, восстанавливая рассинхрон. Если же привязка не проходит по другой причине (например, требуется коммерческий тариф Битрикс24), рабочий плейсмент не снимается.

NEW-0705-5: Новый код ошибки 402 ai_quota_exhausted на AI-эндпоинтах

При включённом контроле месячной AI-квоты портала запросы POST /v1/chat/completions, POST /v1/embeddings и POST /v1/audio/transcriptions могут вернуть 402 с телом { success: false, error: { code: "ai_quota_exhausted", type: "insufficient_quota", reason, resetAt? } }. Поле reason различает три случая: breaker — сработал часовой предохранитель расходов сверх квоты, wallet_empty — квота исчерпана и на балансе портала нет средств, wallet_off — расход сверх квоты для портала недоступен. resetAt — момент, когда запросы снова начнут проходить (для wallet_off при полном отключении может отсутствовать). Пока квота портала не исчерпана, поведение эндпоинтов не меняется.

2026-07-04

BC-0704-1: Перегрузочные отказы: 429/503 вместо 504

Поддержка старого формата до: 31.07.2026

Перегрузочные отказы сменили HTTP-статусы (коды в теле ответа НЕ изменились — меняется только статус). Правило: 429 — запрос НЕ был обработан, безопасно повторить через Retry-After (заголовок теперь ставится всегда); 503 — платформе или апстриму плохо, повторите позже, для write-операций сначала проверьте, применилось ли изменение. Прикладной 504 из API исключён.

Что изменилось: QUEUE_OVERFLOW 503→429; QUEUE_TIMEOUT 504→429; BITRIX_TIMEOUT (Bitrix24 не ответил за 15 секунд — для этого кода write мог примениться, перечитайте сущность перед повтором) →503; ai_provider_timeout 504→503; UPSTREAM_TIMEOUT (веб-поиск /v1/search — апстрим-провайдер не ответил вовремя) 504→503; RUNTIME_TIMEOUT / GATEWAY_TIMEOUT / WAKE_TIMEOUT 504→503.

NEW-0704-2: Новый код перегрузки AI: 429 ai_congested

AI-запросы к платформенному кластеру теперь проходят через admission-гейт: при перегрузке пула ответ — 429 с кодом ai_congested в теле и заголовком Retry-After. Повтор безопасен (запрос не выполнялся, списания нет). BYOK-ключи и внешние провайдеры гейтом не затрагиваются. По умолчанию гейт выключен — включается платформой.

BC-0704-3: ключи в режиме «только чтение» (READONLY) больше не выполняют запись на рукописных эндпоинтах

Поддержка старого формата до: 03.07.2026

Было

APP-ключ с режимом доступа «только чтение» (accessMode: READONLY) доходил до записи в Битрикс24 на части рукописных эндпоинтов (реквизиты и пресеты, пользовательские поля, timeline pin/note/bind, телефония, почта, диск, бизнес-процессы, приглашение и деактивация пользователя и другие) — гард проверял только скоуп, но не режим доступа ключа.

Стало

Любая попытка записи ключом в режиме «только чтение» возвращает 403 с кодом WRITE_BLOCKED_READONLY_KEY. Эндпоинты чтения не затронуты.

Что делать интеграторам

Если интеграция выполняла запись ключом «только чтение», переключите ключ в режим чтения и записи на странице управления ключами.

FIX-0704-4: деплой galaxy-приложения корректно распаковывает архивы с обёрткой и понятно сообщает о пустом source.content

Было

POST /v1/infra/servers/:id/deploy для galaxy-приложения (kind=GALAXY_APP), где файлы проекта в source.content лежали внутри обёрточной папки (типичный zip, собранный в macOS), собирал приложение с пустым контекстом сборки и падал в рантайме с npm error enoent Could not read package.json. Если же source.content вообще не распаковывался в файлы (передан versionId, путь или пустой архив) — деплой давал ту же непонятную ошибку сборки.

Стало

Такие архивы деплоятся корректно: файлы приложения оказываются в корне контекста сборки. А если source.content распаковался в пустой контекст, деплой сразу возвращает GALAXY_APP_BUILD_FAILED с понятным сообщением о пустом контексте сборки и подсказкой, что source.content должен быть base64-архивом (tar.gz или zip) файлов вашего проекта, а не versionId и не путём.

2026-07-03

FIX-0703-1: привязка размещения по ключу разработчика больше не возвращает 500 после цикла снятия и повторной публикации

Было

POST /v1/placements/bind для приложения, управляемого ключом разработчика (тип local.*), мог стабильно возвращать 500 (BITRIX_UNAVAILABLE, INTERNAL_SERVER_ERROR) при привязке размещения (например CRM_DEAL_DETAIL_TAB или CRM_CONTACT_DETAIL_TAB) после того, как приложение снимали с публикации и публиковали заново. Прежняя регистрация размещения на стороне Битрикс24 сохранялась, и попытка зарегистрировать поверх неё новую завершалась внутренней ошибкой. Повторные вызовы давали ту же ошибку.

Стало

Перед регистрацией размещения запрос сначала снимает его прежнюю регистрацию на стороне Битрикс24, поэтому привязка проходит успешно и после цикла снятия с публикации и повторной публикации. Менять вызов не нужно.

FIX-0703-2: /v1/sites — фильтр по типу «база знаний» (KNOWLEDGE) и «группа» (GROUP) больше не игнорируется

Было

POST /v1/sites/search (а также GET /v1/sites и POST /v1/sites/aggregate) с filter[type]=KNOWLEDGE или filter[type]=GROUP молча возвращал обычные сайты-лендинги (PAGE / STORE / VIBE) вместо баз знаний или страниц групп. Причина на стороне Битрикс24: метод landing.site.getList привязывает фильтр по TYPE к внутренней области (scope), и без параметра scope типы KNOWLEDGE / GROUP не входят в область по умолчанию — фильтр по типу тихо отбрасывался. Обойти можно было только вручную, добавив scope (см. Список сайтов).

Стало

Если в фильтре указан один такой тип и scope не передан явно, Вайбкод сам подставляет соответствующую область (type=KNOWLEDGE → scope=KNOWLEDGE, type=GROUP → scope=GROUP) — и запрос возвращает именно базы знаний / страницы групп. Явно переданный scope всегда в приоритете и не переопределяется. Если тип задан списком или оператором (например {"type":{"$in":["KNOWLEDGE","PAGE"]}}), где одну область выбрать нельзя, в meta.warnings приходит подсказка с кодом TYPE_REQUIRES_SCOPE.

Влияние на интеграторов

Действий не требуется. Запросы с filter[type]=PAGE / STORE / VIBE и запросы без фильтра по типу работают как раньше. MAINPAGE — это область, а не тип сайта (её сайты имеют тип VIBE), поэтому из фильтра по типу область MAINPAGE не выводится. Для /v1/pages поведение не изменилось.

FIX-0703-3: агрегация страниц и сайтов снова возвращает count

Было

POST /v1/pages/aggregate и POST /v1/sites/aggregate возвращали count: 0 и meta.totalRecords: 0 даже при наличии страниц и сайтов — во всех формах: без фильтра, с фильтром, с выражением count и как верхний count при groupBy. Счётчики отдельных групп при groupBy при этом были корректными.

Стало

count и meta.totalRecords отражают фактическое число записей; верхний count при groupBy равен сумме счётчиков групп.

Влияние на интеграторов

Действий не требуется — ответ стал корректным.

NEW-0703-4: Параметры качества и таймстампов в расшифровке аудио

Расшифровка аудио POST /v1/audio/transcriptions принимает пять новых необязательных полей. Качество распознавания: prompt — контекстная подсказка (тема разговора, стиль, правильное написание терминов, до 2000 символов), hotwords — список спец-слов через запятую (редкие термины, бренды, имена, до 500 символов), vad_filter — фильтр тишины перед распознаванием (меньше галлюцинаций на записях с паузами). Управление результатом: temperature — температура декодера от 0 до 1, timestamp_granularities[] — детализация таймстампов word/segment (только с response_format=verbose_json; со значением word каждый сегмент дополняется массивом words с таймингом и вероятностью каждого слова). Поля передаются в multipart/form-data рядом с file и совместимы с OpenAI-контрактом. Невалидные значения отклоняются кодами invalid_prompt, invalid_hotwords, invalid_temperature, invalid_vad_filter, invalid_timestamp_granularities.

FIX-0703-5: Приложения, созданные через API, корректно открываются как плейсменты

Было

Часть приложений, созданных через POST /v1/apps, не открывалась при вызове плейсмента в Битрикс24 — вместо интерфейса приложения пользователь видел ошибку распознавания приложения.

Стало

Созданные приложения корректно резолвятся и открываются как плейсмент-виджеты в Битрикс24. Ответ создания не изменился — приложение сразу пригодно для публикации и привязки плейсментов.

Влияние на интеграторов

Ничего менять не нужно. Ранее не открывавшиеся приложения нужно пересоздать (удалить и создать заново) — новое приложение открывается корректно.

NEW-0703-6: удаление ключа блокируется при привязанном агенте или боте

DELETE /v1/keys/:id теперь возвращает 409 с кодом KEY_HAS_LINKED_AGENT, если ключ является управляющим ключом живого AI-агента или управляемого бота.

Было

Удаление такого ключа осиротляло агента и каскадно удаляло бота вместе с его токеном — идентичность бота в Bitrix24 терялась безвозвратно, без предупреждения.

Стало

Тело ответа: { success: false, error: { code: "KEY_HAS_LINKED_AGENT", message, details: { linkedAgentCount, linkedBotCount, agents: [{ id, name, status }] } } }. Перед удалением перепривяжите ресурсы к другому ключу либо удалите сам агент/бот; для восстановления доступа осиротевшему агенту используйте кабинетное действие «Восстановить доступ». Проверка идёт до синхронизации с Bitrix24 — при 409 учётные данные на стороне Bitrix24 не затрагиваются. Сиблинг существующего KEY_HAS_ACTIVE_SERVERS.

NEW-0703-7: История изменений задачи и стадии канбана

Добавлены два метода только для чтения (скоуп task). GET /v1/tasks/:taskId/history возвращает историю изменений задачи целиком за один вызов: смены стадий канбана, перемещения в спринт и бэклог, статусы и другие события. Фильтр по типу события — ?field=STAGE (несколько типов через запятую, например ?field=STAGE,MOVE_TO_SPRINT); сортировка — ?order=asc или ?order=desc (по умолчанию по возрастанию даты создания). Каждая запись содержит id, createdDate, field, объект value с прежним и новым значением и user с идентификатором автора. GET /v1/tasks/stages/:entityId возвращает текущие колонки канбана рабочей группы (N) или личного плана (0).

FIX-0703-8: bizproc-activities: понятная ошибка вместо «Wrong handler URL» при отсутствии handler

Было

POST /v1/bizproc-activities без поля handler (или с URL обработчика, ошибочно переданным в поле handlerUrl) возвращал непрозрачную ядровую ошибку 422 BITRIX_ERROR: Wrong handler URL.

Стало

Поля code, name, handler проверяются до вызова Битрикс24: при отсутствии handler метод возвращает 400 MISSING_REQUIRED_FIELDS с сообщением Body field "handler" is required to create bizprocActivity, подсказывая правильное имя поля. Успешные вызовы с корректным полем handler не затронуты.

FIX-0703-9: POST /v1/batch — ответы update и delete теперь нормализованы, как create и get

Было

В глобальном пакетном вызове POST /v1/batch подвызов update возвращал ответ в сырой обёртке (вложенный объект вместо плоской записи), а подвызов delete возвращал пустой массив без признака успеха. Это расходилось с create и get в том же эндпоинте и с одиночным PATCH /v1/{entity}/:id, которые отдают плоскую нормализованную запись.

Стало

Подвызов update возвращает плоскую нормализованную запись (camelCase-поля) — как create, get и одиночный PATCH. Подвызов delete возвращает признак успеха вида { id, deleted: true }.

FIX-0703-10: POST /v1/{entity}/batch — create, update и delete снова работают для сущностей CRM

Было

По-сущностный пакетный вызов POST /v1/{entity}/batch с действием create, update или delete для сделок, контактов, компаний, лидов, предложений и счетов возвращал по каждому элементу ошибку «Could not find value for parameter {entityTypeId}», и запись не создавалась, не менялась и не удалялась. Одиночные вызовы (POST /v1/{entity}) и глобальный POST /v1/batch на тех же сущностях при этом работали.

Стало

По-сущностный пакетный create, update и delete для этих сущностей выполняется корректно — так же, как одиночные вызовы и глобальный пакетный эндпоинт.

FIX-0703-11: Пропуск поля model в чате снова подставляет модель по умолчанию

Было

POST /v1/chat/completions без поля model возвращал 400 no_default_model на порталах, где модель по умолчанию не была явно назначена, — даже когда на портале была доступная бесплатная модель. При этом GET /v1/me мог показывать в defaultModel модель, которую нельзя вызвать.

Стало

Если поле model не передано, запрос автоматически берёт первую доступную для вызова модель портала — как и описано в документации. GET /v1/me в поле defaultModel теперь всегда показывает вызываемую модель, ту же самую, которую подставит чат.

FIX-0703-12: include по связанным сущностям снова возвращает сами сущности, а не null

Было

GET /v1/deals/:id?include=contact,company возвращал _included.company = null, а _included.contacts — только метаданные связи (sort/isPrimary/roleId) без полей самой сущности, хотя сделка ссылалась на существующие компанию и контакты.

Стало

_included.company содержит полный объект компании, а _included.contacts — полные объекты контактов (id, name, …) вместе с метаданными связи. Исправление затрагивает include по всем CRM-сущностям (deals, leads, quotes и другим).

FIX-0703-13: поиск сделок отклоняет неизвестное поле фильтра вместо тихой отдачи всех строк

Было

GET /v1/deals и POST /v1/deals/search с неизвестным полем фильтра (опечатка в имени, поле не из схемы) молча пропускали его в Битрикс24, который игнорирует незнакомые ключи фильтра и возвращает весь набор сделок с ответом 200. Клиент, отправивший фильтр с ошибкой в имени поля, получал не пустой результат и не 400, а полную таблицу — как будто фильтр применился.

Стало

Неизвестное поле фильтра теперь отклоняется до вызова Битрикс24 ответом 400 с кодом UNKNOWN_FILTER_FIELD и списком доступных полей в сообщении — как уже делают contacts, companies, leads, quotes, invoices и items. Объявленные поля (включая псевдонимы вроде amount), пользовательские поля (UF_CRM_* и ufCrm*) и id работают как прежде.

FIX-0703-14: Каталог: список и поиск отдают чистый 400 при отсутствии iblockId

Было

Список и поиск GET /v1/catalog-products, POST /v1/catalog-products/search, GET /v1/catalog-sections и POST /v1/catalog-sections/search без iblockId в фильтре доходили до Битрикс24 и возвращали мутный 422 BITRIX_ERROR («Field iblockId is not specified in the filter»).

Стало

Каталожные список и поиск требуют iblockId в фильтре — при отсутствии сразу возвращается 400 MISSING_REQUIRED_FILTER с примером, запрос до Битрикс24 не доходит.

Влияние на интеграторов

Менять ничего не нужно — корректные запросы (с filter[iblockId]) работают как прежде. Изменились только код и ясность ошибки для запросов, которые и так не выполнялись.

FIX-0703-15: Контакты — реальные поля дат createdTime/updatedTime вместо фантомных createdAt/updatedAt

Было

GET /v1/contacts/fields объявлял поля createdAt и updatedAt, но в ответах контактов они никогда не появлялись — Битрикс24 отдаёт даты под ключами createdTime/updatedTime, и именно они были в теле контакта. При этом фильтр и select по createdTime (имя, которое клиент реально видит в ответе) отклонялись как неизвестное поле, а по фантомному createdAt — «работали», хотя само поле в ответе не читалось.

Стало

Схема объявляет реальные ключи createdTime и updatedTime (тип datetime, только чтение): они присутствуют в /fields, фильтр и select по ним работают, а значение нормализуется к ISO-8601 в UTC. Фантомные createdAt/updatedAt больше не объявлены — фильтр или select по ним возвращает 400 UNKNOWN_FILTER_FIELD.

Влияние на интеграторов

Чтение не меняется — ключи createdTime/updatedTime и раньше были в теле ответа, теперь ещё и нормализованы. Если вы фильтровали или проецировали контакты по createdAt/updatedAt, замените имена на createdTime/updatedTime.

FIX-0703-16: Список открытых линий теперь учитывает параметр limit

Было

GET /v1/openline-configs и одноимённый поиск игнорировали limit: нижележащий метод Битрикс24 возвращает весь набор конфигураций, а обёртка отдавала все строки. Поле hasMore при этом было неверным — false, даже когда за пределами запрошенного limit оставались ещё записи.

Стало

Ответ обрезается до limit на стороне обёртки. hasMore: true, когда Битрикс24 вернул больше записей, чем запрошенный limit (есть следующая страница), иначе false. total — число записей в текущем окне.

Влияние на интеграторов

Ответ на запрос с limit теперь содержит не больше limit записей. Клиенты, полагавшиеся на возврат всего набора без учёта limit, увидят усечённый список — используйте offset для следующей страницы.

2026-07-02

NEW-0702-1: фильтр и сортировка задач по реальному статусу (realStatus)

GET /v1/tasks, POST /v1/tasks/search и POST /v1/tasks/aggregate теперь принимают поле realStatus в filter (а список и поиск — ещё и в sort) — фильтрация по фактически сохранённому статусу задачи: 1 — новая, 2 — ждёт выполнения, 3 — выполняется, 4 — ожидает контроля, 5 — завершена, 6 — отложена, 7 — отклонена. Раньше filter[realStatus] молча игнорировался и запрос возвращал весь набор.

В отличие от filter[status], который на стороне Bitrix24 работает как виртуальный (мета-)фильтр (значения −1 просрочена, −2 не просмотрена, −3 почти просрочена) и не совпадает со значением поля status в ответе, realStatus фильтрует именно по хранимому статусу. Поле доступно только для чтения (статус меняется через status) и участвует только в filter/sort — в ответе реальный статус задачи уже отдаётся в поле status.

FIX-0702-2: создание приложения переиспользует упавший одноимённый слот

Было

Повторный POST /v1/infra/servers с тем же name после неудачного деплоя создавал новый слот приложения. Упавшие слоты накапливались и удалялись автоочисткой только через 7 дней.

Стало

Если у владельца ключа на портале уже есть слот с тем же name в статусе error (или созданный, но так и не получивший ни одного деплоя), повторный вызов возвращает этот же слот: его id сохраняется, ошибка и лог сборки сбрасываются, статус возвращается в provisioning — деплойте в него. Слоты, в которые ни разу не отправляли код, теперь удаляются автоочисткой через 24 часа вместо 7 дней (слоты с упавшей сборкой по-прежнему хранятся 7 дней вместе с логом сборки).

Влияние на интеграторов

Изменений в запросах не требуется. Если ваш сценарий пересоздавал слот с тем же именем после ошибки, вы начнёте получать прежний id вместо нового — это ожидаемо: деплой в возвращённый слот работает как обычно. Слоты других пользователей портала и работающие приложения под переиспользование не попадают.

FIX-0702-3: ключи «только чтение» больше не пишут через /v1/bots

Было

Ключ API в режиме «только чтение» (accessMode: READONLY) мог выполнять операции записи через эндпоинты бота (POST /v1/bots, отправка и удаление сообщений, добавление участников в чат, регистрация и удаление бота и другие) — вызов возвращал 200 вместо 403. Остальные проксирующие поверхности Битрикс24 такие записи уже блокировали.

Стало

Запись через /v1/bots/* ключом «только чтение» возвращает 403 с кодом WRITE_BLOCKED_READONLY_KEY. Операции чтения не затронуты, включая получение контекста сообщения (GET /v1/bots/:botId/messages/:messageId/context) и скачивание файла (GET /v1/bots/:botId/files/:fileId).

Влияние на интеграторов

Если бот-интеграции нужна запись — переключите ключ в режим «чтение и запись» в разделе /keys.

FIX-0702-4: include=storage у папок теперь резолвится

Было

GET /v1/folders/:id?include=storage (и список GET /v1/folders?parentId=...&include=storage) не добавляли _included в ответ, хотя GET /v1/folders/fields объявляет для связи storage признак includable: true.

Стало

Связанное хранилище резолвится: в ответе появляется _included.storage с карточкой хранилища, найденной по storageId. Связь описана в GET /v1/folders/fields.

FIX-0702-5: PAGE_BACKGROUND_WORKER: привязка больше не падает с 500

Было

POST /v1/placements/bind для placement PAGE_BACKGROUND_WORKER подставлял обязательный для Битрикс24 параметр options.errorHandlerUrl только когда вызов шёл через OAuth-сессию. Если приложение привязывалось по ключу разработчика или на коробочном портале, параметр не добавлялся и Битрикс24 отвечал 500 (BITRIX_UNAVAILABLE, «Field errorHandlerUrl is empty»), хотя остальные placement привязывались нормально.

Стало

Для PAGE_BACKGROUND_WORKER значение options.errorHandlerUrl по умолчанию подставляется равным handler независимо от способа привязки. Явно переданный options.errorHandlerUrl по-прежнему имеет приоритет. В ответе поле options теперь отражает применённое значение (с подставленным errorHandlerUrl).

Влияние на интеграторов

Действий не требуется — вызов, который раньше возвращал 500, теперь проходит.

FIX-0702-6: POST /search сообщает об отсутствии обязательных параметров чистой ошибкой

Было

POST /v1/{entity}/search для сущностей, чей метод списка Битрикс24 требует обязательные параметры, при их отсутствии не проверял этого и передавал запрос в Битрикс24 как есть. Наружу утекала сырая ошибка Битрикс24 (BITRIX_ERROR, например «Invalid value of parameter [ $id ]» или «Не задан обязательный параметр type»), тогда как у эквивалентного GET-списка та же ситуация давала понятный 400 MISSING_REQUIRED_PARAMS. Затрагивало POST /v1/calendar-events/search (нужен type), POST /v1/files/search (нужен folderId) и POST /v1/folders/search (нужен parentId).

Стало

POST /v1/{entity}/search проверяет обязательные параметры до вызова Битрикс24 — так же, как это давно делает GET-список. При отсутствии параметра приходит 400 с кодом MISSING_REQUIRED_PARAMS и перечнем недостающих полей, без обращения к Битрикс24. Обязательный параметр можно передать в filter, а параметр-родитель (folderId для файлов, parentId для папок) — также на верхнем уровне тела запроса.

NEW-0702-7: Цена research в самоописании ключа и сумма к пополнению в ответе 402

GET /v1/me теперь отдаёт cost у каждого провайдера в блоке webResearch.providers[] — по образцу блока webSearch. Поле несёт цену режима research в Ꝟ (cost.research) и валюту (cost.currency), так что агент видит стоимость глубокого поиска прямо в самоописании ключа, без отдельного вызова.

Ответ 402 при недостатке средств (INSUFFICIENT_BALANCE, а также BILLING_FROZEN) на POST /v1/search и POST /v1/research теперь содержит поле required — сумму в Ꝟ, необходимую для запроса. Прежние поля userMessage и hint не изменились.

Клиентам со строгой валидацией схемы по additionalProperties нужно учесть новые поля ответа.

NEW-0702-8: POST /v1/triggers/fire поддерживает счета (SmartInvoice)

Эндпоинт POST /v1/triggers/fire принимает новое значение entityType — invoice. Передайте entityType: "invoice" и entityId счёта (его выдаёт GET /v1/invoices), чтобы запустить триггер автоматизации по смарт-счёту. Прежние значения (deal, lead, contact, company, quote, item) работают как раньше.

Раньше запустить триггер по счёту было нельзя, а попытка через entityType="item" с entityTypeId=31 отклонялась сообщением, которое уводило в тупик. Теперь item с зарезервированным entityTypeId (в том числе 31) подсказывает перейти на соответствующий entityType — для счёта это invoice.

NEW-0702-9: GET /v1/ai/usage отдаёт длительность аудио по моделям транскрибации

GET /v1/ai/usage в блоке byModel[] теперь возвращает поле audioSeconds — суммарное количество секунд аудио, переданных на транскрибацию по каждой модели за выбранный период. Поле заполняется для вызовов speech-to-text (Whisper) и равно 0 для текстовых моделей, где длительность аудио неприменима.

Поле аддитивное, существующие интеграции продолжают работать без изменений.

BC-0702-10: categories: code и isDefault помечены read-only (были phantom-writable)

Поддержка старого формата до: 01.10.2026

Было

GET /v1/categories/:entityTypeId/fields объявлял code и isDefault записываемыми (readonly: false), но запись этих полей в crm.category.add/update молча игнорировалась (значения не сохранялись, ответ 200).

Стало

Оба поля помечены readonly: true. /fields теперь честно показывает их как read-only, а попытка записать code или isDefault возвращает 400 READONLY_FIELD вместо тихой потери данных.

Что делать интеграторам

Раньше передача code/isDefault в теле POST/PATCH /v1/categories/:entityTypeId принималась (200, значения молча игнорировались). Теперь такой запрос возвращает 400 READONLY_FIELD. Уберите code и isDefault из тела запросов create/update категорий — на запись эти поля больше не принимаются.

BC-0702-11: telephony-lines: поле crmAutoCreate нормализовано в boolean и появилось в /fields

Поддержка старого формата до: 01.10.2026

Было

GET /v1/telephony-lines/fields отдавал только number, serverName, name. Поле автосоздания CRM протекало в ответах списка сырым UPPER-именем CRM_AUTO_CREATE строкой "Y"/"N" — единственное UPPER-поле среди camelCase, и его не было в /fields. На запись camelCase crmAutoCreate молча отбрасывался.

Стало

Поле объявлено как crmAutoCreate (boolean). Теперь оно присутствует в /fields, в ответах list приходит нормализованным (true/false) вместо сырого "Y"/"N", а на create/update принимается camelCase boolean (сырое UPPER-имя ещё принимается на запись для совместимости). Клиенты, читавшие data[].CRM_AUTO_CREATE, должны перейти на data[].crmAutoCreate (boolean).

NEW-0702-12: workgroups: раскрыта операция aggregate и groupBy по полям

Было

POST /v1/workgroups/aggregate работал, но нигде не был заявлен: операции не было в машинном индексе /v1/guide, а groupBy возвращал 400 на любом поле (Available: .), потому что список агрегируемых полей был пуст.

Стало

Объявлен список агрегируемых полей: membersCount (числовые sum/avg/min/max) плюс категориальные active, isProject, ownerId для группировок. Теперь операция видна в /v1/guide и /fields, а groupBy по этим полям работает.

FIX-0702-13: /fields: полнота метаданных у doc-templates и bookings

Было

GET /v1/doc-templates/fields не содержал полей isDefault и productsTableVariant, хотя они приходят в ответах списка. У GET /v1/bookings/fields обязательные resourceIds и datePeriod не были помечены required, поэтому их обязательность не была видна в схеме.

Стало

doc-templates: объявлены isDefault и productsTableVariant (только чтение) — теперь состав /fields совпадает с ответами. bookings: resourceIds и datePeriod помечены required: true, обязательность видна в /fields.

FIX-0702-14: orders: /fields синхронизирован с ответом, убран псевдо-ключ order, companyId фильтруется

Было

GET /v1/orders/fields содержал лишний псевдо-ключ order (артефакт разбора sale.order.getFields) и не содержал полей, реально приходящих в ответах: companyId, clients, dateMarked, personTypeXmlId, statusXmlId, version. Из-за отсутствия companyId в схеме фильтр по нему падал с UNKNOWN_FILTER_FIELD.

Стало

Состав /fields теперь собирается из схемы: псевдо-ключ order убран, объявлены шесть недостающих полей (companyId — записываемое число; clients — объект, только чтение, приходит в get; dateMarked/personTypeXmlId/statusXmlId/version — только чтение). Фильтр и сортировка по companyId теперь работают.

FIX-0702-15: users: limit > 50 теперь работает, meta.hasMore честный

Было

GET /v1/users?limit=500 возвращал только 50 записей, хотя meta.total показывал больше. meta.hasMore всегда был false — документированная пагинация по hasMore молча теряла данные за первой страницей.

Стало

Сущность опирается на легаси-метод user.get (без суффикса .list), поэтому авто-паджинатор не включался. Добавлен флаг paginateViaStart (как у отделов): при limit > 50 идёт многостраничная загрузка через start, а meta.hasMore отражает реальное наличие следующих записей.

FIX-0702-16: POST /v1/batch отклоняет отключённые операции записи и прокидывает обязательные параметры списка

Было

Глобальный POST /v1/batch с действием create, update или delete для сущности, у которой эта операция отключена (например openline-configs — запись вынесена в отдельные роуты), выполнял вызов напрямую в Bitrix24 в обход нормализации и мог тихо создать или изменить запись. Отдельно: действие list или search для сущности с обязательными параметрами метода (например calendar-events — type и ownerId) возвращало AUTO_PAGINATION_FAILED «missing required parameter», хотя прямой запрос списка с теми же параметрами работал. Кроме того, batch-list для folders и files отправлял родительскую папку под именем parentId или folderId, которое метод disk.folder.getchildren игнорирует, поэтому список тихо возвращался не по той папке; а batch-list для calendar-events с лишним ключом filter молча прокидывал его в Bitrix24, и метод возвращал весь календарь без ошибки.

Стало

Отключённая операция записи в под-вызове отклоняется с кодом ACTION_NOT_SUPPORTED до обращения к Bitrix24 — так же, как в POST /v1/{entity}/batch. Обязательные параметры списка и параметры верхнего уровня метода прокидываются в Bitrix24 с исходными именами, поэтому batch-list работает так же, как прямой список, а при их отсутствии возвращается понятный MISSING_REQUIRED_PARAMS вместо сырой ошибки Bitrix24. Для folders и files родительская папка приводится к имени id, которого ждёт метод, поэтому batch-list возвращается по нужной папке. Для сущностей, у метода которых нет конверта filter (calendar-events), лишний ключ filter теперь отклоняется с кодом UNSUPPORTED_FILTER до обращения к Bitrix24 — так же, как в прямом списке.

Влияние на интеграторов

Ничего менять не нужно. Если под-вызов batch раньше опирался на выполнение отключённой операции записи — переведите его на выделенный роут сущности. Для batch-list по сущностям с обязательными параметрами (calendar-events) передавайте type и ownerId в params под-вызова. Если batch-list для calendar-events использовал filter — уберите его или перенесите в параметры верхнего уровня, иначе под-вызов вернёт UNSUPPORTED_FILTER.

NEW-0702-17: GET /:entity/fields отдаёт label и description полей

Ответ GET /v1/{entity}/fields теперь по каждому полю может нести человекочитаемое короткое имя label и пояснение description по-русски — раньше поле описывалось только парой {type, readonly}. Это позволяет ИИ-агенту и интерфейсу показывать название и назначение поля, не обращаясь к документации. Поля добавлены для сущностей: Отделы, Смарт-процессы, Хранилища, Папки, Файлы, Рабочие группы, Шаблоны документов, Бронирования, События календаря, Задачи, Реквизиты, Сотрудники. Для Сотрудников дополнительно исправлена подстановка подписей: GET /v1/users/fields теперь возвращает настоящие названия полей Битрикс24 из user.fields вместо технических кодов. Изменение аддитивное: новые ключи появляются дополнительно к прежним, существующие вызовы продолжают работать без изменений.

FIX-0702-18: ключ только со скоупами vibe:* теперь выписывается, а не падает

Было

Создание ключа авторизации POST /v1/keys, у которого все запрошенные права — внутренние права платформы Вайбкод vibe:* (например только vibe:infra), на портале с режимом dev-key (коробочная Битрикс24 или подключённый облачный портал) отклонялось с 502 DEVKEY_MINT_FAILED. Права vibe:* не передаются в Битрикс24, поэтому набор прав для вебхука Битрикс24 оказывался пустым, и Битрикс24 отклонял выписку, требуя хотя бы одно право.

Стало

Ключ только с правами vibe:* теперь выписывается успешно. Вебхук Битрикс24 для него не создаётся — он не нужен, такой ключ не обращается к REST Битрикс24, — а ключ работает с внутренними возможностями платформы Вайбкод по своим правам.

Влияние на интеграторов

Действий не требуется: прежде падавший запрос теперь возвращает созданный ключ.

FIX-0702-19: пагинация /v1/storages — limit больше 50 отдаёт все записи, meta.hasMore корректен

Было

GET /v1/storages с limit больше 50 возвращал максимум 50 записей, а meta.hasMore был всегда false — даже когда в портале записей больше. Клиент с ?limit=50 при 489 хранилищах видел hasMore: false и не знал, что нужно запросить следующую страницу. То же на POST /v1/storages/search и в /v1/batch.

Стало

limit больше 50 проходит авто-пагинацию (как у остальных списков) и отдаёт запрошенное число записей, а meta.hasMore равен (offset + число записей) < meta.total и для limit не больше 50. Изменение распространяется на GET /v1/storages, POST /v1/storages/search и список storages в /v1/batch.

Примечание: корректный расчёт meta.hasMore для ответов списка при limit не больше 50 теперь распространяется на все сущности (GET /v1/{entity} и POST /v1/{entity}/search), а не только на storages — ранее на этом пути meta.hasMore был всегда false.

FIX-0702-20: infra: кириллица в displayName при создании приложения

Было

При POST /v1/infra/servers с кириллическим displayName название могло сохраниться как последовательность знаков вопроса (??????) — повреждение кодировки при передаче в Битрикс24.

Стало

Название передаётся в кодировке UTF-8, кириллица сохраняется корректно.

Влияние на интеграторов

Действий не требуется. Кириллические названия больше не искажаются.

FIX-0702-21: items: фильтрация по полям связи parentId

Было

Фильтр по динамическому полю связи (например parentId2 — связанная сделка) на GET /v1/items/:entityTypeId и POST /v1/items/:entityTypeId/search отклонялся с 400 UNKNOWN_FILTER_FIELD, хотя поле присутствует в GET /v1/items/:entityTypeId/fields и возвращается в ответах.

Стало

Поля вида parentId<N> принимаются в фильтре и передаются в запрос как есть. Найти смарт-процесс, связанный с конкретной родительской сущностью, теперь можно напрямую через обёртку items.

Влияние на интеграторов

Действий не требуется. Запросы, ранее получавшие 400, теперь отрабатывают.

FIX-0702-22: смарт-процессы: сохранение списка значений и названия пользовательского поля

Было

На POST /v1/items/:entityTypeId/userfields поле-список (userTypeId: enumeration) создавалось, но варианты значений не сохранялись (список приходил пустым), а название поля, переданное строкой, в интерфейсе оставалось пустым.

Стало

Варианты значений принимаются как в enum, так и в list и корректно сохраняются. Название, переданное строкой, автоматически оборачивается в языковую карту и заполняет подписи в форме, колонке и фильтре.

Влияние на интеграторов

Действий не требуется. Ранее «молча терявшиеся» значения списка и название теперь сохраняются.

FIX-0702-23: req-family: /fields у адресов и preset-fields отдают ключи в camelCase

Было

GET /v1/addresses/fields и GET /v1/requisite-presets/:presetId/fields/schema возвращали описание полей с сырыми ключами в UPPER_SNAKE_CASE (TYPE_ID, ADDRESS_1, FIELD_NAME, IN_SHORT_LIST), хотя данные этих сущностей (GET /v1/addresses, список полей пресета) уже приходили в camelCase — схема не совпадала с реальными именами в данных.

Стало

Оба эндпоинта нормализуют ключи описания в camelCase (typeId, address1, fieldName, inShortList), как и весь остальной V1. Внутренние дескрипторы поля (type, isRequired, isReadOnly, title) не меняются.

Влияние на интеграторов

Ключи в ответе /fields теперь совпадают с именами полей в данных. Клиент, читавший camelCase-имена из данных, получает согласованную схему; действий не требуется.

2026-07-01

FIX-0701-1: Загрузка файла на Диск принимает файлы больше 1 МБ

Было

POST /v1/files/upload отклонял тело запроса больше ~1 МБ ошибкой FST_ERR_CTP_BODY_TOO_LARGE. Файл передаётся в base64 в JSON-теле, а base64 раздувает размер примерно на треть — поэтому даже файл 1,1 МБ не проходил. Способа загрузить файл крупнее не было.

Стало

Лимит тела этого маршрута поднят до 70 МБ — этого хватает на файл около 50 МБ с учётом base64 и JSON-обёртки (запись звонка, типовые вложения). Битрикс24 по-прежнему применяет собственное ограничение на размер файла Диска: при его превышении ошибка приходит в стандартном конверте. Для файлов в сотни МБ нужен отдельный способ загрузки (multipart / presigned) — он пока не реализован.

NEW-0701-2: иконка приложения сервера: загрузка SVG, анонимная отдача, фавикон

Появился способ задать иконку приложения для сервера. POST /v1/infra/servers/:id/icon (multipart/form-data, поле file, только SVG до 256 КБ, без скриптов, обработчиков событий и внешних ссылок) загружает иконку; она отдаётся анонимно по стабильному GET /api/server-icons/:id и показывается в каталоге приложений Bitrix24. Чтобы иконка стала фавиконом во вкладке браузера, впишите <link rel="icon" type="image/svg+xml" href="<базовый-URL>/api/server-icons/:id"> в HTML приложения во время сборки — после этого перезаливка иконки обновляет каталог и фавикон автоматически. Формат, требования и порядок — Иконка приложения.

NEW-0701-3: Профиль текущего пользователя сообщает права администратора

Ответ GET /v1/users/me теперь возвращает рабочее поле isAdmin: true — пользователь администратор портала, false — нет, null — определить не удалось (временный сбой; профиль при этом всё равно возвращается). Поле пригодно для серверной проверки прав в вашем бэкенде. На GET /v1/users/:id и в списке пользователей поле по-прежнему недоступно — вердикт отдаётся только для текущего пользователя сессии.

NEW-0701-4: Расширенный статический контракт полей в /v1/guide и указатель schema-discovery в /v1/me

По каждой сущности в ответе GET /v1/guide добавлено поле data.entities[].fieldsDetailed — расширенный статический контракт полей: type, readonly, required, createOnly и декодирование enum (например, значения status и priority у задач). Оно доступно по одному заголовку X-Api-Key, без сессии, и предназначено для маппингов и кодогенерации до появления пользовательской сессии. Компактное поле fields сохранено без изменений.

Ответ GET /v1/me для ключа авторизации без пользовательской сессии (без заголовка Authorization: Bearer) теперь содержит блок schemaDiscovery — указатель, где брать статическую схему без сессии (/v1/guide) и как получить живые и пользовательские поля (Bearer-сессия или персональный ключ). Эндпоинты GET /v1/<entity>/fields и GET /v1/userfields/* не изменились.

BC-0701-5: categoryId в ответе постов теперь массив чисел

Поддержка старого формата до: 01.01.2027

Было

GET /v1/posts отдавал categoryId с типом, зависящим от количества категорий поста: null без категорий, число (11) для одной, строка со списком ID через запятую ("5,7,9") для нескольких. Типизированный клиент с полем categoryId: number | null работал на постах с одной категорией, но ломался на постах с двумя и более.

Стало

categoryId — всегда массив чисел number[]: [] без категорий, [11] для одной, [5, 7, 9] для нескольких. Тип единый независимо от количества категорий.

Что делать интеграторам

Читайте categoryId как массив: post.categoryId.length вместо проверки на null, post.categoryId[0] для первой категории. Прежнюю ветку «число или строка» можно убрать.

FIX-0701-6: единые коды ошибок токена и скоупа в разделе Диска

Было

Пользовательские операции Диска — POST /v1/files/:id/moveto, copyto, POST /v1/files/upload, GET /v1/files/:id/download и аналоги для папок — при отсутствии токенов портала возвращали 401 NO_TOKENS, а при нехватке скоупа disk — 403 SCOPE_MISSING. Сгенерированные CRUD-операции того же раздела (list/get/create/update/delete) для тех же условий уже возвращали 401 TOKEN_MISSING и 403 SCOPE_DENIED — поэтому в пределах одного раздела клиент видел два разных кода для одной ошибки.

Стало

Все операции Диска возвращают единые коды — 401 TOKEN_MISSING и 403 SCOPE_DENIED, как и остальной V1 API. HTTP-статусы (401 и 403) не изменились.

Влияние на интеграторов

Если код ветвился по строкам NO_TOKENS или SCOPE_MISSING на операциях moveto/copyto/upload/download, переключитесь на TOKEN_MISSING / SCOPE_DENIED (или проверяйте HTTP-статус). Остальным менять ничего не нужно.

FIX-0701-7: placement CALL_CARD убран из списка допустимых

Было

Код placement CALL_CARD числился допустимым: POST /v1/placements/bind пропускал его через валидацию и передавал в Битрикс24, а GET /v1/placements/available выдавал его в списке. Но ни один модуль Битрикс24 не регистрирует этот placement, поэтому placement.bind завершался внутренней ошибкой, которую платформа отдавала как INTERNAL_SERVER_ERROR.

Стало

CALL_CARD удалён из списка допустимых: он больше не появляется в GET /v1/placements/available, а POST /v1/placements/bind с ним отклоняется сразу понятной ошибкой VALIDATION_ERROR, без обращения к Битрикс24.

Влияние на интеграторов

Привязка CALL_CARD не работала и раньше (возвращала непонятную ошибку 500), поэтому рабочих интеграций изменение не ломает. Для панели приложений в карточке звонка используйте актуальные placement'ы из GET /v1/placements/available.

FIX-0701-8: workday open/close/pause: поле userId теперь применяется

Было

Документированное поле тела userId в POST /v1/workday/open, POST /v1/workday/close и POST /v1/workday/pause молча игнорировалось: операция всегда выполнялась над владельцем токенов ключа, даже если был передан другой сотрудник. Ответ приходил success, но действие затрагивало не того пользователя.

Стало

userId транслируется в параметр Битрикс24 USER_ID, поэтому операция выполняется над указанным сотрудником (при наличии прав администратора или руководителя). Несуществующий userId теперь возвращает ошибку Битрикс24, а не мнимый успех. Некорректный userId (не положительное целое) отклоняется как 400 INVALID_PARAMS. Поведение совпадает с уже работавшим GET /v1/workday/status.

Влияние на интеграторов

Кто не передавал userId, изменений не заметит — операция по-прежнему применяется к владельцу токенов. Кто передавал userId, теперь получит корректное действие над указанным сотрудником.

2026-06-30

NEW-0630-1: POST /v1/cowork/deploy-key — получить проектный ключ для деплоя из Cowork/Code

Ключ Cowork/Code (vibe:cowork) работает только с data-plane и блокируется на control-plane инфраструктуры с 403 INFRA_FORBIDDEN_FOR_COWORK_KEY. Новый эндпоинт POST /v1/cowork/deploy-key позволяет агенту самому получить отдельный проектный ключ с правом деплоя: вызовите его этим же Cowork-ключом, возьмите поле key из ответа (тело — плоский объект, без обёртки data) и используйте его как заголовок X-Api-Key для деплоя/provision/exec под /v1/infra/*.

Возвращаемый ключ несёт скоупы vibe:infra + vibe:storage (без vibe:cowork), действует 7 дней и привязан к владельцу и порталу Cowork-ключа. На каждый вызов выдаётся свежий ключ, прежний проектный ключ при этом отзывается (активен всегда один). Требуется скоуп vibe:cowork и активная подписка Cowork/Code; коды отказа — 403 INSUFFICIENT_SCOPE / 403 COWORK_NOT_ACTIVATED / 503 DEPLOY_KEY_DISABLED / 503 INFRA_DISABLED.

NEW-0630-2: Self-hosted placement получает одноразовый код авторизации на appUrl

Для приложения с собственным appUrl (вне Black Hole), открытого как placement, платформа теперь добавляет к редиректу на appUrl одноразовый код авторизации (?code=...) вместо внутреннего gateway-токена. Приложение обменивает этот код на vibe_session существующим запросом POST /v1/oauth/token — redirect_uri должен точно совпадать с настроенным appUrl. Раньше такие приложения получали невостребуемый токен и не могли авторизовать пользователя.

NEW-0630-3: Новый эндпоинт POST /v1/oauth/placement-session для self-hosted приложений

Self-hosted приложение (на собственном сервере, не на Black Hole), открытое как placement (iframe) в Битрикс24, теперь может обменять токен пользователя Битрикс24, полученный в placement-колбэке на своём обработчике, на vibe_session. Запрос POST /v1/oauth/placement-session с телом { app_key, access_token, member_id, domain } (необязательно refresh_token, expires_in) идёт сервер-к-серверу — токен сессии не попадает в браузер. Раздел авторизации документации описывает обе топологии placement.

FIX-0630-4: PATCH права OAuth-app-ключа: честный отказ вместо ложной выдачи

Было

PATCH /v1/keys/:id с добавлением права Bitrix24 к ключу OAuth-приложения (vibe_app_*), как и PATCH /v1/apps/:id с расширением scopes приложения, возвращал 200 и сохранял новый набор прав. Но права OAuth-приложения фиксируются при выпуске и от такой правки на стороне Bitrix24 не меняются, поэтому GET /v1/me затем сообщал право, которого Bitrix24 не выдавал, а реальный вызов отклонялся.

Стало

Добавление права Bitrix24 к ключу OAuth-приложения или к приложению теперь отклоняется с 403 OAUTH_SCOPE_CHANGE_REQUIRES_REISSUE. Снятие прав и изменение vibe:*-прав работают по-прежнему. Чтобы получить новое право, выпустите новый ключ авторизации с нужным набором.

FIX-0630-5: ключ со скоупом `tasks` теперь реально открывает методы задач

Было

Ключ, выписанный со скоупом tasks (множественное число — его предлагает UI-пикер), на dev-key-портале минтил вебхук, который Bitrix24 принимал, но к REST-методам модуля Задач не привязывал. GET /v1/me рапортовал tasks, но вызовы методов задач отклонялись на стороне Bitrix24. Скоуп task (единственное число) работал.

Стало

При выписке и правке ключа набор прав канонизируется к написанию, которое Bitrix24 реально привязывает к методам (tasks → task), поэтому ключ открывает методы задач независимо от выбранного написания. На уже выписанные ключи изменение не распространяется ретроактивно — перевыпустите ключ.

FIX-0630-6: неверный формат FILES в комментарии таймлайна отклоняется с 400

Было

POST /v1/timelines и PATCH /v1/timelines/:id принимали поле FILES в любом виде и отвечали 200/201. Если форма отличалась от массива пар [[имяФайла, base64Содержимое]] — например плоский массив строк или одиночная пара без внешнего массива — комментарий создавался, но файл прикреплялся как мусорный (со случайным именем и нечитаемым содержимым) либо терялся молча, без признаков ошибки.

Стало

Поле FILES, переданное не в виде массива пар [[имяФайла, base64Содержимое]], отклоняется до обращения к Битрикс24 ошибкой 400 INVALID_FILES_SHAPE с подсказкой о правильной форме. Пустой FILES ([]) и отсутствие поля по-прежнему допустимы. Проверка действует на одиночных POST/PATCH и в батче (POST /v1/batch, POST /v1/timelines/batch).

Влияние на интеграторов

Кто передаёт FILES в задокументированной форме [[имяФайла, base64Содержимое]] — изменений нет. Кто полагался на другие формы — теперь получит явную 400 вместо молча испорченного вложения, и сможет исправить запрос.

FIX-0630-7: пустые поля ботов и сотрудников приходят как null/[], а не false/{}

Было

В ответах карточки бота (GET /v1/bots/:botId, POST /v1/bots, PATCH /v1/bots/:botId) незаполненные поля в users[] отдавались неверным примитивом: даты lastActivityDate, mobileLastDate, desktopLastDate приходили как булево false, а пустой список phones — тоже как false. То же поле lastActivityDate в GET /v1/users приходило как пустой объект {}. Из-за этого new Date(lastActivityDate) молча давал начало эпохи, а phones.map(...) падал с ошибкой типа.

Стало

Незаполненная дата на всех путях кодируется единообразно как null, а пустой список телефонов — как []. Заполненная дата по-прежнему приходит ISO-строкой, заполненный список — массивом.

Влияние на интеграторов

Менять ничего не нужно — типы стали корректными. Код, который опирался на сравнение с false для пустых значений, перестанет срабатывать: проверяйте дату на null, а список телефонов — как массив.

Затронутые эндпоинты: GET /v1/bots/:botId, POST /v1/bots, PATCH /v1/bots/:botId, GET /v1/users

NEW-0630-8: Пересвязка OAuth-credentials приложения без удаления

Новый эндпоинт POST /v1/apps/:id/relink-oauth обновляет bitrixClientId и bitrixClientSecret у существующего приложения, не удаляя его. Это нужно, когда локальное OAuth-приложение пересоздали на портале Битрикс24 и у него сменился client_id: раньше единственным путём было удалить приложение (что рвало связанные бот, openline и привязки) и создать заново.

Тело запроса: { bitrixClientId, bitrixClientSecret } (оба обязательны). Парный ключ, бот и привязки сохраняются. Если этот client_id уже привязан к другому приложению — 409 OAUTH_CLIENT_ID_IN_USE. Ключом самого OAuth-приложения вызвать нельзя — 403 OAUTH_APP_KEY_CANNOT_RELINK (нужен личный ключ или кабинет). После пересвязки переустановите приложение на портале — это восстановит подписку на события.

NEW-0630-9: Веб-поиск: полный текст страниц, изображения, режим новостей и фильтры по доменам в research

Было

POST /v1/search принимал include_raw_content как булев флаг, но полный текст найденных страниц в ответ не попадал. Не было параметров для режима новостей и для запроса изображений. POST /v1/research принимал include_domains и exclude_domains, но молча их отбрасывал.

Стало

POST /v1/search получил два новых необязательных параметра: topic (general или news, по умолчанию general) и include_images (булев, по умолчанию false). Булев include_raw_content теперь действительно возвращает полный текст: у каждого результата появилось поле rawContent (полный текст страницы, ограниченный по размеру). В ответе добавились верхнеуровневые images (массив объектов с полем url) и ignored_filters (массив строк — переданные фильтры, которые провайдер не смог применить). Эти поля приходят и в синхронном теле ответа, и в кадре done потоковой передачи. Заголовок X-Search-Filters-Ignored сохранён и теперь может перечислять topic и include_images. POST /v1/research теперь применяет include_domains и exclude_domains (до 20 каждый) у провайдеров с поддержкой и сообщает о превышении через ignored_filters в кадре done. Какие именно возможности доступны у выбранного движка — возвращает GET /v1/search/providers. Клиентам со строгой валидацией схемы по additionalProperties нужно учесть новые поля ответа.

NEW-0630-10: Чтение AI-расшифровок звонков клиентов через API

Новый эндпоинт GET /v1/activities/:activityId/transcript возвращает готовую AI-расшифровку звонка клиента по идентификатору CRM-активности «Звонок». Метод только читает уже готовую расшифровку — генерацию не запускает. Требует скоуп crm. Если расшифровки для звонка ещё нет, поле data.transcription равно null — это штатный ответ, а не ошибка.

2026-06-29

FIX-0629-1: поиск узлов оргструктуры теперь ищет по названию

Было

POST /v1/humanresources/nodes/search проксировал в humanresources.node.list: тип узла задавался внутри filter, поиска по названию не было вовсе, а тело { "type": ..., "name": ... } на верхнем уровне (или запрос без тела) возвращало 400 или 500.

Стало

Эндпоинт обёрнут на humanresources.node.search. Обязательны два поля на верхнем уровне тела — type (DEPARTMENT или TEAM) и name (подстрока названия). Необязательны parentId и pagination.limit (по умолчанию 50, максимум 200). Возвращаются узлы, чьё название содержит name, в плоском data с meta (total, hasMore). Поля filter, order и select больше не принимаются.

Влияние на интеграторов

Присылайте { "type": "TEAM", "name": "<подстрока>" } на верхнем уровне вместо прежнего { "filter": { "type": "TEAM" } }. Чтобы перечислить все узлы типа без поиска по названию, используйте GET /v1/humanresources/nodes с ?type=....

NEW-0629-2: Расписание выгодных часов AI-квоты (off-peak)

Добавлен эндпоинт GET /v1/off-peak — расписание скидок «выгодных часов» (Time-of-Use) для AI-квоты. Ответ содержит множитель цены прямо сейчас (currentMultiplier), ближайшее окно, когда станет дешевле (nextWindow), сетку 24×7 по часам и дням недели (grid), текущую ячейку сетки (nowCell) и часовой пояс расписания (timezone). Скидка применяется только к квотируемому расходу — в эти часы квота расходуется медленнее; оплата за токены по кошельку не затрагивается. Необязательный параметр model=<идентификатор> возвращает расписание конкретной модели вместо общего по умолчанию. Требуется скоуп vibe:ai. Пока выгодные часы не включены, ответ — { "enabled": false }.

Поддержка старого формата до: 26.12.2026

Было

Поле provider в POST /v1/search и POST /v1/research принимало слаг vibe-search — отдельный платформенный движок, добавленный 06.06.2026. Он также присутствовал в перечне слагов GET /v1/search/providers.

Стало

Слаг vibe-search удалён. Платформенный поисковый движок на всех инстансах называется bitrix-search — конкретный апстрим за ним зависит от инстанса. Запрос с provider: "vibe-search" теперь возвращает 400 INVALID_REQUEST (значение не проходит валидацию). Поддержка research для bitrix-search тоже зависит от инстанса — читайте GET /v1/search/providers.

Что делать интеграторам

Если в запросе явно передавался provider: "vibe-search", замените его на bitrix-search либо опустите поле provider, чтобы использовать движок по умолчанию инстанса (его показывает поле defaultProvider в GET /v1/me). Слаг vibe-search не был движком по умолчанию ни на одном проде, поэтому затронуты только интеграции, прописавшие его явно.

FIX-0629-4: Значение null в поле через POST /v1/batch больше не пишет в поле строку «null»

Было

В составном POST /v1/batch создание или обновление со значением поля null (например {"entity":"deals","action":"update","entityId":123,"params":{"comments":null}}) записывало в поле литеральную строку "null".

Стало

Поле получает пустое значение, которое Bitrix24 трактует по типу поля: текстовое — очищается, числовое — становится 0, датовое — остаётся без изменений. Литеральная строка "null" больше не пишется, ошибки не возникает. Это совпадает с поведением одиночного PATCH /v1/{entity}/:id с null. Постраничный путь /v1/{entity}/batch по-прежнему пропускает null-поле целиком (оставляет значение без изменений у всех типов).

FIX-0629-5: Поиск и список с фильтром по null теперь отдают больше 50 строк

Было

Запрос POST /v1/{entity}/search или GET /v1/{entity} с фильтром по пустому значению (например {"filter": {"closedDate": null}}) и limit больше 50 возвращал максимум 50 записей, хотя meta.total показывал реальное число совпадений и meta.hasMore был true. Автопагинация молча обрывалась после первой страницы, и типовой обход «читать, пока строк ровно limit» получал неполный результат без единой ошибки.

Стало

Такой запрос отдаёт до limit записей, как и с любым другим фильтром. Значение null в фильтре трактуется как «поле пусто» одинаково на всех страницах выборки.

Влияние на интеграторов

Клиентам, которые из-за обрыва листали вручную через offset шагом 50, ручной обход больше не нужен — можно запросить до 5000 записей одним вызовом.

FIX-0629-6: смена порта и автомаршрутизация деплоя работают на новых серверах-приложениях из коробки

Было

Обычный сервер «Опубликовать приложение» поднимался с фиксированным портом агента, поэтому PATCH /v1/infra/servers/:id/port и шаг автомаршрутизации деплоя отвечали 409 PORT_NOT_APPLIED (NO_SCANNER), а публичный URL отдавал служебную страницу Black Hole, пока сервис слушал не порт по умолчанию.

Стало

Новые обычные серверы-приложения поднимаются с авто-определением порта: сервис на любом порту доступен через туннель сразу, а смена порта и автомаршрутизация деплоя проходят успешно. Серверы агентов и galaxy-хостов поведение не меняют.

FIX-0629-7: установка приложения через /v1/apps возвращает понятный код ошибки вместо общего BOX_APP_INSTALL_FAILED

Было

При сбое установки OAuth-приложения POST /v1/apps всегда возвращал 502 BOX_APP_INSTALL_FAILED, а в error.message подставлялся сырой ответ Битрикс24 целиком.

Стало

Ответ при сбое классифицируется: 403 B24_INSUFFICIENT_SCOPE (служебная интеграция потеряла права на портале), 410 STALE_DEVELOPER_KEY (доступ изменили или удалили — восстановить автоматически нельзя), 502 RECOVERY_FAILED (временный сбой, можно повторить) или 502 DEVKEY_MINT_FAILED (прочее). error.message больше не содержит сырой ответ Битрикс24 — диагностика переехала в очищенное поле error.details.b24Body.

Влияние на интеграторов

Обработка «ответ не 201 — установка не удалась» продолжает работать без изменений. Если код различал именно BOX_APP_INSTALL_FAILED, добавьте обработку новых кодов выше.

FIX-0629-8: поиск по широкому диапазону дат больше не возвращает пусто

Было

POST /v1/deals/search (и аналогично для leads, contacts, companies, quotes, invoices, items) с фильтром по дате и нижней границей (>= / >) за период шире 14 дней возвращал 200 с пустым data и meta.total: 0, хотя записи за этот период существовали.

Стало

Такой запрос возвращает все совпадающие записи. Поведение GET /v1/deals, узких диапазонов (≤ 14 дней) и параметра autoWindow: false не менялось.

NEW-0629-9: Новый код ошибки CONNECTOR_APP_INSTALL_FORBIDDEN при установке приложения

POST /v1/apps на коробочном портале теперь возвращает 403 с кодом CONNECTOR_APP_INSTALL_FORBIDDEN, когда администратор портала Битрикс24 запретил пользователю устанавливать приложения. Поле error.message содержит понятное локализованное объяснение с подсказкой обратиться к администратору портала. Прежде такой отказ отдавался как общий 502 CONNECTOR_APP_INSTALL_FAILED без объяснения причины; этот код по-прежнему используется для прочих сбоев установки.

NEW-0629-10: Перенос владения ботом на другой ключ

Добавлен эндпоинт POST /v1/bots/:botId/transfer — переносит владение ботом на другой API-ключ того же аккаунта Битрикс24 и того же пользователя (или администратора аккаунта). Решает ситуацию, когда бот «осиротел» после пересоздания приложения: ключ-владелец отозван, и рантайм бота переставал работать. Тело: { "targetApiKeyId": "<id>" }. Целевой ключ должен быть активен, в том же аккаунте, с областью imbot. После переноса проверьте B24-привязку нового ключа через POST /v1/bots/:botId/reauth.

2026-06-28

FIX-0628-1: редактирование scopes ключа применяется к вебхуку Битрикс24

Было

PATCH /v1/keys/:id со списком scopes сохранял новый набор на платформе Вайбкод, но на self-hosted (коробочных) порталах Битрикс24 не переносил его на вебхук портала. Вебхук оставался со старым набором scopes, и вызов метода из только что добавленного scope отклонялся Битрикс24 (403), хотя по данным платформы Вайбкод ключ этот scope уже имел.

Стало

Изменение scopes теперь применяется к вебхуку Битрикс24 в том же запросе. Если синхронизацию выполнить не удалось, ключ не обновляется (Вайбкод и Битрикс24 остаются на прежнем наборе), а ответ несёт код ошибки: INVALID_SCOPES (400) — портал не выдаёт один из запрошенных scope, STALE_DEVELOPER_KEY (410) или RECOVERY_FAILED (502) — ключ доступа портала недействителен, BOX_NO_DEVELOPER_KEY (400) — у владельца ключа нет ключа доступа портала, DEVKEY_SCOPE_SYNC_FAILED (502) — прочий отказ Битрикс24.

Влияние на интеграторов

Менять ничего не нужно — scopes, добавленные через PATCH /v1/keys/:id, теперь работают сразу. Если в ответ пришёл один из кодов выше, набор scopes остался прежним: устраните причину и повторите запрос.

2026-06-27

NEW-0627-1: Свойства товаров каталога — чтение и управление схемой свойств

Добавлен раздел /v1/catalog-product-properties — определения пользовательских свойств торгового каталога (идентификатор, название, тип). Поддерживаются список, получение, создание, изменение, удаление, поиск и справочник полей. Свойства-списки товара приходят в /v1/catalog-products полями вида propertyNNN, где NNN — идентификатор свойства. Новый раздел сопоставляет этот идентификатор с названием и типом свойства. Фильтр filter[iblockId] ограничивает выборку одним каталогом, идентификатор берётся из /v1/catalogs. Требуется скоуп catalog.

2026-06-26

FIX-0626-1: catalog-prices: системные поля priceScale, extraId, timestampX объявлены в схеме

Было

GET /v1/catalog-prices и GET /v1/catalog-prices/:id возвращали поля priceScale, extraId и timestampX, но они не были объявлены в схеме: проходили без нормализации (поле timestampX приходило в формате со смещением, например 2024-06-17T16:53:24+03:00) и отсутствовали в ответе GET /v1/catalog-prices/fields.

Стало

Три поля объявлены как доступные только для чтения. Теперь они перечислены в GET /v1/catalog-prices/fields, а timestampX нормализуется в ISO 8601 UTC (2024-06-17T13:53:24.000Z) — единообразно с остальными полями типа datetime.

Влияние на интеграторов

Момент времени в timestampX не меняется — меняется только его строковое представление (UTC вместо локального смещения). Клиенты, разбирающие значение стандартным парсером дат, продолжают работать без изменений.

FIX-0626-2: Скачивание исходников сервера и приложения: подписанный URL больше не отдаёт 403

Было

GET /v1/infra/servers/:id/sources/:versionId/download и GET /v1/apps/:id/sources/:versionId/download возвращали 200 с подписанным URL, но скачивание по этому URL падало с 403 AccessDenied, если запрос шёл под личным ключом (vibe_api_*). Листинг версий при этом работал, и файл физически присутствовал в хранилище.

Стало

Подписанный URL теперь привязан к фактическому расположению объекта в хранилище, поэтому скачивание возвращает содержимое архива. Исправление покрывает и снапшоты, у которых ключ хранения принадлежит другому семейству (legacy-снапшоты приложения с привязкой к серверу).

Влияние на интеграторов

Контракт эндпоинтов не меняется — это восстановление задокументированного поведения «200 + рабочий подписанный URL». Никаких изменений на стороне клиента не требуется.

NEW-0626-3: Обновление api-bearer токена и причина отказа Gateway

Новый эндпоинт POST /v1/infra/servers/:id/access-tokens/:tokenId/refresh выпускает свежий JWT (до 10 минут) для уже существующего токена режима api-bearer — без создания новой записи, не расходуя лимит активных токенов и лимит выпусков в час. Долгоживущий клиент (CI, AI-агент) обновляет токен перед истечением jwtExpiresAt вместо повторного выпуска. Один отзыв гасит и исходный, и все обновлённые JWT одного токена.

Ответ 401 BH_LOGIN_REQUIRED, который Gateway возвращает на субдомене приложения при отклонении заголовка Authorization: Bearer, теперь содержит поле reason с конкретной причиной: expired, signature, subdomain, type, revoked, malformed или invalid. Поле аддитивное — прежние клиенты его игнорируют.

2026-06-25

FIX-0625-1: нейтральные идентификаторы провайдера, плана и региона в инфраструктурном API

Было

На международной (.com) поверхности GET /v1/infra/providers, GET /v1/infra/providers/:id/plans и GET /v1/infra/providers/:id/regions отдавали идентификаторы провайдера, планов, типа диска и регионов в сыром виде нижележащей инфраструктуры, а не в нейтральном пространстве имён бренда.

Стало

Те же поля приведены к нейтральному пространству имён бренда Bitrix Cloud, как в российском сегменте: провайдер bitrix-cloud, планы bc-small/bc-medium/bc-large/bc-xlarge, diskType: "network-ssd", регионы bc-eu-central/bc-eu-west/bc-us-east/bc-us-west/bc-ap-southeast. Поля name и country остаются человекочитаемыми (например, «Frankfurt (EU Central)», DE). Те же значения возвращаются в полях provider/plan/region ответов GET /v1/infra/servers и GET /v1/infra/servers/:id.

Влияние на интеграторов

Стандартный сценарий не требует изменений: идентификатор, полученный из каталога, по-прежнему передаётся в POST /v1/infra/servers без правок. При создании сервера принимаются и новые идентификаторы (bitrix-cloud/bc-small/bc-eu-central), и идентификаторы из прежнего каталога, поэтому существующие интеграции продолжают работать. Поправьте только код, который сверяет идентификаторы ответа с захардкоженными строками из прежнего каталога провайдера, планов или регионов.

FIX-0625-2: offset внутри подвызовов /v1/batch теперь листает страницы

Было

В POST /v1/batch подвызовы list и search молча игнорировали offset в params: B24 получал неизвестный ключ offset вместо start, поэтому каждая страница возвращала один и тот же первый набор записей. Три подвызова contacts.search с offset 0, 50 и 100 отдавали идентичную первую страницу.

Стало

offset в подвызове list/search переводится в start Bitrix24 (как в одиночном POST /v1/{entity}/search). Те же три подвызова теперь возвращают три разные непересекающиеся страницы. Поведение одиночного эндпоинта не изменилось.

FIX-0625-3: Файлы и папки: deletedBy у не удалённых объектов теперь null

Было

GET /v1/files/:id, GET /v1/files и эндпоинты папок возвращали deletedBy: 0 у не удалённого объекта, хотя поле объявлено как number | null с «null — объект не удалён». Поле-сосед deletedAt при этом корректно отдавало null, так что два поля с одинаковым контрактом вели себя по-разному, и проверка deletedBy !== null ошибочно считала каждый активный объект удалённым.

Стало

deletedBy нормализуется в null для не удалённых объектов (Битрикс24 хранит в колонке DELETED_BY ноль как «нет пользователя»; пользователя с id 0 не существует). У удалённых объектов поле по-прежнему содержит id пользователя, выполнившего удаление.

Влияние на интеграторов

Поведение приведено к задокументированному контракту number | null. Клиенты, проверявшие deletedBy === null / deletedBy !== null, теперь получают корректный результат для активных объектов.

NEW-0625-4: чтение Базы знаний 2.0 — список баз, документы, дерево, поиск

Read-доступ к Базе знаний 2.0: список доступных баз знаний (курсорная пагинация), получение базы знаний и документа по идентификатору (документ — с Markdown-содержимым), дерево документов базы знаний и полнотекстовый поиск по документам. Скоуп note.

Затронутые эндпоинты: GET /v1/note/collections (список), GET /v1/note/collections/:id (одна база), GET /v1/note/collections/:collectionId/documents (дерево), GET /v1/note/documents/:id (документ с Markdown), GET /v1/note/documents/search (поиск по query).

FIX-0625-5: методы Базы знаний 2.0 (создание, изменение, загрузка файлов) теперь работают

Было

Создание и изменение баз знаний и документов (POST /v1/note/collections, PATCH /v1/note/collections/:id, POST /v1/note/documents, PATCH /v1/note/documents/:id) возвращали 400 с ошибкой валидации Битрикс24, а загрузка вложения (POST /v1/note/documents/:documentId/files) сохраняла файл, но не возвращала его идентификатор в data.id.

Стало

Методы работают: создание и изменение возвращают 200, а ответы создания баз знаний, документов и файлов содержат идентификатор в data.id. Архивирование, удаление и получение файла работали и раньше.

FIX-0625-6: /v1/me: supportedVisibilities хранилища теперь в верхнем регистре

Было

GET /v1/me в блоке storage отдавал supportedVisibilities: ["private","public"] в нижнем регистре, а эндпоинты загрузки принимают только PRIVATE/PUBLIC (верхний регистр). Агент, скопировавший значение из манифеста, получал STORAGE_INVALID_VISIBILITY.

Стало

supportedVisibilities отдаётся как ["PRIVATE","PUBLIC"] — ровно те значения, которые принимает параметр visibility при загрузке.

Влияние на интеграторов

Если клиент брал значение visibility из /v1/me и приводил его к верхнему регистру сам — ничего не меняется. Если передавал как есть — теперь загрузка проходит без ошибки.

2026-06-24

FIX-0624-1: логи galaxy-приложения отдают вывод контейнера

Было

GET /v1/infra/servers/:id/logs для galaxy-приложения (kind=GALAXY_APP) возвращал только системный журнал хоста (journalctl), а не логи самого контейнера приложения — увидеть stdout/stderr упавшего приложения было нельзя.

Стало

Для galaxy-приложения эндпоинт читает stdout/stderr контейнера (docker logs). Чтение read-only: если хост-галактика спит или недоступен, ответ — пустой data.logs плюс data.hint, хост не будится. Параметр since для galaxy-приложений принимает только длительность (10m) или метку RFC3339 — человекочитаемые формы journalctl («1 hour ago») допустимы лишь для Black Hole-серверов.

NEW-0624-2: код ошибки GALAXY_APP_START_FAILED при деплое

POST /v1/infra/servers/:id/deploy для galaxy-приложения возвращает 502 GALAXY_APP_START_FAILED, когда приложение успешно собралось, но упало или ушло в OOM-перезапуск сразу после старта. Это отдельный код от GALAXY_APP_BUILD_FAILED (ошибка сборки): по нему видно, что сборка прошла, а проблема в рантайме (например, превышение лимита памяти). Хвост логов контейнера приходит в поле buildLog.

FIX-0624-3: окно блокирующего пробуждения сервера увеличено до ~5 минут

Было

Блокирующее пробуждение — POST /v1/infra/servers/:id/wake с ?wait=true и автопробуждение спящего сервера при POST /v1/infra/servers/:id/deploy — ждало готовности (статус RUNNING плюс подключённый туннель) примерно до 3 минут, после чего возвращало 504 WAKE_TIMEOUT.

Стало

Основная фаза ожидания увеличена с ~3 до ~5 минут, а с учётом фазы перезагрузки полный потолок до 504 — около 6.5 минуты. Глубоко «остывший» хост (например, спавшая несколько дней галактика) успевает загрузиться и подключиться, а не получает ложный таймаут. Код ошибки, форма ответа и потолок со стороны прокси прежние.

Влияние на интеграторов

Если клиент задаёт собственный таймаут на эти вызовы, заложите около 6.5 минуты вместо 3. Прочее поведение прежнее, переписывать интеграцию не нужно.

NEW-0624-4: параметры placement и graduateFrom при создании сервера

POST /v1/infra/servers получил два необязательных параметра. placement — auto (по умолчанию, поведение прежнее) или dedicated: на портале с моделью размещения galaxies-only значение dedicated создаёт не приложение Galaxy, а отдельную виртуальную машину, проходя те же проверки, что и обычное создание сервера — политику serverCreation и квоту серверов на пользователя. graduateFrom принимает идентификатор вашего приложения Galaxy (kind=GALAXY_APP): после создания выделенного сервера это приложение удаляется. graduateFrom ограничен владельцем — чужой или не-Galaxy идентификатор вернёт 404, ничего не удаляя.

Это аддитивно: без placement или с placement: "auto" запрос ведёт себя в точности как раньше.

Кроме того, при OOM-падении приложения Galaxy ответ POST /v1/infra/servers/:id/deploy с кодом 502 GALAXY_APP_START_FAILED теперь содержит структурную подсказку error.hint с recoveryAction: "graduate-to-dedicated-vm" — как пересоздать приложение на выделенном сервере через placement: "dedicated" и graduateFrom. Подсказка добавляется только когда причина падения — OOM (превышение лимита памяти контейнера), а не обычный краш.

FIX-0624-5: создание сервера с кодом в source.content принимает архивы до 500 МБ

Было

POST /v1/infra/servers с inline-архивом в source.content (одношаговое создание приложения Galaxy) возвращал 413 FST_ERR_CTP_BODY_TOO_LARGE уже на архивах больше ~750 КБ, хотя в документации поля source.content заявлен лимит 500 МБ на тело запроса. Маршрут наследовал глобальный лимит тела 1 МБ.

Стало

Маршрут принимает тело до 500 МБ — как и POST /v1/infra/servers/:id/deploy и POST /v1/infra/servers/:id/upload. Лимит из документации теперь действует на самом деле.

NEW-0624-6: группировка реквизитов по ИНН/ОГРН/КПП в aggregate

POST /v1/requisites/aggregate теперь принимает groupBy по строковым идентификаторам реквизита: rqInn, rqKpp, rqOgrn, rqOgrnip, rqOkpo, rqVatId, rqResidenceCountry, rqCompanyName, а также presetId, entityTypeId, active. Раньше группировка по этим полям возвращала 400 INVALID_PARAMS с пустым списком доступных полей.

Группировка по rqInn — самый быстрый способ найти дубли реквизитов одним вызовом: группы с count > 1 содержат повторяющиеся ИНН. Прежние вызовы (groupBy по entityTypeId/presetId) работают без изменений. Числовые функции (sum/avg/min/max) по этим строковым полям по-прежнему недоступны — они только для группировки.

FIX-0624-7: availableActions спящего сервера показывает wake/start; repair-status сразу `running`

Было

Для спящего сервера без туннеля (SLEEPING + blackholeStatus: DISCONNECTED — обычное состояние остановленного сервера) поле availableActions в ответе 422 SERVER_WRONG_STATE и в GET /v1/me (infra.unhealthyServers) содержало только ["repair","delete"] — без очевидного способа поднять сервер. Отдельно: сразу после POST /v1/infra/servers/:id/repair опрос repair-status в первые миллисекунды мог вернуть {status:"idle"}, и цикл опроса завершался преждевременно.

Стало

availableActions для любого спящего сервера теперь содержит ["wake","start","repair","delete"] — оба действия реально принимаются эндпоинтами /wake и /start. А repair-status выставляет running синхронно при старте ремонта, поэтому первый же опрос видит running, а не idle. Прежние вызовы продолжают работать без изменений.

FIX-0624-8: stage-history?entityType=invoice теперь отдаёт историю смарт-счёта (31)

Было

GET /v1/stage-history?entityType=invoice возвращал историю упразднённого старого счёта (entityTypeId 5, status-based: statusId/statusSemanticId). Текущий смарт-счёт (31) был доступен только под ключом entityType=new-invoice. Клиент, работающий со счетами через /v1/invoices (тип 31), запросив историю под invoice, получал чужой упразднённый тип.

Стало

entityType=invoice отдаёт историю текущего смарт-счёта (entityTypeId 31, stage-based: stageId/stageSemanticId/categoryId) — в одном ряду с /v1/invoices. Ключ new-invoice сохранён как алиас на 31 для обратной совместимости, ломать ничего не нужно.

NEW-0624-9: эндпоинт эмбеддингов bitrix/embeddings

Появился OpenAI-совместимый эндпоинт POST /v1/embeddings — преобразование текста в векторные представления (эмбеддинги) для семантического поиска, кластеризации, дедупликации и поиска похожих карточек CRM. Модель bitrix/embeddings бесплатная и платформенная, свой ключ провайдера не требуется. В поле input принимается строка или массив строк, ответ возвращается в сыром OpenAI-формате: поле object со значением list, массив data с объектами вида { object: "embedding", embedding, index } и блок usage. Поддерживаются необязательные параметры encoding_format (float или base64) и dimensions. Список доступных моделей и их возможностей — GET /v1/models, у модели эмбеддингов выставлена возможность embeddings.

FIX-0624-10: типы полей календарных событий приведены к реальным ответам

Было

GET /v1/calendar-events/fields объявлял rrule как string, а dateCreate и updatedAt — как datetime, хотя на чтение rrule приходит объектом, а dateCreate и updatedAt — строкой в формате региональных настроек портала (не ISO 8601). В схеме числилось поле ownerType, которого в ответах нет. В объекте rrule приходили служебные ключи ~UNTIL и UNTIL_TS, а в элементах списка повторяющихся событий — служебный ключ RINDEX.

Стало

/fields объявляет rrule как object, а dateCreate и updatedAt — как string. Фантомное поле ownerType убрано из схемы. Служебные ключи ~UNTIL, UNTIL_TS и RINDEX больше не попадают в ответы.

Влияние на интеграторов

Документированные поля не изменились — клиент, читавший только их, продолжает работать. Для абсолютной метки времени используйте from и to (ISO 8601). Значения dateCreate и updatedAt не разбирайте фиксированным парсером — их формат зависит от региональных настроек портала.

NEW-0624-11: поле provisionReason в ответе серверов

GET /v1/infra/servers и GET /v1/infra/servers/:id теперь возвращают поле provisionReason со значением oom, crash или null — структурный признак причины ошибки galaxy-приложения. Раньше его отдавали только сессионные роуты кабинета, и в Vibecode API приходилось разбирать свободный текст provisionError. Значение oom — сигнал к «выпуску» приложения на выделенный сервер: создайте сервер с параметрами placement равным dedicated и graduateFrom. Поле необязательное и аддитивное — прежние интеграции работают без изменений.

FIX-0624-12: graduation-сигнал срабатывает при любой нехватке памяти galaxy-приложения

Было

Приложение Galaxy, которому не хватило памяти контейнера — и упёршееся в лимит с перезапусками у предела, и исчерпавшее память сразу на старте (например, грузит большую модель), — классифицировалось как обычный крэш: GET /v1/infra/servers/:id возвращал provisionReason crash, а ответ POST /v1/infra/servers/:id/deploy с 502 GALAXY_APP_START_FAILED шёл без graduation-подсказки. И наоборот, не-OOM краш-луп (необработанное исключение) мог ошибочно помечаться oom.

Стало

Реальная нехватка памяти в любой форме — и мгновенная на старте, и постепенный рост до лимита — надёжно даёт provisionReason oom и error.hint с recoveryAction graduate-to-dedicated-vm. Сборочные ошибки и приложения, которые вообще не стартовали (битая команда запуска), остаются crash без graduation.

Влияние на интеграторов

Менять ничего не нужно: значение поля и подсказка теперь точнее отражают нехватку памяти. Агент может надёжно ловить provisionReason oom для любого исчерпания памяти и «выпускать» приложение на выделенный сервер.

2026-06-23

NEW-0623-10: подсказка по настройке push-доставки в ошибках подписки на события

Ответы об ошибке 400 NOT_OAUTH_APP и 400 NO_USER_TOKEN у POST /v1/infra/servers/:id/event-subscriptions теперь содержат поле error.hint — текстовую инструкцию, как получить сервер под ключом авторизации (vibe_app_) для push-доставки: создать ключ авторизации через POST /v1/apps, авторизовать приложение на портале, создать новый сервер под этим ключом. Отдельной «миграции» существующего сервера с обычного ключа нет. Поле аддитивное — прежние клиенты не затронуты.

FIX-0623-1: список действий бизнес-процессов

Было

GET /v1/bizproc-activities возвращал каждый код действия как объект с числовыми ключами по символам — например, {"0":"D","1":"i", …} вместо строки "DiskRead". Проверка Array.includes(code) не работала.

Стало

Эндпоинт возвращает коды действий массивом строк, как и задокументировано.

FIX-0623-2: ключи ответа поиска дубликатов в camelCase

Было

POST /v1/duplicates/find возвращал ключи объекта data в верхнем регистре (LEAD, CONTACT, COMPANY), в отличие от остального API в camelCase.

Стало

Ключи приходят в camelCase (lead, contact, company); значения (массивы идентификаторов) не меняются.

FIX-0623-3: поле files эпика Scrum массивом идентификаторов

Было

GET /v1/scrum/epics/:id отдавал поле files сырым UF-объектом Битрикс24 (с VALUE_RAW, USER_TYPE_ID и прочими внутренними метаданными).

Стало

files — массив идентификаторов вложений ([417]) или пустой массив, в едином стиле с остальным API.

BC-0623-4: создание ключа авторизации только для администраторов

Поддержка старого формата до: не предусмотрена, ограничение действует сразу

Было

Создать ключ авторизации (POST /v1/keys) мог любой пользователь портала.

Стало

Создание ключа доступно только администраторам портала, остальным запрос отклоняется.

Что делать интеграторам

Создавайте ключи под учётной записью с правами администратора Битрикс24.

FIX-0623-5: заголовок Retry-After при ограничении частоты

Было

При ответе 429 (превышение лимита частоты) заголовок Retry-After не возвращался, и интегратор не знал, через сколько повторить запрос.

Стало

Ответ 429 несёт Retry-After с интервалом в секундах. Используйте его как паузу перед повтором.

FIX-0623-6: удалённый сервер снова отдаёт 404

Было

GET /v1/infra/servers/:id для мягко удалённого сервера возвращал 200 с полным телом и status: "deleted", хотя документация обещает 404. Клиент, опрашивающий эндпоинт и ожидающий 404 как подтверждение удаления, его не получал.

Стало

Эндпоинт возвращает 404 NOT_FOUND для удалённого сервера — так же, как список и удаление, и как описано в документации.

Влияние на интеграторов

Если ваш код полагался на 200 с status: "deleted", переключитесь на проверку 404 (либо на отсутствие сервера в списке) как на признак удаления.

NEW-0623-7: Универсальные списки — полный REST API

Появился раздел Списки (scope lists): программное управление универсальными списками Битрикс24 — самими списками, их полями, разделами и элементами. Это 24 эндпоинта под /v1/lists поверх методов lists.*.

Список адресуется типом инфоблока (iblockTypeId — lists, lists_socnet или bitrix_processes, по умолчанию lists) и идентификатором: числовой сегмент пути трактуется как IBLOCK_ID, строковый — как символьный IBLOCK_CODE. Поля, разделы и элементы доступны вложенными путями.

Если модуль «Универсальные списки» не подключён на портале, вызов возвращает 409 LISTS_MODULE_NOT_ENABLED — это признак выключенного модуля, а не ошибка интеграции.

Затронутые эндпоинты: /v1/lists, /v1/lists/:iblockId, /v1/lists/:iblockId/fields, /v1/lists/:iblockId/sections, /v1/lists/:iblockId/elements

FIX-0623-8: флаг isChildrenListEnabled у связей смарт-процессов

Было

Вложенный флаг связи isChildrenListEnabled принимался только как true/false. Значение Y/N, как у остальных флагов смарт-процесса, молча сохранялось как выключенное.

Стало

POST /v1/smart-processes и PATCH /v1/smart-processes/:entityTypeId приводят Y/N (а также 1/0, yes/no) к true/false для isChildrenListEnabled в связях.

FIX-0623-9: фильтр и выбор пользовательских полей сделок

Было

При фильтрации и выборе пользовательских (UF) полей сделки в форме UF_CRM_* поле отклонялось с UNKNOWN_FILTER_FIELD в фильтре и молча пропускалось из select.

Стало

Пользовательские поля сделок указываются в camelCase (ufCrmCheckOut) и работают без изменений в фильтре и выборе.

Затронутые эндпоинты: GET /v1/deals, POST /v1/deals/search

2026-06-22

NEW-0622-1: связь сделки с контактами

Управление набором контактов сделки: чтение, добавление, замена всего набора, удаление. PUT заменяет весь набор разом. Скоуп crm.

Затронутые эндпоинты: GET/POST/PUT/DELETE /v1/deals/:id/contacts — Контакты сделки

BC-0622-2: список моделей содержит только GA-модели

Поддержка старого формата до: не предусмотрена, экспериментальные модели не входили в стабильный контракт

Было

GET /v1/models и список моделей в /v1/me включали экспериментальные не-GA модели.

Стало

Публичный список содержит только GA-модели. Экспериментальные исключены из списка и отклоняются при вызове.

Что делать интеграторам

Берите модель из актуального ответа GET /v1/models, не зашивайте идентификаторы экспериментальных моделей.

FIX-0622-3: авто-пагинация подразделений

Было

GET /v1/departments возвращал только первую страницу при limit > 50.

Стало

Авто-пагинация собирает все подразделения в один ответ.

2026-06-19

FIX-0619-1: создание документа

Было

POST /v1/documents возвращал 422 и не создавал документ.

Стало

Эндпоинт создаёт документ из шаблона и возвращает запись.

FIX-0619-2: авто-пагинация складов

Было

GET /v1/warehouses и остатки по складу возвращали только первую страницу при limit > 50.

Стало

Авто-пагинация собирает все записи в один ответ.

Затронутые эндпоинты: GET /v1/warehouses, GET /v1/warehouses/:id/stock

FIX-0619-3: сохранение значений списочных пользовательских полей

Было

При создании и обновлении пользовательского поля типа «список» значения списка терялись.

Стало

Значения списка сохраняются при создании и обновлении.

Затронутые эндпоинты: создание, обновление пользовательского поля

FIX-0619-4: частичное обновление позиции корзины

Было

PATCH /v1/basket-items/:id не выполнял частичное обновление позиции.

Стало

Частичное обновление работает, в теле обязательно поле quantity.

FIX-0619-5: фильтр и сортировка настроек открытых линий

Было

У настроек открытых линий фильтр и сортировка работали не для всех полей, а значения при записи не нормализовались.

Стало

Фильтр и сортировка учитывают схему полей, булевы значения при записи приводятся к формату Битрикс24 (Y/N).

2026-06-18

NEW-0618-1: группировка в агрегации сделок

POST /v1/deals/aggregate принимает groupBy: "stageSemanticId" — разбивка по семантике стадии (в работе, успех, провал) для аналитики воронки.

NEW-0618-2: пагинация и фильтр истории стадий

GET /v1/stage-history поддерживает пагинацию (meta.total, meta.hasMore) и фильтр по типу сущности entityTypeId.

FIX-0618-3: учёт времени задачи не переназначает автора

Было

PATCH /v1/tasks/:taskId/time/:id принимал поле userId, но Битрикс24 не переназначает автора записи — значение молча игнорировалось.

Стало

Поле userId отклоняется с 400 — автора записи учёта времени сменить нельзя.

Влияние на интеграторов

Не передавайте userId при обновлении записи учёта времени.

FIX-0618-4: таймзона события календаря

Было

PATCH /v1/calendar-events/:id мог сохранять время в таймзоне пользователя Битрикс24, а не самого события.

Стало

Таймзона события сохраняется при обновлении.

2026-06-17

NEW-0617-1: База знаний 2.0 (note.*)

Коллекции, документы и вложения базы знаний: создание, изменение, архивирование и удаление баз знаний и документов, загрузка вложений. Скоуп note.

Затронутые эндпоинты (методы записи): POST /v1/note/collections, POST /v1/note/documents, POST /v1/note/documents/:documentId/files, а также парные PATCH и DELETE. Методы чтения добавлены отдельной записью.

2026-06-16

NEW-0616-1: AI follow-up завершённых звонков

AI follow-up по завершённым звонкам. Скоуп call.

В процессе раскатки — методы выходят в обновлении Битрикс24 call 26.600.0 и доступны не на всех порталах. Пока обновление не приехало на портал, метод возвращает 422 METHOD_NOT_YET_AVAILABLE с целевой версией в ответе — это признак раскатки, а не ошибка интеграции.

Затронутые эндпоинты: POST /v1/calls/followups/list, GET /v1/calls/followups/:callId

FIX-0616-2: формат ответа транскрипции

Было

POST /v1/audio/transcriptions всегда возвращал JSON-объект, даже при response_format=text, srt или vtt.

Стало

text, srt, vtt отдают сырое тело в формате text/plain, SubRip или WebVTT. json и verbose_json отдают JSON-объект.

2026-06-12

BC-0612-1: поле payed заказа только для чтения

Поддержка старого формата до: не предусмотрена, поле стало read-only

Было

payed принимался в теле создания и обновления заказа.

Стало

payed доступно только для чтения — при записи отклоняется.

Что делать интеграторам

Уберите payed из тела POST /v1/orders и PATCH /v1/orders/:id.

Затронутые эндпоинты: POST /v1/orders, PATCH /v1/orders/:id

2026-06-11

NEW-0611-1: Scrum API

Эпики, привязка задач к эпикам и чтение чата задачи. Скоуп tasks.

Затронутые эндпоинты: /v1/scrum/epics, /v1/scrum/epics/:id, /v1/scrum/tasks/:taskId — раздел Scrum

NEW-0611-2: оценка звонка при завершении

POST /v1/calls/:callId/finish принимает оценку завершённого звонка и передаёт её в Битрикс24.

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