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

Изменения API: 5 августа 2026

← Журнал изменений · Август 2026

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) больше не отказывает из-за несовпадения ключей.

Что по-прежнему не так, как хотелось бы

Старый ключ теряет доступ к серверу и приложению НЕМЕДЛЕННО, в момент перевыпуска — а не по истечении льготного периода. Формально ключ ещё активен эти часы, но сервер и приложение уже переехали на новый, поэтому вызовы старым ключом к этому серверу перестают находить его сразу.

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

Менять ничего не нужно. Клиент, ловивший пропажу сервера после ротации как постоянную проблему, теперь видит непрерывный доступ — кроме самого старого ключа, который перестаёт видеть сервер раньше, чем истекает формально.

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 записей обрабатываются как прежде.