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

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

### NEW-0806-1: 402 при исчерпанной квоте Cowork/Code несёт заголовок Retry-After

Ответ `402` с кодом `cowork_quota_exhausted` на [POST /v1/chat/completions](/docs/ai/chat/completions) теперь несёт заголовок `Retry-After` — число секунд до сброса исчерпанного окна квоты (`5h`, `week` или `month`). Раньше момент сброса был виден только в поле `resetAt` тела ответа; заголовок понимают и обычные HTTP-клиенты без разбора тела. Ответ `402` с кодом `insufficient_balance` заголовок не несёт — у пустого баланса нет времени сброса.

### FIX-0806-2: Веб-поиск: статус ответа различает отказ ключа провайдером и сбой провайдера

**Было**

Любая ошибка поискового провайдера в [POST /v1/search](/docs/search/run) приходила как `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](/docs/keys-auth/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](/docs/entities/tasks/create) и [PATCH /v1/tasks/:id](/docs/entities/tasks/update) отвечали `400 READONLY_FIELD` на `changedBy`, `closedBy`, `statusChangedBy`, `createdDate`, `changedDate`, `closedDate` и до Битрикс24 не доходили. Так же отклонялись написания верхним регистром — `CHANGED_BY` и остальные. Отказ приходил и в подзапросе [POST /v1/batch](/docs/batch). Седьмое поле, `createdBy`, при создании работало, а при обновлении отклонялось.

**Стало**

Все семь принимаются на обеих операциях и на всех трёх поверхностях записи — одиночный маршрут, пакетный запрос сущности и общий пакетный запрос. Принимаются оба написания, `createdBy` и `CREATED_BY`. Значение применяется в пределах прав вызывающего пользователя: если Битрикс24 отказывает в правке задачи, отказ приходит как есть — `422` с его собственным текстом, без подмены на нашу ошибку и без ложного успеха.

Смену постановщика Битрикс24 пишет в журнал изменений задачи, и там остаётся настоящий вызывающий пользователь. Для остальных шести полей записи в журнале не предусмотрено. Ещё одна тонкость — у трёх дат значение без часового пояса в написании `createdDate` получает смещение из заголовка `X-Vibe-Timezone`, а в написании `CREATED_DATE` уходит как есть. Обе тонкости разобраны на странице [PATCH /v1/tasks/:id](/docs/entities/tasks/update).

Что НЕ изменилось: `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](/docs/apps/update) до карточки не доходила.

**Стало**

Карточка ведёт по полному адресу связанного приложения вместе с подпутём, query и fragment, если этот адрес указывает на тот же поддомен Black Hole, что и сервер карточки. Во всех остальных случаях — прежний корень сервера: другой поддомен, свой домен, другая схема, пустой или неразбираемый адрес.

Правка `appUrl` через [PATCH /v1/apps/:id](/docs/apps/update) теперь ставит карточку в очередь на обновление, поэтому новый адрес доезжает до Битрикс24 сам. Уже опубликованные карточки тоже обновлены — от интегратора действий не требуется.

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

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

### FIX-0806-6: распознавание речи сообщает о временной паузе провайдера

**Было**

При временной недоступности кластера [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) мог отвечать `502 ai_provider_unavailable`, не сообщая клиенту, сколько ждать перед повтором.

**Стало**

В этом состоянии метод отвечает `429 ai_provider_cooldown` с заголовком `Retry-After` в секундах. Запрос не выполняется и не списывает квоту или деньги. Дождитесь указанного интервала и повторите тот же запрос.

### FIX-0806-7: распознавание речи действительно ждёт ответа заявленные 15 минут

**Было**

Для длинной записи [POST /v1/audio/transcriptions](/docs/ai/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`](/docs/infra/servers/list) и [`GET /v1/infra/servers/:id`](/docs/infra/servers/get) отдавали `status` строчными (`running`, `sleeping`), а [`POST /v1/infra/servers/:id/wake`](/docs/infra/lifecycle/wake), [`POST /v1/infra/servers/:id/refresh`](/docs/infra/lifecycle/refresh), поле `currentState.status` в ответах 422 у [`POST /v1/infra/servers/:id/start`](/docs/infra/lifecycle/start), [`POST /v1/infra/servers/:id/stop`](/docs/infra/lifecycle/stop), [`POST /v1/infra/servers/:id/reboot`](/docs/infra/lifecycle/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](/docs/infra/deploy/deploy) принимает обе формы там, где платформа включила выкладку по ссылке для вашего аккаунта Битрикс24; где не включила — код `GALAXY_DEPLOY_CONTENT_ONLY` возвращается как прежде, и встроенный `source.content` продолжает работать всегда.

Ссылку скачивает сам хост, поэтому архив не проезжает через тело запроса: потолок на встроенный архив (`413 GALAXY_UPLOAD_TOO_LARGE`) на этот путь не распространяется, и слот одновременных «толстых» запросов (`429 DEPLOY_BACKEND_BUSY`) он не занимает. `source.versionId` выкладывает версию, уже лежащую в [хранилище исходников](/docs/source-storage): платформа сама минтит подписанную ссылку и связывает версию с этим деплоем, поэтому в истории видно, что именно уехало в прод.

Ссылка у galaxy-приложения ведёт только в хранилище исходников — подойдёт подписанная ссылка сохранённой версии. Сторонний адрес отклоняется кодом `400 GALAXY_SOURCE_URL_NOT_ALLOWED`, поэтому крупный архив сначала сохраняют версией, а выкладывают по `source.versionId`. У отдельной виртуальной машины такого ограничения нет.

Создание сервера с источником ([POST /v1/infra/servers](/docs/infra/servers/create)) принимает `source.url` на тех же условиях. `source.versionId` там не принимается: сервера, чьё хранилище задавало бы область поиска версии, в момент создания ещё нет — выкладывайте версию вторым шагом, через `/deploy`.

### FIX-0806-11: справочник полей элементов смарт-процессов больше не показывает поле contacts

**Было**

[GET /v1/items/:entityTypeId/fields](/docs/entities/items/fields) показывал поле `contacts` с типом `crm_contact`. Значения по нему не приходило ни в списке, ни в карточке элемента, а записать его было нельзя: Битрикс24 принимал только пустой массив, а любое непустое значение отклонял ошибкой своего внутреннего слоя данных. Поле попадало в справочник сквозным пробросом схемы Битрикс24, а не объявлялось платформой.

**Стало**

Поле убрано из справочника. Привязанные контакты читаются и пишутся через `contactId` и `contactIds` — они не изменились. Фильтр и сортировка по `contacts` как и раньше отклоняются с кодом `UNKNOWN_FILTER_FIELD` и `UNKNOWN_SORT_FIELD`.

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

Действий не требуется: значения по полю не существовало, поэтому клиент, читавший его, всегда получал пустоту. Если вы строили модель данных по справочнику — уберите `contacts` из неё и опирайтесь на `contactIds`.

### FIX-0806-12: справочник полей страниц сайта сообщает, какие поля могут быть пустыми

**Было**

Ответ [GET /v1/pages/fields](/docs/entities/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](/docs/entities/sites/fields) теперь отдаёт подпись `label` и описание `description` у всех 22 полей — раньше они были только у поля `type`, а остальные 21 приходили с одним типом и признаком «только для чтения». Описания сообщают то, чего по типу не видно: что `active` через API не устанавливается, что `code` хранится в форме, обрамлённой слешами, что `landingIdIndex`/`landingId404`/`landingId503` задаются только при обновлении, а `dateCreate` и `dateModify` приходят строкой в формате локали портала, а не в ISO 8601.

[GET /v1/users/fields](/docs/entities/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](/docs/keys-auth/guide) (блок `fieldsDetailed` сущности) и схема OpenAPI (`x-enumValues` у свойства). Подписи и описания объявленных полей схемы отдаёт только OpenAPI (`title` и `description` у свойства), поэтому клиент, сгенерированный по схеме, получает их без дополнительных вызовов; путеводитель подписи не несёт намеренно. Подписи и описания десяти полей рабочих сведений приходят только в самом справочнике полей.

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

### FIX-0806-15: разделы товаров: поле sort помечено «только для чтения» и «не возвращается»

**Было**

`GET /v1/product-sections/fields` объявлял `sort` записываемым, [POST /v1/product-sections](/docs/entities/product-sections/create) и [PATCH /v1/product-sections/:id](/docs/entities/product-sections/update) принимали его без ошибки, а Битрикс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](/docs/entities/sites/create) и [PATCH /v1/sites/:id](/docs/entities/sites/update) возвращали успех. Значение при этом терялось. Битрикс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](/docs/infra/deploy/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](/docs/infra/deploy/deploy).

### BC-0806-20: расход квоты отдаётся только в процентах

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

**Было**

[GET /v1/ai/quota](/docs/ai/consumption/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](/docs/ai/consumption/usage) или снимайте на вызове модели: ответ [POST /v1/chat/completions](/docs/ai/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](/docs/keys-auth), [PATCH /v1/keys/{id}](/docs/keys-auth),
[POST /v1/keys/{id}/rotate](/docs/keys-auth)

Смежное изменение ответа: у личного ключа в поле `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` несут понятную причину вместо пустоты. Значение аддитивное: прежние коды не менялись. Отказ терминальный — автоматически платформа его не повторяет.
