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

[← Журнал изменений](/docs/changelog) · [Август 2026](/docs/changelog/2026-08)

### FIX-0805-1: перевыпуск ключа больше не осиротит зарегистрированного им бота

**Было**

Бот, зарегистрированный через [POST /v1/bots](/docs/bots), запоминает ключ, которым его завели. После [POST /v1/keys/:id/rotate](/docs/management-keys) эта привязка оставалась на старом ключе: новый ключ получал `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](/docs/management-keys) обходила стороной серверы на общем хосте (`kind=GALAXY_APP`): такой контейнер оставался за старым ключом, и после окончания льготного периода деплой, выполнение команд, загрузка файлов и просмотр логов новым ключом переставали его находить.

**Стало**

Контейнер на общем хосте переключается на новый ключ вместе с остальными серверами. Исключение осталось ровно одно и узкое: контейнер не переводится на ключ авторизации приложения (`vibe_app_`) — такая привязка необратима и ломает выкладку.

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

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

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

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

### FIX-0805-3: перевыпуск ключа больше не отрывает от него сервер и приложение

**Было**

После [POST /v1/keys/:id/rotate](/docs/management-keys) сервер и приложение, созданные с помощью этого ключа, продолжали внутренне числиться за старым ключом. Когда старый ключ истекал по окончании льготного периода, вызовы деплоя, выполнения команд, загрузки файлов и просмотра логов такого сервера с новым ключом переставали находить сервер.

**Стало**

После перевыпуска сервер, приложение и его живые токены доступа (`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](/docs/apps/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](/docs/apps/placements/available) отдаёт по каждому коду три новых поля — `requiredScope`, `requiresIconName`, `restBindable` — и показывает десять кодов, которые привязывались, но в справочнике не значились, включая вкладки и панели задач. Блок `placements.bindPrerequisite` в [данных ключа](/docs/keys-auth/me) описывает требование права заранее.

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

Менять вызовы не нужно. Если вы разбираете отказы привязки по коду — добавьте три новых кода; если полагались на обязательность `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](/docs/entities/requisite-links/register) сообщал, что вместо 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](/docs/entities/items/update)) и у предложений ([PATCH /v1/quotes/:id](/docs/entities/quotes/update)) было помечено только для чтения, поэтому запись отклонялась с `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](/docs/entities/payments/fields) — 44 поля, [GET /v1/basket-items/fields](/docs/entities/basket-items/fields) — 27, [GET /v1/pages/fields](/docs/entities/pages/fields) — 27, [GET /v1/catalog-sections/fields](/docs/entities/catalog-sections/fields) — 10, [GET /v1/items/:entityTypeId/fields](/docs/entities/items/fields) — 34, а у [GET /v1/statuses/fields](/docs/entities/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`](/docs/infra/deploy/deploy) и [`POST /:id/upload`](/docs/infra/deploy/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`](/docs/infra/lifecycle/repair-status) не изменились; при провале обоих путей
поле `error` содержит обе причины через `; serial fallback:`. Резервная попытка добавляет до
двух минут к уже неуспешному вызову.

### FIX-0805-22: встраивание: Битрикс24 назвал причину — называем её и мы

**Было**

Когда Битрикс24 отказывал в привязке места встраивания словами «приложение не найдено» или «доступ запрещён», [POST /v1/placements/bind](/docs/apps/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](/docs/apps/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](/docs/feedback) отвечал `429 RATE_LIMITED`, даже когда ваш ключ отправил меньше пяти обращений в минуту: счётчик был общим для всех вызывающих сразу, поэтому чужой поток обращений исчерпывал ваш лимит. Наблюдалось это как редкий необъяснимый отказ на первом же вызове.

**Стало**

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

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

Менять ничего не нужно. Отказы, вызванные чужим потоком обращений, на этом методе исчезают. Пороговое число оставьте прежним: обработку `429 RATE_LIMITED` с повтором по заголовку `Retry-After` стоит сохранить — она по-прежнему единственный надёжный способ узнать свой лимит.

### FIX-0805-26: пагинация конфигураций открытых линий: окно больше не смещается дважды

**Было**

[GET /v1/openline-configs](/docs/openlines/config/list) и [POST /v1/openline-configs/search](/docs/openlines/config/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 записей обрабатываются как прежде.
