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

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

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

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

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

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

## 2026-08-11

### NEW-0811-1: тарифы серверов называют единицу цены

В ответе [GET /v1/infra/providers/{providerId}/plans](/docs/infra/providers/plans) у каждого тарифа появилось поле `currency` со значением `"Vibes"` — единица, в которой считаются `priceMonthly` и `sleepPriceMonthly`. Раньше цены приходили безымянными числами, и единицу приходилось брать из текста документации; теперь это та же машинная пометка, что у стоимости поиска в `GET /v1/me`.

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

### NEW-0811-2: каталог данных приложения объявляется в теле деплоя

Приложение на отдельной виртуальной машине запускается под непривилегированной учётной записью, и платформа передавала ей во владение только каталог распаковки. Каталог состояния вне него — тот самый `/opt/data`, который наша документация советует для данных, переживающих выкат, — создаётся администраторскими шагами `install` и `preStart`, поэтому оставался за администратором, и первая же запись из приложения падала с ошибкой доступа. Обойти это можно было только ручной раздачей прав в `preStart` на каждом деплое.

**Было**

Приложение писало только в свой каталог распаковки. Для каталога состояния декларативного способа не было.

**Стало**

В теле [POST /v1/infra/servers/{id}/deploy](/docs/infra/deploy/deploy) появились два необязательных поля. `dataDirs` — до восьми каталогов вне каталога распаковки, которые платформа создаёт и передаёт учётной записи приложения на каждом деплое, а при откате возвращает обратно. Путь — абсолютный, нормализованный, внутри `/opt`, `/srv` или `/var/lib`; сами корни отвергаются новым кодом `INVALID_DATA_DIRS` до того, как деплой займёт сервер. `dataDirsRecursive` дополнительно передаёт содержимое объявленных каталогов — нужно только для заранее развёрнутого дерева; для `/opt/data` не принимается, потому что там по нашему же рецепту восстановления базы лежит файл с паролем.

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

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

### NEW-0811-3: выпуск ключа ровно с выбранными платформенными правами

Тело `POST /v1/keys` принимает необязательное поле `exactScopes`. С `exactScopes: true` ключ сохраняет ровно те права, что перечислены в `scopes`: четыре платформенных (`vibe:infra`, `vibe:ai`, `vibe:search`, `vibe:storage`) не дописываются при выпуске, и `vibe:ai` / `vibe:search` не добавляются к правам запроса на лету. Поэтому `GET /v1/me` возвращает ровно сохранённый набор, а ключ без `vibe:infra` отвечает `403 INFRA_SCOPE_REQUIRED` на `POST /v1/infra/servers`.

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

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

### NEW-0811-4: выгодные часы видны в подписке Cowork/Code и в отказе по исчерпанной квоте

В отдельные часы недели квота расходуется медленнее — тот же вызов забирает меньшую долю лимита. Раньше об этом можно было узнать только из расписания выгодных часов, теперь то же самое видно в ответах подписки Cowork/Code и в отказе по исчерпанной квоте, поэтому приложение может предложить перенести объёмную задачу на выгодный час, не собирая расписание само.

| Ответ | Что появилось |
|---|---|
| [GET /v1/off-peak](/docs/ai/consumption/off-peak) | `currentWindowEndsInHours` — через сколько часов расход перестанет быть таким же выгодным. `null`, если в пределах недели вперёд дороже не станет |
| [GET /v1/cowork/me](/docs/cowork/me) | Блок `offPeak` — действует ли скидка сейчас, какой множитель расхода и когда наступит ближайший выгодный час. Без сетки часов |
| [GET /v1/cowork/state](/docs/cowork/state) | Тот же блок `offPeak` вместе с сеткой часов на неделю и поле `touSavedPct` — какую долю расхода за текущий расчётный период сняли выгодные часы |
| Отказ 402 `cowork_quota_exhausted` на [POST /v1/chat/completions](/docs/ai/chat/completions) | `offPeakHint` — через сколько часов снимется блокировка (`inHours`) и какой множитель расхода будет действовать в этот момент (`multiplier`). Приходит, только когда этот момент попадает в час со скидкой |

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

Множитель — коэффициент расхода, а не размер скидки. Значение `0.5` означает, что вызов забирает половину той доли квоты, которую забрал бы без скидки.

Поля блока, примеры ответов и особенности — [Выгодные часы в подписке Cowork/Code](/docs/cowork/off-peak).

### FIX-0811-5: ближайший выгодный час отсчитывается от границы часа, а не от минуты запроса

**Было**

Поле `nextWindow.inHours` в ответе [GET /v1/off-peak](/docs/ai/consumption/off-peak) отсчитывалось от момента запроса. Расписание почасовое, поэтому ответ «через 2 часа», полученный в 10:55, указывал на 12:55 — на середину выгодного часа, начавшегося в 12:00. Клиент, прибавивший это число к моменту запроса, попадал в дешёвый час, когда большая его часть была уже позади. Так же вёл себя тот же отсчёт в карточке выгодных часов в кабинете (`GET /api/ai/tou`).

**Стало**

Отсчёт идёт от границы текущего часа в часовом поясе расписания. То же «через 2 часа», полученное в любую минуту между 10:00 и 11:00, указывает на 12:00 — на начало выгодного часа. Поле стало тем, чем его описывает документация, — ближайшим часом, который дешевле текущего. Расхождение с прежним прочтением не превышает одного часа. То же исправление действует в карточке выгодных часов в кабинете.

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

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

## 2026-08-10

### NEW-0810-1: выдача ссылки-приглашения отмечается в журнале доступа портала

Ответ [POST /v1/infra/servers/:id/access-tokens](/docs/infra/access-tokens) не изменился — меняется то, что происходит на стороне портала.

**Было**

Выдача ссылки с `mode=share-url` не оставляла следа в надзорном слое портала: администратор не видел, кто и когда открыл приложение ссылкой.

**Стало**

После успешной выдачи ссылки платформа записывает смену открытости в журнал портала, а если ссылка не требует входа в Битрикс24 (`identityBound=false`) и приложение до этого не было открыто наружу — отправляет администраторам портала сообщение в чат-бот со ссылкой на список открытых приложений.

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

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

### BC-0810-2: ошибка фильтра в POST /v1/{entity}/batch стала ошибкой подвызова, а не всего запроса

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

**Было**

Один неверный ключ фильтра в любом подвызове [POST /v1/{entity}/batch](/docs/batch) отменял
весь запрос: ответ `400`, код в `error.code`, результаты остальных подвызовов
отбрасывались. Глобальный [POST /v1/batch](/docs/batch) вёл себя иначе — клал ошибку рядом с
конкретным подвызовом и выполнял остальные.

**Стало**

Обе поверхности ведут себя одинаково. Ответ — `200`, код отказа лежит в `data[i].error.code`
для того подвызова, который его вызвал; остальные подвызовы выполняются и возвращают данные.
Сам набор отказов и их коды не изменились — изменился только радиус: `UNKNOWN_FILTER_FIELD`,
`INVALID_FILTER_OPERATOR`, `INVALID_FILTER_FIELD`, `UNSUPPORTED_FILTER`.

Прежний текст ошибки начинался с `Call at index N:` — теперь позиция подвызова видна по его
месту в массиве `data`, и префикс убран.

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

Если код читает отказ фильтра как `HTTP 400` с `error.code`, добавьте разбор `200` с
`data[i].error.code` — иначе подвызов, у которого фильтр не принят, будет прочитан как
успешный. Проверяйте наличие `error` у каждого элемента `data`, как это уже делается для
глобального `POST /v1/batch`.

### BC-0810-3: чтение через батч больше не обходит выключенную операцию сущности

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

**Было**

Сущность может выключать отдельную операцию — обычно потому, что универсальный
обработчик для неё неверен: настоящий список живёт на собственном маршруте с другим
конвертом, либо метод Битрикс24 не понимает фильтр и отдаёт таблицу целиком. Одиночные
маршруты это учитывали, запись через батч — тоже, а чтение через батч — нет. Поэтому
[POST /v1/{entity}/batch](/docs/batch) и [POST /v1/batch](/docs/batch) с `action` `list`,
`get`, `search` или `fields` отвечали `200` там, где та же операция на своём маршруте
отвечает `404`
— и отдавали ровно тот результат, ради отказа от которого операцию и выключили.

Отдельно: устаревший `GET /v1/{entity}/aggregate` вообще не спрашивал, есть ли у сущности
агрегация. У сущности, где все числовые поля — идентификаторы, `POST /v1/{entity}/aggregate`
отвечает `404`, а этот адрес отвечал `200`.

**Стало**

Оба батча отвечают на выключенную операцию `400` с кодом `ACTION_NOT_SUPPORTED` до вызова
Битрикс24: в per-entity батче это ответ всего запроса, в глобальном — ошибка конкретного
подвызова (остальные подвызовы выполняются). Устаревший `GET /v1/{entity}/aggregate`
регистрируется по тому же признаку, что и `POST`-версия, поэтому у сущности без агрегации
теперь `404` на обоих адресах.

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

Затронуты только те сущности и операции, где ответ и раньше был неверным. Проверить стоит
все четыре чтения — `list`, `get`, `search`, `fields`: у шести сущностей выключен именно
`search` при работающем `list`, поэтому пачка, которая раньше проходила целиком, теперь
вернёт `ACTION_NOT_SUPPORTED` на этом подвызове. Полный набор действий
сущности — в `operations.batch` ответа `GET /v1/guide` и в описании OpenAPI; `data.batch` в
`GET /v1/{entity}/fields` перечисляет только действия ЗАПИСИ и на вопрос про чтения не отвечает. У сущности, где выключено всё,
маршрут остаётся на месте, но любое действие отвечает `ACTION_NOT_SUPPORTED` — отказ
называет действие, чего `404` на адресе сказать не может.

### BC-0810-4: `filter`, переданный не объектом, отклоняется вместо тихой потери

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

> Как и в соседней записи про неизвестное имя поля, «старый формат» здесь означает неверный
> ответ, а не рабочий.

**Было**

`filter` — объект условий, но передать вместо него строку, число, булево значение или массив
никто не мешал. Строка и массив уходили в Битрикс24 как есть, число и булево превращались в
пустой отбор — и во всех случаях запрос отвечал `200` со ВСЕЙ коллекцией:

```
POST /v1/tasks/search   { "filter": [{ "responsibleId": 1 }] }   →  200, все задачи портала
```

**Стало**

Такой запрос отклоняется до вызова Битрикс24 — `400` с кодом `INVALID_FILTER_SHAPE`; в
сообщении сказано, что именно пришло вместо объекта, и показана правильная форма.

Действует на всех сущностях и на всех поверхностях, где `filter` приходит в теле: поиск,
агрегация и оба батча.

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

Передавайте `filter` объектом. В строке запроса это отдельная история: там условия пишутся
скобочной формой (`?filter[responsibleId]=1`), а `filter`, закодированный в JSON одной
строкой, с этой же поставки не отклоняется, а разбирается и применяется — раньше он молча
терялся и возвращал всю коллекцию.

### BC-0810-5: неизвестное имя поля в фильтре отклоняется ещё на пятнадцати сущностях

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

> До сих пор такой фильтр отвечал `200` и всей коллекцией — то есть «старый формат» здесь
> означает неверный ответ, а не рабочий.

**Было**

Фильтр по имени, которого у сущности нет, уходил в Битрикс24. Битрикс24 такой ключ не
отклоняет — он молча его выбрасывает и отвечает `200` со ВСЕЙ коллекцией. Опечатка в имени
поля поэтому выглядела как успешный запрос с неправдоподобно большим результатом:

```
GET /v1/tasks?filter[responsable]=1     →  200, все задачи портала
```

Так вели себя [задачи](/docs/entities/tasks/list), [пользователи](/docs/entities/users/list),
[рабочие группы](/docs/entities/workgroups/list), [реквизиты](/docs/entities/requisites/list),
[банковские реквизиты](/docs/entities/bank-details/list),
[шаблоны реквизитов](/docs/entities/requisite-presets/list),
[адреса](/docs/entities/addresses/list), [сайты](/docs/entities/sites/list),
[страницы](/docs/entities/pages/list), [комментарии таймлайна](/docs/entities/timelines/list),
[шаблоны бизнес-процессов](/docs/entities/bizproc-templates/list), настройки открытых линий и
элементы универсальных списков.

**Стало**

Такой запрос отклоняется до вызова Битрикс24 — `400` с кодом `UNKNOWN_FILTER_FIELD` и списком
доступных имён в сообщении — этот список и есть точный ответ на вопрос «по чему можно
фильтровать».

Проверяется только имя: операторы, диапазоны, `$in`/`$nin` и логика И работают как раньше.
Кроме объявленных полей принимаются пользовательские поля (`UF_*`, `ufCrm*`) и — у сущностей,
где он объявлен, — ключ `id`.

Отдельно: у [действий](/docs/entities/bizproc-activities/list) и
[роботов бизнес-процессов](/docs/entities/bizproc-robots/list) метод Битрикс24 не принимает
фильтр ни в каком виде, поэтому там отклоняется любой ключ фильтра — код `UNSUPPORTED_FILTER`.

Полное описание — [Фильтрация и поиск](/docs/filtering#неизвестное-имя-поля-в-фильтре).

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

Сверьте имена полей в своих фильтрах со списком из сообщения об ошибке. Запрос, который
раньше «работал», но возвращал больше записей, чем должен был, теперь ответит `400` с точным
указанием, какое имя не найдено — это и есть его исходная ошибка. Остальные фильтры не
затронуты.

### FIX-0810-6: нехватка места на общем хосте — отдельный повторяемый отказ деплоя, а не поломка приложения

**Было**

Когда на общем хосте galaxy-приложения кончалось свободное место, сборка падала, и [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) отвечал `502 GALAXY_APP_BUILD_FAILED` — тем же кодом, что и ошибка в исходниках самого приложения. Приложение при этом помечалось сломанным, хотя работать не переставало: отказ происходил на сборке, до подмены контейнера, поэтому предыдущая версия продолжала отвечать на запросы. Отличить нехватку места от настоящей ошибки сборки можно было только по хвосту лога в `buildLog`, а повтор того же деплоя давал тот же результат.

**Стало**

Тот же случай возвращает `502 GALAXY_LOW_DISK` с признаком `retryable: true` и структурированным полем `error.hint`: причина, что сделать и предупреждение не удалять слот. Приложение не помечается сломанным — слот, контейнер и его том с данными целы, а уже работающая версия обслуживает трафик дальше. Повторять деплой имеет смысл после того, как на хосте освободили место: само оно не освобождается, поэтому немедленный повтор упирается в тот же отказ. Новый код перечислен в машинном описании контракта деплоя, которое отдают [GET /v1/me](/docs/quickstart) и `GET /v1/openapi.json`.

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

Менять ничего не нужно: успешные деплои не затронуты. Ветку на `GALAXY_APP_BUILD_FAILED` оставьте — она по-прежнему приходит на настоящих ошибках сборки, а у нехватки места теперь есть отдельный код, по которому видно, что дело не в исходниках. Автоматический повтор бывает только там, где платформа сама пересобирает уже существующее приложение: там попытки идут без участия клиента, пока не истечёт отведённый на ожидание бюджет. При создании приложения сразу с исходниками отказ приходит сразу и не повторяется — решение о повторе остаётся за вами.

### FIX-0810-7: Открытие из каталога Битрикс24 предупреждает, что приложение не авторизовано

**Было**

Приложение, открытое из каталога Битрикс24 пользователем, который ещё не выдал ему доступ, запускалось как обычно, но шлюз не проставлял заголовок `X-Vibe-Authorization`. Вызовы к API отвечали `401`, и приложение показывало текст, который наша же документация предписывала для `401`, — «откройте приложение из меню Битрикс24». Совет был тупиковым: пользователь именно так и открыл, а открытие из каталога доступ не выдаёт.

**Стало**

Такой запуск платформа перехватывает и показывает экран с кнопкой «Авторизовать приложение»; второй путь — один раз открыть приложение через встройку в меню Битрикс24. Там же есть неприметная ссылка «открыть без авторизации» на тот же адрес запуска — приложение, которому сессия не нужна, открывается как прежде. Экран показывается только там, где кнопке есть куда вести: облачный портал, ключ приложения и зарегистрированное OAuth-приложение. Во всех остальных случаях запуск идёт как раньше.

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

Менять код не нужно, и ни один запуск не становится недоступным. Стоит поправить только текст своей ошибки на `401`: у неё две причины — истёкшая сессия и не выданный доступ, — поэтому «откройте из меню» как единственная формулировка вводит пользователя в заблуждение. Рекомендация обновлена в разделе [Среда выполнения приложения](/docs/infra/app-runtime).

### NEW-0810-8: список документов CRM появился в машинной схеме OpenAPI

Эндпоинт [GET /v1/crm-documents](/docs/entities/documents/crm-list) теперь описан в схеме OpenAPI, которую отдаёт `/v1/openapi.json`. Сам вызов работал и раньше, но клиенты и ИИ-агенты, которые строят интеграцию по машинному описанию, считали его несуществующим. В описании указаны обязательный параметр `entityTypeId`, необязательные `entityId`, `select`, `order` и `start`, требуемый доступ `crm`, форма ответа с массивом документов и блоком `meta` с полями `total`, `start` и `next`, а также коды отказа `MISSING_PARAMS`, `INVALID_ENTITY_ID`, `INVALID_START`, `TOKEN_MISSING` и `SCOPE_DENIED`. Параметр размера страницы эндпоинт не принимает и в описании его нет: за следующей страницей идут со значением `meta.next` в `start`. Поведение самого эндпоинта не изменилось.

### FIX-0810-9: при входе по прямой ссылке приложение получает имя и токен посетителя

**Было**

Приложение, открытое по прямой ссылке на свой адрес (а не плиткой из Битрикс24), получало имя посетителя из его учётной записи Вайбкода, а не из карточки сотрудника: участник нескольких Битрикс24 видел имя, под которым он записан в другом из них. Заголовок `X-Vibe-Authorization` на этом пути не приходил вовсе — приложение не могло обратиться в Битрикс24 от лица посетителя.

**Стало**

`X-Vibe-User-Name` и `X-Vibe-User-Name-Encoded` несут имя из карточки сотрудника того Битрикс24, которому принадлежит приложение — как и при открытии плиткой. Токен доступа `X-Vibe-Authorization` резолвится по номеру сотрудника, поэтому приходит и здесь.

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

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

### FIX-0810-10: пользовательские поля счёта

**Было**

Пользовательские поля счёта нельзя было прочитать или создать ни одним путём.
[GET /v1/userfields/invoices](/docs/userfields/smart-processes) отвечал ошибкой `UNKNOWN_ENTITY`, а
[GET /v1/items/31/userfields](/docs/userfields/smart-processes) — ошибкой доступа от Битрикс24,
потому что счёт адресовался так же, как обычный смарт-процесс.

**Стало**

Оба пути работают и дают одинаковый результат: шесть операций (список, справочник типов,
чтение, создание, изменение, удаление) над пользовательскими полями счёта. Путь
`/v1/userfields/invoices` добавлен для тех, кому удобнее обращаться по имени сущности, а не по
числовому идентификатору типа.

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

Менять ничего не нужно. Речь идёт о счетах в текущем виде; счета старого образца через API
по-прежнему недоступны.

### BC-0810-11: Справочник полей товарных позиций описывает то, что реально приходит в ответах

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

**Было**

[GET /v1/deals/{id}/products/fields](/docs/entities/deals/products-fields) отдавал набор полей Битрикс24 как есть, и он расходился с ответами самих товарных позиций сразу в трёх местах. Сумма скидки называлась в справочнике `discountSum`, а в данных и при записи — `discount`. Внешний код позиции и цена в валюте отчёта приходили в каждой строке, но в справочнике отсутствовали. Владелец строки, тип владельца и склад, наоборот, были в справочнике, но из ответов вырезались.

Хуже того, клиент, сгенерировавший запись по этому справочнику, отправлял `discountSum` — обёртка это имя не распознавала, отвечала `201 Создано` и молча выбрасывала скидку. То же происходило с любым другим неизвестным полем: ответ об успехе, данные не записаны.

**Стало**

Справочник собирается из тех же таблиц, по которым формируются сами ответы, поэтому разъехаться они больше не могут. Скидка называется `discount` — как в данных, при записи и в документации. Прежнее имя `discountSum` при этом никуда не делось: оно осталось устаревшим псевдонимом, по-прежнему приходит в справочнике и по-прежнему принимается при записи, поэтому код, написанный по старому списку полей, продолжает работать — с той разницей, что скидка теперь действительно записывается, а не теряется молча. В самих товарных позициях приходит только `discount`. Добавлены `priceAccount` и `xmlId`, которые Битрикс24 возвращает в строках, но в своём справочнике не описывает. Поля `ownerId`, `ownerType` и `storeId` теперь приходят в ответах [списка](/docs/entities/deals/products-get) и [одной позиции](/docs/entities/deals/products-get-single); `storeId` равен `null`, если складской учёт выключен.

Признаки `isReadOnly` и `isRequired` описывают контракт этого API, а не контракт Битрикс24: `ownerId`, `ownerType`, `customized` и `measureName` помечены только для чтения (записать их через обёртку нельзя), а у `ownerId` и `ownerType` снят признак обязательности — они берутся из адреса запроса. У `id` появилось пояснение: как атрибут он только для чтения, но в элементах [PUT /products](/docs/entities/deals/products-set) его надо возвращать, иначе строка будет создана заново с новым идентификатором.

Запись с неизвестным именем поля больше не отвечает успехом: [POST](/docs/entities/deals/products-add), [PUT](/docs/entities/deals/products-set) и [PATCH](/docs/entities/deals/products-update) возвращают `400 INVALID_PARAMS` и перечисляют записываемые поля. Поля только для чтения по-прежнему принимаются и игнорируются, поэтому объект, прочитанный через GET, можно отправить обратно без чистки.

Поле `id` в теле принимается только там, где оно что-то значит, — в элементах [PUT /products](/docs/entities/deals/products-set), где оно велит обновить строку на месте вместо пересоздания. При [создании](/docs/entities/deals/products-add) и [правке](/docs/entities/deals/products-update) оно отбрасывается: строку там задаёт адрес запроса, а тело с `id` уже существующей строки — ровно то, что получается, если отправить обратно объект, прочитанный через GET.

Признак `taxIncluded` при записи снова принимается булевым: `true` и `false` уходят в Битрикс24 как `Y` и `N`. Раньше булево значение отправлялось как есть, и налог по строке фактически оставался незаданным — то есть строка, прочитанная через GET и отправленная обратно, теряла этот признак. Кто обходил это, посылая `"Y"` и `"N"` строками, ничего не заметит: такая форма принимается по-прежнему.

Если Битрикс24 отдал неполные метаданные, справочник всё равно приходит полным, но ответ дополняется предупреждением `meta.warnings[].code = "fields_partial"` — раньше такой ответ был неотличим от нормального. Текст предупреждения различает два случая: метаданных не пришло вовсе или не описана только часть полей (тогда он их перечисляет).

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

**Ломается запись с посторонним ключом в теле.** Проверьте, что при создании и правке товарной позиции вы не отправляете ничего сверх записываемых полей: своих служебных пометок, остатков внутренней модели, имён Битрикс24 в верхнем регистре (`PRICE_ACCOUNT`, `XML_ID`). Раньше такой запрос отвечал `201 Создано` и молча выбрасывал значение — теперь он отвечает `400 INVALID_PARAMS` и перечисляет, что принимается. Окна поддержки у прежнего поведения нет намеренно: оно и было дефектом, из-за которого заявку завели, а держать его параллельно значит держать тихую потерю данных. Полный список записываемых полей приходит в ответе на отказ и в [справочнике полей](/docs/entities/deals/products-fields).

Остальное менять не нужно. `discountSum` продолжает и приходить, и приниматься — перейти на `discount` можно без спешки, это имя приходит в самих товарных позициях, тогда как псевдоним живёт только в справочнике. Ветвитесь на `isReadOnly` или `isRequired` — сверьтесь с новыми значениями у `ownerId`, `ownerType`, `customized` и `measureName`. Заменяете строки целиком — сохраняйте `id` у каждого элемента.

### FIX-0810-12: подбор сотрудников по серверу больше не пустует из-за свежего ключа без доступа к Bitrix24

**Было**

Когда управляющий ключ сервера не давал доступа к Bitrix24, `GET /v1/infra/servers/{id}/b24-users`
и подбор сотрудников в интерфейсе переходили к личным ключам владельца сервера, но проверяли
только один — самый свежий подходящий. Если у него не было ни вебхука, ни токена установленного
приложения, ответ приходил пустым (`data: []` с подсказкой), хотя у владельца был другой активный
ключ с нужными правами. Ключи, которые платформа выпускает сама под задачи без обращений к
Bitrix24, всегда оказываются самыми свежими, поэтому подбор мог не работать постоянно.

**Стало**

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

Заодно исправлена подсказка в поле `hint`: раньше она называла только неавторизованное приложение
и отозванный ключ, из-за чего активный ключ выглядел отозванным. Теперь третьей причиной названо
отсутствие доступа к Bitrix24 у ключей и подсказано действие — выпустить личный ключ с нужными
правами. Форма ответа не изменилась.

### FIX-0810-13: привязка места встраивания называет отказ в правах установки вместо общей ошибки шлюза

**Было**

Битрикс24 отказывал в установке встройки, и проверить состояние подписки в этот момент не удавалось — [POST /v1/placements/bind](/docs/apps/placements/bind) отвечал `502 BITRIX_UNAVAILABLE` и складывал код Битрикс24 в `details`. Названного кода в ответе не было, а вместе с ним и подсказки, что делать дальше. Отказ приходил и на аккаунт с оформленной подпиской.

**Стало**

Когда проверку выполнить не удалось, а аккаунт уже числится подписанным в Вайбкод, тот же отказ приходит как `403 B24_EMBEDDING_INSTALL_DENIED` с `details.remedy` равным `install-rights`. Ссылка на оформление не передаётся: подписка есть, не хватает права ставить локальные приложения. Если действующей подписки нет ни по одному из источников, ответ остаётся `502 BITRIX_UNAVAILABLE`.

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

Менять ничего не нужно. Ветка обработки `502 BITRIX_UNAVAILABLE` продолжает работать для остальных отказов, а этот сценарий уходит из неё в `403`. Повторять запрос в нём бессмысленно — нужно, чтобы ключ разработчика принадлежал пользователю с правом ставить локальные приложения и с доступом к приложению.

### BC-0810-14: команды, выкладка и загрузка файлов по id машины-галактики больше не выполняются

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

**Было**

Галактика — это одна машина, на которой в контейнерах живут приложения нескольких ключей одного аккаунта Битрикс24. Список серверов отдаёт как сами приложения (`kind=GALAXY_APP`), так и несущую их машину (`kind=GALAXY`), и вызов [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec), [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) или [POST /v1/infra/servers/:id/upload](/docs/infra/deploy/upload) с id несущей машины выполнялся на ней самой — то есть за пределами контейнера вызывающего.

**Стало**

Тот же вызов с id машины вида `GALAXY` отвечает отказом: команды — `403 GALAXY_HOST_EXEC_FORBIDDEN`, выкладка и загрузка файлов — `400 GALAXY_HOST_NOT_A_DEPLOY_TARGET`. Текст отказа называет замену: работать с приложением по его собственному id (`kind=GALAXY_APP`). Для машин вида `STANDALONE` и для приложений галактики ничего не изменилось.

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

Возьмите из списка серверов строку своего приложения (`kind=GALAXY_APP`) и обращайтесь по её id. Если сценарий опирался на доступ к самой машине ради свободного места, эта величина теперь доступна как данные, а не как результат команды.

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

### NEW-0810-15: занятость диска машины-галактики видна в списке серверов

Строка машины-галактики (`kind=GALAXY`) в [GET /v1/infra/servers](/docs/infra/servers/list) и в карточке сервера теперь несёт четыре поля: `diskTotalMb` и `diskFreeMb` — размер и свободное место в мебибайтах, `diskState` — оценка (`ok`, `warning`, `critical` или `unknown`, если замера ещё не было), `diskProbedAt` — время замера в ISO-8601.

Обе границы оценки платформа держит с запасом: `warning` означает «место кончается», `critical` — что свободного места меньше, чем платформа считает безопасным; обе зажигаются раньше, чем выкладке действительно перестанет хватать места. Замер обновляется сам, пока машина не спит; у спящей показывается последнее известное значение вместе с его временем.

Для машин вида `STANDALONE` и для приложений галактики (`kind=GALAXY_APP`) все четыре поля равны `null`: у первых диск не измеряется, у вторых своего диска нет.

### NEW-0810-16: агентская модель bitrix/bitrixgpt-5.6-agent

[GET /v1/models](/docs/ai/models/list) отдаёт новую агентскую модель `bitrix/bitrixgpt-5.6-agent` с контекстом 1 048 576 токенов. Модель поддерживает потоковую выдачу, вызов инструментов (`tools`) и структурированный ответ по схеме — `response_format` с `type: "json_schema"`. Публичный идентификатор модели входит в список `ai.structuredOutputs.models` в ответе [GET /v1/me](/docs/keys-auth/me).

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

**Затронутые эндпоинты:** [GET /v1/models](/docs/ai/models/list), [POST /v1/chat/completions](/docs/ai/chat/completions), [GET /v1/me](/docs/keys-auth/me)

### NEW-0810-17: bitrix/bitrixgpt-5.5-agent помечена устаревшей

Ответы [POST /v1/chat/completions](/docs/ai/chat/completions) на модели `bitrix/bitrixgpt-5.5-agent` теперь несут заголовки `Deprecation: true`, `X-Model-Replacement: bitrix/bitrixgpt-5.6-agent` и `Link` со ссылкой на преемника (`rel="successor-version"`).

Модель продолжает работать без ограничений и остаётся в выдаче [GET /v1/models](/docs/ai/models/list). Дата отключения не назначена — заголовок `Sunset` не отдаётся, и менять в интеграции ничего не требуется. Преемник для новых интеграций — `bitrix/bitrixgpt-5.6-agent`.

### FIX-0810-18: временный сбой транзакции базы больше не отдаёт 500 с внутренним кодом движка

**Было**

Если транзакция в базе закрывалась или истекала до конца операции, запрос отвечал `500` и клал в тело внутренний код движка — `{"error":{"code":"P2028"}}`. Кода нет ни на одной странице документации, заголовка `Retry-After` в ответе не было, и по ответу нельзя было понять, что запрос стоит повторить. Чаще всего это видели на `DELETE /v1/apps/{id}`: приложение оставалось на месте, а повтор выглядел бессмысленным.

**Стало**

Тот же класс отказа отвечает `503` с кодом `DB_TRANSIENT`, полем `error.retryAfter` и заголовком `Retry-After` — та же посадка на повтор, что у `POOL_EXHAUSTED`. Изменение при таком отказе не применяется ни частично, ни полностью, поэтому прямой повтор через несколько секунд безопасен. На маршрутах AI (`/v1/ai/*`, `/v1/chat/*`, `/v1/audio/*`, `/v1/models`) код приходит в нижнем регистре — `db_transient` — в конверте, совместимом с OpenAI. Внутренний код движка в теле ответа больше не встречается.

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

Менять ничего не нужно. Если ваш обработчик считал `500` окончательным отказом — теперь этот случай приходит как `503` с указанным сроком и попадает в вашу ветку повторов. Отдельная обработка кода `DB_TRANSIENT` не требуется: достаточно уважать `Retry-After` на любом `503`.

### FIX-0810-19: preserveEnv не теряет .env, если выкладка упала после очистки каталога

**Было**

При `cleanDeploy: true` вместе с `preserveEnv: true` существующий `.env` читался до очистки
каталога, но записывался обратно только на шаге `env` — уже после установки рантайма и
зависимостей. Если выкладка обрывалась раньше, [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy)
отвечал `DEPLOY_FAILED`, а каталог оставался без `.env` совсем. Настройки приложения
приходилось заливать заново.

**Стало**

Сохранённая копия возвращается на диск при любом обрыве выкладки после очистки — на установке
зависимостей, на рантайме, на загрузке архива, на самой очистке, а также при разрыве связи с
сервером. Восстановление идёт по мере возможности и не добавляет отдельного шага в ответ.
Правило приоритета не изменилось: переданный в этом же запросе `env` по-прежнему побеждает, а
явный пустой `env: {}` означает «очистить» и сохранённую копию не возвращает. Успешная выкладка
работает как раньше, включая подстановку часового пояса для расписаний пробуждения. Флаг
по-прежнему только для отдельной виртуальной машины (`kind: "STANDALONE"`).

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

Менять на своей стороне ничего не нужно. Форма ответа `DEPLOY_FAILED` и набор шагов в
`data.steps` не изменились. Если вы обходили этот дефект — заливали `.env` вручную после каждой
неудачной выкладки — обходной путь больше не нужен.

## 2026-08-09

### BC-0809-1: meta.total в списках больше не приходит по умолчанию

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

**Было**

[GET /v1/{entity}](/docs/entity-api) и [POST /v1/{entity}/search](/docs/entity-api) присылали `meta.total` — количество записей под фильтр — если запрос не отказался от подсчёта явно. Отказаться можно было параметром `withTotal=false` или настройкой `totalDefault` на ключе, но умолчание платформы означало «считать», поэтому интеграция, которая про подсчёт ничего не знала, получала число всегда.

**Стало**

Платформенное умолчание сменилось на «не считать»: подсчёт заказывается явно. Попросить его можно тремя способами, они перекрывают друг друга в этом порядке: параметр запроса `withTotal=true` (у `POST /v1/{entity}/search` — поле тела `"withTotal": true`), настройка `totalDefault` на API-ключе, платформенное умолчание.

Если количество не попросили, наличие `meta.total` определяется формой вызова:

| Вызов | `meta.total` |
|---|---|
| `limit` не больше 50, `offset` 0, страница короче запрошенного | приходит, точное число — в том числе `0` |
| `limit` не больше 50, страница полная либо `offset` больше нуля | не приходит |
| `limit` больше 50 | приходит |

Короткая страница доказывает количество сама, поэтому точное число приходит бесплатно и подсчёт не заказывается. На вызове с `limit` больше 50 подсчёт нужен платформе, чтобы спланировать обход, поэтому число приходит как раньше — передавать туда `withTotal=false` ради экономии незачем: параметр уберёт число, а не стоимость.

Явный `withTotal=false` убирает ключ на любом из этих вызовов: за этим параметром обещание «поля не будет» остаётся безусловным. Настройка `totalDefault` на ключе и платформенное умолчание точное число из короткой страницы не запрещают, поэтому два одинаковых запроса от двух разных ключей могут вернуть ответы разной формы.

Всё это относится к вызовам, на которых подсчёт можно пропустить. Там, где пропустить его нельзя, `withTotal=false` игнорируется и `meta.total` приходит как раньше. Проверяйте наличие поля в конкретном ответе, а не выводите его из настроек ключа.

Новое умолчание действует и на списочных вызовах внутри [POST /v1/batch](/docs/batch): там количество приходит в `data.totals` и `meta` по идентификатору вызова и отсутствует по тем же правилам.

Остальной ответ не изменился: `data` — те же записи в том же порядке, `meta.hasMore` на месте и по-прежнему говорит, есть ли ещё страницы.

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

Если код читает `meta.total`, выберите одно из двух.

Разово и на всю интеграцию — включите на API-ключе настройку `totalDefault`: страница ключей в кабинете или [PATCH /v1/keys/:id](/docs/management-keys) с телом `{"totalDefault": true}` (управляющий ключ `vibe_live_`). Код менять не нужно.

Точечно — добавьте `withTotal=true` тем вызовам, которым количество действительно нужно: `GET /v1/deals?withTotal=true`, в теле поиска `"withTotal": true`.

Что действует на ваш ключ сейчас, показывает блок `totalDefault` в [GET /v1/me](/docs/keys-auth/me): `key` — настройка ключа, `platform` — платформенное умолчание, `effective` — что получится, если запрос не передаст `withTotal`.

Отдельно: если `meta.total` использовался как граница цикла листания, переключитесь на `meta.hasMore` — так надёжнее в любом случае. А если нужна была именно цифра, спросите её прямо: `POST /v1/{entity}/aggregate` с функцией `count` отдаёт количество одним вызовом. Обходить коллекцию постранично ради счётчика не надо — это десятки вызовов вместо одного.

## 2026-08-07

### NEW-0807-1: импорт записей CRM: автор и даты из внешней системы, без запуска автоматизации

Перенести уже существующую базу в CRM теперь можно одним запросом на сущность: `POST /v1/leads/import` и такие же маршруты у сделок, контактов, компаний, предложений, счетов и элементов смарт-процессов. До ста записей за раз, в теле — массив `items`, поля записи те же, что у обычного создания.

Импорт отличается от создания тремя вещами, и все три — свойства самой операции в Битрикс24, а не наши параметры. Он проверяет отдельное право «импорт», которое администратор портала выдаёт явно. Он **не запускает** роботов, триггеры и бизнес-процессы, настроенные на создание элемента. И он принимает служебные поля, которые обычное создание молча игнорирует: кто создал (`createdBy`), кто изменил (`updatedBy`), кто перевёл на стадию (`movedBy`) и соответствующие даты. Проставить их может только администратор портала — обычному пользователю Битрикс24 ответит отказом по такой записи, остальные записи пакета при этом создадутся. Набор доступных служебных полей у объектов разный; точный перечень отдаёт `GET /v1/leads/fields` — там они помечены `importable`.

У даты создания есть окно, которое задаёт Битрикс24: не позже текущего момента и не раньше, чем у самой свежей уже существующей записи этого объекта. То есть историю целиком получится перенести в пустую CRM или в такую, где всё старше переносимого; «подложить» записи задним числом в наполненную CRM Битрикс24 не даст. Если история не нужна, даты можно не передавать — автор проставляется и без них.

Ответ приходит со статусом `200` даже когда часть записей не прошла: импорт не транзакционен, поэтому исход читается по каждой записи в `results[]`, а сводка лежит в `summary`. Всегда проверяйте `summary.failed` — статус говорит «запрос обработан», а не «всё создано». Повторный импорт создаёт дубли; если повторы возможны, записывайте идентификатор из внешней системы в `originatorId` и `originId`.

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

Подробности, коды ошибок и примеры — на странице [Импорт записей CRM](/docs/import).

### FIX-0807-2: версия исходников выкладывается по номеру на своём сервере

**Было**

Версия, сохранённая через [POST /v1/infra/servers/:id/sources](/docs/source-storage), не выкладывалась на том же сервере: [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) с телом `{ "source": { "versionId": "v1" } }` отвечал `400 SOURCE_VERSION_REQUIRES_APP`, если ключ-владелец сервера не привязан к приложению — то есть на личном ключе `vibe_api_*`. Обойти можно было только вручную: скачать архив по ссылке и выложить его формой `{ "source": { "url": … } }`.

**Стало**

Если сервер принадлежит тому же ключу, которым идёт вызов, версия ищется в контексте этого сервера, и выкладка по `versionId` работает, включая личный ключ. Не нашлась на сервере, а ключ-владелец привязан к приложению — поиск повторяется в контексте приложения, как раньше. Не нашлась нигде — `404 SOURCE_VERSION_NOT_FOUND`; текст ошибки теперь называет сервер и путь сохранения вместо приложения.

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

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

Менять ничего не нужно. Вызов, который раньше отбивался, теперь проходит; поле `sha256` в ответе сохранено.

Одно исключение, редкое: если на сервере и на его приложении лежат версии с ОДНИМ номером — так бывает, когда версия сохранена до перепривязки сервера на другой ключ, — теперь берётся версия сервера, а не приложения. Проверить, какая именно версия уехала, можно по полю `sha256` в ответе.

### FIX-0807-3: чтение и правка несуществующего дела отвечают 404, а не 422

**Было**

`GET /v1/activities/{id}` и `PATCH /v1/activities/{id}` с идентификатором, которого
на портале нет, отвечали `422` с кодом `BITRIX_ERROR` и текстом вида
`Bitrix24 API error: 400`. Причина внешняя: на этих двух методах Битрикс24 отдаёт
отказ, в котором и код ошибки, и описание пустые, — распознать «записи нет» было
не по чему. При этом `DELETE /v1/activities/{id}` на тот же идентификатор уже
отвечал `404`, потому что там портал текст ошибки присылает. Один и тот же
несуществующий идентификатор давал два разных ответа в зависимости от глагола,
и оба раздела документации — [get](/docs/entities/activities/get) и
[update](/docs/entities/activities/update) — обещали `404`.

**Стало**

Оба метода отвечают `404` с кодом `ENTITY_NOT_FOUND` и сообщением
`Activity is not found.` — тем же, что и `DELETE`. Правило привязано к этим двум
методам и срабатывает только когда портал не прислал ни кода, ни текста: отказ с
любым кодом или сообщением (в том числе ошибка валидации при обновлении) остаётся
как был. Список ошибок в документации не менялся — изменился ответ, который теперь
ему соответствует.

### FIX-0807-4: запрос без тела доходит до обработчика, а не падает на разборе

Часть HTTP-клиентов (axios, PowerShell `Invoke-RestMethod`, некоторые обёртки над fetch)
подставляет `Content-Type: application/x-www-form-urlencoded` в каждый POST, PATCH и DELETE —
даже когда тело не отправляется вовсе. Ещё часть не ставит `Content-Type` совсем. Обе формы
до этой правки не доходили до обработчика.

**Было**

[POST /v1/deals](/docs/entities/deals/create) без тела и без заголовка `Content-Type` отвечал
`500 INTERNAL_ERROR` — обработчик падал на пустом теле раньше, чем успевал проверить, что
создавать нечего. Тот же вызов с пустым телом и заголовком
`application/x-www-form-urlencoded` отвечал `415 Unsupported Media Type` ещё до проверки
ключа. [POST /v1/chats/events/subscribe](/docs/chats/events/subscribe) — тело которому не
нужно вовсе — отвечал `415` с формовым заголовком и `400` с пустым телом под
`application/json`.

**Стало**

Пустое тело принимается независимо от заголовка: `POST /v1/deals` без тела отвечает
`400 EMPTY_CREATE_BODY` — тем же ответом, что и `POST /v1/deals` с телом `{}`;
`POST /v1/chats/events/subscribe` без тела отрабатывает штатно.

Заголовок `Content-Type` с пустым телом теперь не мешает нигде на сущностях
(`/v1/deals`, `/v1/contacts`, `/v1/tasks` и остальные генерируемые маршруты, включая
пакетные и агрегирующие), в чатах (`/v1/chats/*`), в пользовательских полях
(`/v1/userfields/*`, `/v1/items/:entityTypeId/userfields`), в базе знаний (`/v1/note/*`) и в
ключах (`/v1/portals`, `/v1/keys`). Отдельный случай «заголовка нет совсем» — это другая
поломка, и она закрыта на сущностях: там запрос без тела и без заголовка теперь получает
обычный ответ проверки полей вместо `500`.

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

Менять ничего не нужно: запрос, который работал, работает так же. Непустое тело под
незнакомым `Content-Type` по-прежнему отклоняется с `415` — тем же кодом, что и раньше; `413`
приходит только если тело действительно больше допустимого размера. Тело с битым JSON под
`application/json` теперь отвечает `400 INVALID_JSON_BODY` на всех перечисленных маршрутах
(раньше на чатах приходил код разборщика Fastify).

### BC-0807-5: дополнение своего обращения больше не выглядит ответом платформы

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

**Было**

Автором обращения считался конкретный ключ, которым оно создано. Комментарий, отправленный другим ключом того же владельца (или обращение, заведённое из кабинета и дополняемое ключом), уходил в платформенную ветку: записывался как `authorType: PLATFORM`, переводил обращение в `AWAITING_USER` и перезаписывал `Feedback.resolution` своим телом. Тем ключом, которым обращение создано, дополнить решённое обращение было нельзя — [POST /v1/feedback/:id/comments](/docs/feedback/comments) отвечал `409 FEEDBACK_CLOSED`. Единственным обходом было сменить статус через [PATCH /v1/feedback/:id](/docs/feedback/update).

Кроме того, `Feedback.resolution` работал зеркалом последней реплики команды: любой комментарий со скоупом `vibe:feedback` перезаписывал текст решения, и вернуть прежнее значение было нечем. Лимита темпа у операции комментирования не было вовсе.

**Стало**

Автором считается владелец ключа. Обращение, созданное другим вашим личным ключом или заведённое из кабинета, для вас своё: комментарий записывается как `authorType: USER`, поле `resolution` не переписывается, а переданный `status` игнорируется. Одно условие: такому ключу нужен скоуп `vibe:feedback` — без скоупа своим считается только тот ключ, которым обращение и создано. Правило не распространяется на ключи приложений и управляющие ключи — там владелец ключа и тот, кто пишет, разные лица.

Комментарий автора к обращению в статусе `RESOLVED` возвращает его в работу (`NEEDS_REVIEW`) и снимает `resolvedAt` / `resolvedBy`; текст решения при этом сохраняется. Статусы `ARCHIVED` и `WITHDRAWN` остаются закрытыми и по-прежнему отвечают `409 FEEDBACK_CLOSED`.

Поле `resolution` заполняет только комментарий, закрывающий обращение (целевой статус `RESOLVED` или `ARCHIVED`). При любом другом статусе поле не меняется, а текст комментария по-прежнему доходит до автора письмом и виден в ленте.

У операции комментирования появился лимит темпа — 20 комментариев в минуту, как у той же операции в кабинете. Счётчик общий на ВЛАДЕЛЬЦА ключа: несколько своих ключей делят один бюджет. Превышение — `429 RATE_LIMITED` с заголовком `Retry-After`.

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

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

1. **Обработчик `409 FEEDBACK_CLOSED`.** На решённом обращении теперь приходит `201`, и обращение возвращается в работу. Если по этому коду вы решали «обращение закрыто, дальше не пишем», проверку надо перевести на статус из ответа: закрытыми остались только `ARCHIVED` и `WITHDRAWN`.
2. **Ветвление по `authorType`.** Для второго ключа того же владельца значение сменилось с `PLATFORM` на `USER`. Код, который по `PLATFORM` рисует реплику как «ответ поддержки», начнёт показывать её как сообщение пользователя — и это верно, но если у вас на этой ветке висела логика, её надо пересмотреть.
3. **Чтение `resolution`.** Как «последняя реплика команды» поле больше не работает: там лежит вердикт последнего закрытия, а на обращении, которое ни разу не закрывали, поле пустое. За последним ответом команды идите в ленту комментариев — последний элемент с `authorType: PLATFORM`.
4. **Закрытие своего обращения комментарием.** Если вы закрывали своё обращение через `POST /comments` с полем `status`, этот способ больше не работает: у автора `status` игнорируется молча, ответ приходит `201`, а статус остаётся прежним. Отзыв обращения — через [PATCH /v1/feedback/:id](/docs/feedback/update) с `status: WITHDRAWN`.
5. **Обработка `429` на комментариях.** Операция получила лимит темпа, которого у неё не было. Если ваш код шлёт комментарии пачкой или в цикле — добавьте обработку `429 RATE_LIMITED` с паузой по заголовку `Retry-After`. Бюджет общий на владельца ключа, поэтому выпуском второго ключа его не расширить.

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

**Затронутые эндпоинты:** [POST /v1/feedback/:id/comments](/docs/feedback/comments), [GET /v1/feedback/:id](/docs/feedback/get), [GET /v1/feedback](/docs/feedback/list)

### BC-0807-6: сервер AI-агента больше не принимает выкладку приложения

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

**Было**

`POST /v1/infra/servers/{id}/deploy` принимал архив на сервер, принадлежащий
AI-агенту. Выкладка проходила, код агента затирался чужим приложением, и агент
переставал отвечать. Той же машиной агент числился рабочим — ни в ответе, ни в
интерфейсе следов не оставалось. По этой же причине сервер агента мог быть
переиспользован под приложение при создании нового сервера с тем же именем и
при повторной привязке приложения.

**Стало**

Выкладка в такой сервер отбивается `403` с кодом `AGENT_SLOT_DEPLOY_FORBIDDEN`;
текст ошибки называет рабочую альтернативу — кнопку «Повторить» на карточке
агента или создание отдельного сервера под приложение. Пути повторного
использования сервер агента больше не выбирают: вместо перезаписи создаётся
новый. Управляемые ботов это не касается — они выкладывают свой код через тот
же вызов штатно.

Выписка ключа обслуживания на агента, чей сервер снесён, теперь отвечает `409`
с кодом `AGENT_SERVER_GONE` вместо выдачи ключа, которым некуда идти. Чтение
состояния ключа осталось `200`, в теле появилось поле `reason` со значением
`SERVER_GONE`.

### NEW-0807-7: `GET /v1/cowork/state` сообщает о запланированном понижении тарифа

**Было**

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

**Стало**

Понижение планируется на конец оплаченного периода, и объект `subscription` получил
поле `pendingTier` — код тарифа, на который сиденье перейдёт при следующем списании,
либо `null`. Дата перехода — уже имеющееся поле `currentPeriodEnd`.

Поле аддитивное: клиенты, которые его не читают, работают как раньше. Отменённая
подписка всегда отдаёт `null` — отмена сильнее плана, и обещать тариф закрывающемуся
сиденью нельзя.

### FIX-0807-8: архив исходников галактического приложения добывает сама машина

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

Коды отказа и поля ответа прежние; добавился `UPLOAD_NO_SPACE` для нехватки места на диске. Неизвестный формат архива и ссылки, присланные в теле запроса, идут прежним путём. Затронуто [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy).

Отдельно: `extractTo` теперь проверяется на стороне платформы, а не только на машине. Набор допустимых путей не изменился ни на [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy), ни на [POST /v1/infra/servers/:id/upload](/docs/infra/deploy/upload), где своей проверки не было вовсе — отвергается то же, что и раньше, но сразу и с внятным кодом `INVALID_EXTRACT_TO`.

### FIX-0807-9: деплой больше не падает на остановке медленного приложения

**Было**

Повторный `POST /v1/infra/servers/:id/deploy` поверх работающего приложения, которое не
завершается сразу по SIGTERM, падал на шаге `stop_existing` через ~45 секунд:
`DEPLOY_TIMEOUT`, `Deploy step timed out: GATEWAY_TIMEOUT: no response within 45s`. Новая
версия не выкатывалась. Платформа отводила остановке 15 секунд, а операционная система на
сервере — до 90, поэтому приложение, которому на завершение нужно от 45 до 90 секунд,
роняло обновление гарантированно. Подсказка при этом советовала ремонтировать туннель —
ложный след.

**Стало**

Шаг `stop_existing` ждёт остановку столько, сколько ей отведено на сервере (лимит шага —
105 секунд), и больше не прерывает деплой: если остановку не удалось
подтвердить — результат не пришёл ЛИБО сервер ответил, что остановить не смог, — шаг
возвращает `warning` с честным текстом «исход неизвестен» и деплой идёт дальше.
Раньше второй случай не показывался вообще: шаг отдавал `ok`, и о том, что остановка
не удалась, узнать было негде. Сюда же попал соседний шаг очистки каталога: он тоже мог отдать
`ok`, ничего не удалив, если сервер команду не выполнял, — и деплой падал двумя шагами
позже с сообщением, которое причины не называло. Теперь такой случай останавливает деплой
сразу и говорит причину. Когда одновременно занят порт, в
предупреждении остаются оба факта. Ответ по-прежнему `success: true`, статус шага виден в
`data.steps[]`.

### FIX-0807-10: переименование поля шаблона реквизитов проверяется до записи

**Было**

`PATCH /v1/requisite-presets/:presetId/fields/:id` с полем `fieldName`, которого нет
среди доступных, отвечал `200` и `{"updated": true}`, а несуществующее имя реально
сохранялось в строке шаблона. Метод обновления в Битрикс24, в отличие от метода
добавления, не проверяет имя и принимает любую строку — поэтому строка шаблона
оставалась с именем, за которым нет поля, и переставала отображать данные.

**Стало**

Если `fieldName` в теле запроса отличается от имени, которое строка уже несёт, имя
сверяется со списком [GET /v1/requisite-presets/:presetId/fields/available](/docs/entities/requisite-presets/preset-fields/available)
до записи. Имя вне списка — `400` с кодом `INVALID_FIELD_NAME`, обновление не
выполняется; ни одно поле строки не меняется. Имя, занятое другой строкой того же
шаблона, в списке доступных отсутствует и тоже отклоняется. Переименование в
свободное имя проходит как раньше, а регистр имени приводится к написанию, которое
вернул Битрикс24.

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

Изменились три ответа. Мусорное имя вместо `200` даёт `400`: прежний `200` записывал
имя, за которым нет поля, поэтому терялись и остальные поля того же запроса — просто
молча. `fieldName`, переданный не строкой, тоже даёт `400 INVALID_FIELD_NAME`: раньше
такое значение уходило в Битрикс24 и оседало в строке словом `Array`. Запрос с
`fieldName` на несуществующую строку отвечает `404` до записи, а не ответом Битрикс24.

Запрос без `fieldName` работает как раньше. Запрос с тем же именем, что уже стоит в
строке, тоже проходит, но стоит на один вызов к Битрикс24 дороже: чтобы понять, что
имя не меняется, платформа сперва читает строку.

### NEW-0807-11: справочник полей учёта времени задач

Добавлен `GET /v1/task-time/fields` — программный состав полей записи учёта времени. Для каждого из десяти полей отдаются тип, признак «только чтение», подпись и описание. Раньше состав полей был описан только текстом в документации, и клиент не мог получить его вызовом.

Схема одинакова для всех задач, поэтому путь плоский. Вложенный `GET /v1/tasks/:taskId/time/fields` по-прежнему возвращает `400 WRONG_PATH`, но теперь называет в тексте ошибки правильный путь. Запрос требует скоуп `task` и не обращается к Битрикс24.

Числовые по смыслу поля — `id`, `taskId`, `userId`, `seconds`, `minutes`, `source` — объявлены строками, потому что именно строками они и приходят в ответах. Поле `userId` помечено `createOnly`: оно принимается при создании и отклоняется при обновлении.

Затронутые эндпоинты: [GET /v1/task-time](/docs/entities/tasks/time), `GET /v1/task-time/fields`.

### FIX-0807-12: подписи и описания полей daysBeforeClose, fm и FILES в ответе /fields

**Было**

Три поля, которые Битрикс24 отдаёт живьём, приходили без пояснения, а два из них — ещё и с неудобной подписью. [GET /v1/smart-processes/fields](/docs/entities/smart-processes/fields) отдавал `daysBeforeClose` с подписью длиной в предложение вместо краткого названия. [GET /v1/leads/fields](/docs/entities/leads/fields) отдавал `fm` с технической подписью «FM». [GET /v1/timelines/fields](/docs/entities/timelines/fields) отдавал `FILES` с подписью, но без описания, поэтому формат вложений приходилось искать на странице создания комментария.

**Стало**

У всех трёх полей есть `description`. У `daysBeforeClose` подпись сокращена до краткого названия, а прежний длинный текст перенесён в описание. У `fm` подпись заменена на понятную человеку, а описание отправляет к плоским полям `phone` и `email`, через которые те же данные удобнее читать и писать. У `FILES` подпись осталась той, которую прислал Битрикс24, — она зависит от языка портала, — а описание называет формат вложений на запись и на чтение.

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

Менять ничего не нужно: тип поля (`type`) и признак «только для чтения» (`readonly`) по-прежнему приходят от Битрикс24 и не изменились. Если ваш код показывает пользователю значение `label` этих полей, текст станет другим — он берётся из ответа, а не хранится у вас.

### FIX-0807-13: список обращений понимает фильтр в скобочной форме

**Было**

`GET /v1/feedback?filter[status]=RESOLVED` возвращал `200` и весь доступный список: скобочная форма фильтра разбиралась, но не читалась, поэтому и записи, и `total` приходили без фильтра. То же самое с `filter[category]`. Работала только плоская форма — `?status=RESOLVED`.

**Стало**

Обе формы работают одинаково. [GET /v1/feedback](/docs/feedback/list) применяет `filter[status]` и `filter[category]` с той же проверкой, что и плоские параметры: регистр не важен, неизвестное значение отдаёт `400 INVALID_FILTER_VALUE` вместо тихой выдачи всего списка. Если присланы обе формы, побеждает плоская — ответы на запросы, которые работали раньше, не меняются. Значение, которое не является одиночным (`filter[status][]=NEW`), тоже отклоняется с `400 INVALID_FILTER_VALUE`.

### FIX-0807-14: обновление заказа больше не теряет сумму, пометку и её причину молча

**Было**

[PATCH /v1/orders/:id](/docs/entities/orders/update) принимал `price`, `marked` и `reasonMarked` и отвечал `200`, но Битрикс24 эти поля на обновлении не сохраняет. У `marked` и `reasonMarked` значение просто пропадало. У `price` было хуже: сумма пересчитывается из позиций корзины, поэтому запрос с ручной суммой её не менял, а при пустой корзине сохранённая сумма становилась `0` — то есть обновление, посланное с любым другим полем, обнуляло цену заказа. Ответ об этом не сообщал.

**Стало**

Все три поля отклоняются на обновлении с `400 READONLY_FIELD` до обращения к Битрикс24 — на всех трёх поверхностях записи: одиночный `PATCH`, [POST /v1/orders/batch](/docs/batch) с `action: "update"` и [POST /v1/batch](/docs/batch) с `action: "update"`. Создание не изменилось: [POST /v1/orders](/docs/entities/orders/create) и оба батч-создания по-прежнему принимают эти поля и передают их значения. В ответе [GET /v1/orders/fields](/docs/entities/orders/fields) такое поле помечено `readonlyOnUpdate: true`, чтобы отличать его от `readonly` (нельзя и при создании) и от `createOnly` (значение неизменно после создания — про сумму заказа это неверно, её пересчитывает Битрикс24).

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

Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что значение молча терялось. Если запрос на обновление посылал эти поля — уберите их из тела, иначе он начнёт отвечать `400`. Чаще всего так делают клиенты, читающие заказ целиком и отправляющие объект обратно: из такого тела нужно убрать `price`, `marked` и `reasonMarked`. Чтобы изменить сумму заказа, меняйте [позиции корзины](/docs/entities/basket-items/create).

### NEW-0807-15: деплой сообщает, что не применил displayName или description

Поля `displayName` и `description` деплой **заполняет, но не переименовывает**: `displayName` записывается, только пока он ещё равен техническому идентификатору сервера, `description` — только пока оно пусто. Раньше присланное значение, конфликтующее с уже заданным, отбрасывалось молча — ответ приходил со статусом 200 и без единого признака, что поле не записано.

Теперь такой ответ несёт дополнительную запись в `warnings[]`: она называет отброшенные поля, подтверждает, что сам деплой прошёл, и предупреждает, что повтор ничего не изменит. Там же — готовое тело запроса к [PATCH /v1/infra/servers/{id}](/docs/infra/servers/update) с уже подставленным текущим именем: у этой ручки `displayName` обязателен, поэтому образец избавляет от случайной перезаписи имени при правке одного описания. Запись добавляется в конец массива, поведение записи в базу не изменилось.

Заодно операция переименования появилась в машинном описании API (`GET /v1/openapi.json`) — раньше схема утверждала, что переименования в этом API нет.

**Затронутые эндпоинты:** [POST /v1/infra/servers/{id}/deploy](/docs/infra/deploy/deploy), [PATCH /v1/infra/servers/{id}](/docs/infra/servers/update)

### FIX-0807-16: переоткрытие обращения больше не стирает текст резолюции

**Было**

`PATCH /v1/feedback/:id` с одним только статусом — например `{"status":"REVIEWING"}` — при возврате обращения из `RESOLVED`, `WITHDRAWN` или `ARCHIVED` обнулял поле `resolution`, хотя в теле запроса его не было. Ответ приходил `200`, о потере в нём ничего не говорилось. Если текст ответа команды не был продублирован комментарием, восстановить его было нечем.

**Стало**

Возврат в активный статус обнуляет только `resolvedAt` и `resolvedBy`. Поле `resolution` не меняется, если вы его не передали: у обращения, возвращённого из `ARCHIVED`, сохраняется и причина архивации. Чтобы заменить текст — передайте `resolution` в том же запросе, чтобы очистить поле — передайте `"resolution": null`. Путь с комментарием (`POST /v1/feedback/:id/comments`) `resolution` не трогал и раньше — теперь обе поверхности ведут себя одинаково.

### BC-0807-17: агрегации дел нужен сужающий фильтр

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

**Было**

`POST /v1/activities/aggregate` принимал запрос без фильтра. На небольшом аккаунте он отвечал за секунду, на большом — не отвечал никогда: первое же обращение к Битрикс24 (подсчёт всех дел аккаунта) не укладывалось в отведённое на вызов время, и клиент получал `503 BITRIX_TIMEOUT` с заголовком `Retry-After` и подсказкой «чтения можно повторять». Повтор давал тот же результат, потому что причина не была временной. Документация при этом прямо предлагала `{}` как «самый быстрый запрос».

`meta.truncated` означало ровно одно: «под фильтр попало больше 5000 записей». Если часть страниц записей до нас не дошла, ответ приходил с `truncated: false` — то есть числовые агрегации и группы были посчитаны по части записей, а ответ этого не сообщал.

**Стало**

Агрегация дел требует одного сужения из трёх: пара `ownerTypeId` + `ownerId`, либо `responsibleId`, либо граница по дате на `createdAt` / `updatedAt` / `deadline`. Требование включается платформой отдельно на каждый аккаунт. Пока оно выключено, поведение прежнее; после включения запрос без сужения получает `400 MISSING_REQUIRED_FILTER` — в `message` перечислены допустимые сужения и готовый пример тела, обращения к Битрикс24 не происходит.

Независимо от этого переключателя запрос **без** сужения, на который Битрикс24 не ответил за отведённое время, теперь возвращает `422 AGGREGATION_LIMIT_EXCEEDED` вместо `503`: отказ терминальный, заголовка `Retry-After` нет, в тексте — что сделать вместо повтора. Запрос **с** сужением по-прежнему получает на таймауте `503` с `Retry-After`: там повтор — честный совет, потому что причину замедления мы не знаем.

Оба ответа приходят и на устаревший `GET /v1/activities/aggregate` — правило нельзя обойти, вызвав его.

`meta.truncated` теперь означает «часть записей до нас не дошла» и в прежнем случае (выборка шире 5000), и в новом (обработано меньше записей, чем всего под фильтр). Во втором случае рядом приходит `meta.recordsShortfall` — сколько записей не хватило. `count` и `meta.totalRecords` при этом остаются полными: неполны только `data.groups` и числовые агрегации. ⚠️ Эта половина изменения касается агрегации ЛЮБОЙ сущности, а не только дел: раньше в таком ответе приходило `truncated: false`, то есть неполнота не сообщалась нигде.

Список допустимых сужений и то, включено ли требование на аккаунте прямо сейчас, приходят в `data.aggregateFilterRequirement` ответа [GET /v1/activities/fields](/docs/entities/activities/fields): поле `anchors` — сужения, поле `enforcement` — `enforced` либо `advisory`. Статические сужения без состояния аккаунта есть и в `GET /v1/guide`.

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

Добавить в агрегацию дел одно из сужений — этого достаточно и до, и после включения требования. Если сегодня в коде есть ветка на `503` для этого эндпоинта, добавить ветку на `422` и не повторять запрос по ней. Если код читает `meta.truncated`, учесть, что теперь он поднимается и при потере записей, и посмотреть на `meta.recordsShortfall`.

### FIX-0807-18: файл в поле CRM больше не упирается в 1 МБ, а код отказа стал понятным

**Было**

Значение пользовательского поля типа «Файл» едет в теле запроса как base64, а тело было ограничено 1 МБ на всех методах. Практический потолок одного файла — около 750 КБ: `PATCH /v1/deals/{id}` с файлом крупнее отвечал `413` и кодом `FST_ERR_CTP_BODY_TOO_LARGE`, которого нет ни в одной странице документации. Тот же потолок бил по загрузке файлов ботам и в чаты.

**Стало**

Тело ограничено 40 МиБ на создании и обновлении записей (`POST /v1/{entity}`, `PATCH /v1/{entity}/{id}`, включая `POST /v1/items/{entityTypeId}`), а также на `POST /v1/bots/{botId}/files` и `POST /v1/chats/{chatId}/files` — это чуть меньше 30 МиБ исходного файла после base64. Остальные методы сохраняют прежний потолок 1 МБ: поиск (`POST /v1/{entity}/search`), пакетные вызовы, служебные методы.

Код отказа `413` теперь `PAYLOAD_TOO_LARGE` на всех методах `/v1/`, кроме маршрутов AI (`/v1/ai/*`, `/v1/chat/*`, `/v1/audio/*`, `/v1/models`) — там сохраняется конверт ошибки, совместимый с OpenAI. Это тот же код, что уже возвращает пограничный слой на своём пороге. Замена затрагивает и потоковую загрузку исходников приложений (`POST /v1/apps/{id}/sources`) и серверов (`POST /v1/infra/servers/{id}/sources`), и отказ `413` на неверном `Content-Type` у публикации приложения, привязки мест встраивания и создания шаблона документа. Прежний внутренний код в ответах больше не встречается.

Порядок проверок изменился в пользу безопасности: на записи сущностей и на загрузке файлов ботам и в чаты ключ проверяется до чтения тела. Запрос без ключа или с неверным ключом теперь получает `401` там, где раньше мог получить `400` о неразобранном JSON или `413` о размере.

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

Учтите время: вызов в Битрикс24 ограничен 15 секундами и не повторяется, поэтому файл у самой границы на медленном аккаунте может отказать с `BITRIX_TIMEOUT`. Оставляйте запас или переносите крупные файлы в поле «Файл (диск)» через `POST /v1/files/upload`.

## 2026-08-06

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

Ответ `402` с кодом `cowork_quota_exhausted` на [POST /v1/chat/completions](/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): платформа сама минтит подписанную ссылку и связывает версию с этим деплоем, поэтому в истории видно, что именно уехало в прод.

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

## 2026-08-05

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

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

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

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

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

### BC-0805-4: описание полей задачи совпало с её ответом, числа стали числами

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

**Было**

`GET /v1/tasks/fields` описывал 92 поля, из которых 66 приходили именами вида
`MARK`, `NOT_VIEWED`, `STAGE_ID`, `CHAT_ID` — в ответах `GET /v1/tasks` и
`GET /v1/tasks/:id` таких ключей не бывает никогда. При этом бо́льшая часть ключей
самого ответа в описании отсутствовала. Значения тоже расходились с объявленным
типом: `id`, `status`, `priority`, `groupId`, `chatId`, `responsibleId`,
`createdBy`, `changedBy`, `closedBy`, `statusChangedBy`, `timeEstimate`,
`timeSpentInLogs` объявлены числами, а приходили строками (`"289"`, `"2"`).
Признаки «да/нет» приходили строками `"Y"` и `"N"`, а `"N"` в любом языке
истинна. Пустые `tags`, `group`, `accomplicesData`, `auditorsData` приходили
пустым массивом при объявленном объекте. Поля, реально приходящие пустыми, не были
помечены как допускающие пустое значение. `chatId` дополнительно менял тип между
поверхностями: строка в списке, число в карточке.

Клиент, сгенерированный по такому описанию, не работал.

**Стало**

Описание и ответ называют одни и те же поля. Объявлено 69 полей; сырые имена
верхним регистром из описания убраны (остаются только пользовательские поля
портала и `CHECKLIST` — он доступен через эндпоинты чек-листа). Объявленные
числами поля приходят числами, признаки «да/нет» — значениями `true` и `false`,
пустые `tags`, `group`, `accomplicesData`, `auditorsData` — пустым объектом.
27 полей помечены как допускающие пустое значение. `chatId` — число на обеих
поверхностях.

**Что делать интеграторам.** Проверьте в своём коде: сравнения значений со строками (`status === "2"`,
`id === "289"`), проверки признаков на непустую строку и обращения к пустым
`tags` / `group` / `accomplicesData` / `auditorsData` как к массиву.

Поля `subStatus` (только в списке) и `action`, `checklist`, `checkListTree`,
`checkListCanAdd` (только в карточке) приходят по-прежнему и в описание не
включены — списки и карточки задач различаются составом ключей на стороне
Битрикс24. Поле `realStatus` служит только для отбора и сортировки и в ответе
не приходит — теперь это видно и машине, по признаку `notReturned` в описании.

### FIX-0805-5: пауза при недоступной модели удлиняется, пока кластер не восстановится

**Было**

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

**Стало**

Первая пауза по-прежнему около минуты, но если по её истечении кластер всё ещё отвечает ошибками, следующая пауза удваивается — до четырёх минут максимум. Как только вызов проходит успешно, счёт сбрасывается и следующая пауза снова начинается с минуты. Заголовок `Retry-After` (и поле `retryAfter` в терминальном кадре потока) несёт актуальный остаток, поэтому берите время ожидания из ответа, а не из константы в своём коде.

### NEW-0805-6: деплой предупреждает, когда проверенный путь приложения расходится с адресом для Bitrix24

**Было**

Деплой с `healthPath`, отличным от `/`, проверял приложение на подпути и возвращал `200`, ничего не сообщая про адрес, по которому приложение открывает Bitrix24. Если приложение отвечало только на подпути, а `appUrl` оставался голым адресом сервера, плейсмент-фрейм открывал корень: деплой зелёный, приложение внутри Bitrix24 не открывается, и в ответе про это ни строчки.

**Стало**

`POST /v1/infra/servers/{id}/deploy` в таком сочетании добавляет строку в необязательный массив `warnings` (обычный JSON-ответ и событие `done` в SSE — так же, как уже устроены подсказки про `displayName`/`description` и про `changelog`). Подсказка называет обе половины расхождения — проверенный путь и открываемый адрес — и два выхода: раздавать сборку с `/` либо перенести подпуть в `appUrl` приложения через `PATCH /v1/apps/{id}`. Подпуть в `appUrl` поддерживается, запрета нет.

Подсказка не появляется, когда `healthPath` не задан или равен `/`, когда `appUrl` уже несёт путь, когда адрес приложения не на домене платформы и когда приложение ещё не связано с сервером. Форма ответа не меняется: `warnings` как был необязательным, так и остался.

### FIX-0805-7: Привязка места встраивания называет причину отказа

Привязка места встраивания через ключ приложения больше не отвечает безымянным `502 BITRIX_UNAVAILABLE`, когда Битрикс24 отклоняет регистрацию.

**Было**

[POST /v1/placements/bind](/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`](./infra/lifecycle/repair-status.md) не изменились; при провале обоих путей
поле `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 записей обрабатываются как прежде.

## 2026-08-04

### BC-0804-1: V1 /wake и /start будят galaxy-приложение через хост

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

**Было**

`POST /v1/infra/servers/:id/wake` и `/start` для `kind=GALAXY_APP` отвечали **422 `VM_MISSING`** (у приложения нет cloud VM) и советовали редеплой, даже когда контейнер просто спал после idle. Периодические задачи после первого сна не могли подняться через API.

**Стало**

Для размещённого galaxy-приложения оба verb'а вызывают host-mediated wake (как кабинет): будят общий хост при необходимости и стартуют контейнер. Успех — **HTTP 200**. `?wait=true` на galaxy **не** ждёт RUNNING до таймаута / `WAKE_TIMEOUT` — после cold wake приложение может остаться SLEEPING; опрашивайте GET. Хост с `preventWake` блокирует **и** `/wake`, **и** `/start` (**403 `SERVER_WAKE_BLOCKED`** или **402** paywall-коды `MARKETPLACE_REQUIRED` / … — не маркеры `TRIAL_EXPIRED`). Слот без host/`galaxyId` → **404 `GALAXY_HOST_NOT_FOUND`** (не 422 `VM_MISSING`). Для STANDALONE `VM_MISSING` / override `/start` без изменений.

### BC-0804-2: тип архива при сохранении версии сверяется с его первыми байтами

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

**Было**

Сохранение версии исходников — [POST /v1/infra/servers/:id/sources](/docs/source-storage) и [POST /v1/apps/:id/sources](/docs/source-storage) — верило заголовку `Content-Type` на слово. Архив zip, отправленный с `Content-Type: application/gzip`, принимался, сохранялся как gzip и получал имя с расширением `.tar.gz`; выкладка такой версии на сервер падала на распаковке с невнятной ошибкой чтения архива. Автосохранение версии после выкладки записывало формат как gzip всегда, независимо от того, что было в теле запроса.

**Стало**

При приёме читаются первые байты архива. Прямое противоречие между заявленным типом и содержимым — заявлен `application/gzip`, а байты zip, или наоборот — отвергается с кодом `415 UNSUPPORTED_ARCHIVE_FORMAT`; тело ответа содержит `error.hint.declared` и `error.hint.detected`. Типы `application/x-tar` и `application/octet-stream` под этот отказ не попадают никогда: их сигнатура не читается в первых восьми байтах, поэтому противоречие с ними установить нечем. Нераспознанное содержимое принимается как раньше.

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

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

Отправляйте `Content-Type`, соответствующий архиву (`application/gzip` для tar.gz, `application/zip` для zip), либо `application/octet-stream`, если тип неизвестен. Клиенты, которые уже отправляют корректный заголовок, ничего не меняют.

### NEW-0804-3: названия и описания полей у разделов товаров, шаблонов реквизитов и банковских реквизитов

**Что нового**

`GET /v1/product-sections/fields`, `GET /v1/requisite-presets/fields` и `GET /v1/bank-details/fields` возвращали только `type` и `readonly` — ни одно поле не имело человекочитаемого названия, поэтому по справочнику нельзя было понять, что означает, например, `rqAccNum`. Теперь названия объявлены у всех полей: 7 у разделов товаров, 11 у шаблонов реквизитов, 34 у банковских реквизитов. У шаблонов реквизитов и банковских реквизитов вместе с названием приходит и описание.

Ответ дополняется ключами `label` и `description` рядом с уже существующими `type` и `readonly` — прежние поля ответа не изменились.

Динамический справочник Битрикс24 эти названия дать не мог: он дополняет только те поля, которых нет в статической схеме, поэтому у объявленных полей его подписи отбрасывались. Операция `aggregate` у банковских реквизитов остаётся отключённой.

### NEW-0804-4: `limit=0` больше не игнорируется молча

**Что нового**

`limit=0` не является размером страницы: параметр отбрасывается, и применяется значение по умолчанию. Раньше это происходило без единого признака — ответ приходил с кодом 200 и полной страницей записей, как будто параметр учли. Теперь в такой ответ добавляется предупреждение в `meta.warnings`:

```json
{
  "meta": {
    "warnings": [
      {
        "code": "LIMIT_ZERO_IGNORED",
        "field": "limit",
        "message": "limit=0 is not a page size and was ignored. Valid range: 1..5000; pass an explicit limit (e.g. 5000) to read the whole collection."
      }
    ]
  }
}
```

Работает на `GET /v1/{entity}` и `POST /v1/{entity}/search` для всех сущностей. Само значение не изменилось — прежние вызовы продолжают возвращать столько же записей, сколько и раньше; чтобы прочитать всю коллекцию, передайте явный `limit` (максимум 5000).

### FIX-0804-5: банковские реквизиты: `entityTypeId` помечен как невозвращаемое поле

**Было**

`GET /v1/bank-details/fields` описывал `entityTypeId` обычным числовым полем — читаемым и записываемым. Про то, что Битрикс24 это значение принимает при создании, но никогда не возвращает при чтении, было сказано только в тексте описания поля. Клиент, который строит модель по машиночитаемому справочнику, а не по прозе, добавлял поле в тип чтения, а на месте числа получал пустоту.

**Стало**

У поля появился признак `notReturned: true` — в `GET /v1/bank-details/fields`, в `GET /v1/guide` и в OpenAPI-схеме (там это аннотация `x-notReturned` и фраза в описании). Тот же признак этот выпуск ввёл для `serverName` у телефонных линий.

Поле остаётся доступным для записи: `POST /v1/bank-details` по-прежнему принимает `entityTypeId` (всегда 8 — владелец-реквизит). Признак говорит только про чтение.

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

Ничего менять не нужно — признак аддитивен. Если вы генерируете типы по справочнику, `entityTypeId` можно исключить из модели чтения и оставить в модели создания.

### FIX-0804-6: discover называет настоящий идентификатор в пути

**Было**

`GET /v1/guide` и OpenAPI-схема показывали путь одиночной записи как `/:id` у всех сущностей. У четырёх это неправда: `smart-processes` адресуется публичным `entityTypeId` (1030+), `telephony-lines` — номером линии, а `bizproc-robots` и `bizproc-activities` — кодом (`code`), причём собственного `id` у них нет вовсе. Клиент, прочитавший `{id}`, подставлял собственное поле `id` записи и получал `404 SMART_PROCESS_NOT_FOUND`, где то же значение названо `entityTypeId` — притом что опубликованная документация уже писала `:entityTypeId` и `:code`.

**Стало**

`GET /v1/guide` показывает `/v1/smart-processes/:entityTypeId`, `/v1/telephony-lines/:number`, `/v1/bizproc-robots/:code` и `/v1/bizproc-activities/:code`; у остальных сущностей путь остался `/:id`. В OpenAPI имя параметра осталось `id`: имя path-параметра обязано совпадать с плейсхолдером в шаблоне пути, а сам адрес не менялся — вместо этого описание параметра теперь называет настоящий идентификатор. Там, где у сущности нет собственного поля `id` (телефонные линии и оба bizproc-справочника), описание так и говорит, а не отправляет сверяться с полем, которого не существует. Описание поля `id` у `smart-processes` тоже уточнено.

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

Ничего менять не нужно: маршруты и коды ответов не изменились, изменились только описания. Если вы подставляли в путь `smart-processes` внутренний `id` записи, теперь понятно, почему приходил 404 — используйте `entityTypeId`; для роботов и действий бизнес-процессов — `code`.

### FIX-0804-7: телефонные линии: `name` объявлен nullable, `serverName` помечен как невозвращаемый

**Было**

`GET /v1/telephony-lines/fields` объявлял `name` обычной строкой, хотя у линии без названия приходит `null` — модель, построенная по справочнику, ломалась на первом же таком значении. Поле `serverName` выглядело в справочнике обычным читаемым полем, хотя Битрикс24 его не хранит и никогда не возвращает.

**Стало**

У `name` появился признак `nullable: true` — и в `GET /v1/telephony-lines/fields`, и в `GET /v1/guide`, и в OpenAPI-схеме (там это форма `type: ["string","null"]`). У `serverName` появился признак `notReturned: true` в тех же местах, а в OpenAPI — аннотация `x-notReturned` и фраза в описании. Поле осталось в справочнике намеренно: запись в него по-прежнему отклоняется с `400 READONLY_FIELD`, и клиент должен иметь возможность найти поле и прочитать причину.

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

Ничего менять не нужно — признаки аддитивны. Если вы генерируете типы по справочнику, `name` станет `string | null`, а `serverName` можно исключить из модели чтения.

### FIX-0804-8: рабочие группы: работают `limit > 50` и точное смещение

**Было**

`GET /v1/workgroups?limit=500` возвращал первые 50 записей независимо от того, сколько групп доступно, и `meta.hasMore` не помогал дочитать остальное. Причина — метод списка у этой сущности называется не `.list`, а `sonet_group.get`, и признак «это список» у неё не был объявлен, поэтому ни `limit`, ни авто-пагинация до Битрикс24 не доходили. Смещение при этом округлялось вниз до границы страницы: `offset=30` отдавал записи с первой, а не с тридцать первой.

**Стало**

`limit` доходит до Битрикс24, и при `limit > 50` платформа сама читает нужное число страниц — как это давно работает у пользователей, отделов и хранилищ. Смещение стало точным: `offset=30` начинает выдачу с 31-й записи. То же поведение на `POST /v1/workgroups/search` и в списочном подзапросе `POST /v1/batch`.

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

Если вы листали рабочие группы вручную и компенсировали округление смещения на своей стороне (например, отбрасывали первые записи страницы), эту компенсацию нужно убрать — иначе записи будут пропускаться дважды. Для глубокого листания надёжнее курсор: фильтр `{">id": последнийId}` с сортировкой по `id`.

### FIX-0804-9: удаление приложения доводит снятие регистрации с портала до конца

**Было**

[DELETE /v1/apps/:id](/docs/apps/delete) снимал приложение с портала одной попыткой.
Если портал был недоступен, права отозвали или у автора приложения не оказалось ключа
разработчика, попытка молча пропадала: приложение удалялось у нас, но оставалось
установленным на портале Битрикс24 — его пункт оставался в меню. Встройки при этом
отвязывались только у опубликованных в каталоге приложений и только на облачных
порталах.

**Стало**

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

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

Ответ эндпоинта не изменился — по-прежнему `204` сразу после удаления на нашей стороне.
Изменился результат: пункт приложения на портале теперь исчезает и в тех случаях, когда
раньше оставался навсегда.

### NEW-0804-10: Отказ по таймауту шлюза на выполнении команды несёт подсказку по восстановлению

Отказ с кодом `GATEWAY_TIMEOUT` у [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) теперь дополнен объектом `hint` с полями `reason`, `recovery` и `recoveryAction` — так же, как это уже сделано для `EXEC_TIMEOUT` и агентского `EXEC_BUSY`. Подсказка говорит главное: код возврата не получен, поэтому исход команды неизвестен и она может продолжать выполняться на сервере. Повторный запуск вслепую способен создать вторую копию поверх первой, поэтому сначала стоит выяснить фактическое состояние — прочитать логи или выполнить короткую read-only команду. Для работы, которая заведомо не укладывается в предельное время, подсказка направляет на фоновую задачу.

Поля `code` и `message` не изменились — подсказка добавлена аддитивно, и прежние вызовы продолжают работать. Приходит в обоих режимах ответа: в JSON-конверте и SSE-событием `error`.

### FIX-0804-11: отказ при недоступной модели — 429 с выдержкой вместо 502

**Было**

Когда доступ к моделям временно закрывался из-за череды неудачных вызовов, `POST /v1/chat/completions` и `POST /v1/embeddings` отвечали **502** с кодом `AI_PROVIDER_UNAVAILABLE`, а в `error.message` приходила внутренняя служебная строка вместо объяснения. Заголовка `Retry-After` не было, поэтому клиенту нечего было ждать — типовая реакция библиотеки на 5xx — повторить сразу, что продлевало недоступность.

**Стало**

Тот же отказ приходит как **429**, `error.type: "rate_limit_exceeded"`, `error.code: "ai_provider_cooldown"` (в потоковом кадре — то же имя в верхнем регистре, как у остальных кодов этого семейства), с заголовком **`Retry-After`** в секундах; в потоковом ответе то же значение приходит полем `retryAfter` внутри терминального кадра ошибки. Значение — остаток окна ожидания, не меньше секунды. `error.message` больше не содержит служебных строк и адресов. Отказ временный: дождитесь `Retry-After` и повторите тот же запрос.

### FIX-0804-12: деплой архива формой multipart сохраняет версию исходников даже при неудачном деплое

**Было**

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) с телом `multipart/form-data` сохранял архив в хранилище исходников только после успешного деплоя. Если деплой падал, версия не появлялась вовсе — разбирать было нечего, а повторная выкладка требовала заново отправить те же байты.

**Стало**

Архив помещается в хранилище исходников до старта деплоя, и деплой продолжается по ссылке на эту версию. Версия остаётся в истории при любом исходе: при успехе она получает `deployStatus: "success"`, при неудаче — `"failed"`. Блок `data.source` в ответе не изменился: `autoSaved`, `savedVersionId`, `sha256` и `newVersion` заполняются как раньше, повторная отправка тех же байт по-прежнему дедуплицируется и новой версии не создаёт.

Появился новый код отказа `SOURCE_DEPOT_UNAVAILABLE` (502): хранилище исходников не приняло архив, деплой не стартовал, запрос можно повторить без изменений. Раньше такой сбой приходил как `VALIDATION_ERROR` (400), то есть выглядел ошибкой запроса.

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

Действий не требуется. Список версий ([GET /v1/infra/servers/:id/sources](/docs/source-storage)) у клиентов, деплоящих формой multipart, теперь может содержать версии неудавшихся выкладок — они помечены `deployStatus: "failed"`.

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

Раньше [GET /v1/addresses/fields](/docs/entities/addresses/fields) у шести полей из четырнадцати отдавал в `title` имя поля Битрикс24 — `TYPE_ID`, `ENTITY_TYPE_ID`, `ENTITY_ID`, `COUNTRY_CODE`, `ANCHOR_TYPE_ID`, `ANCHOR_ID`. Показать такую подпись пользователю нельзя, а значения кодов приходилось искать в документации.

Теперь у этих шести полей в `title` приходит подпись на языке портала, а у полей, которым есть что добавить к подписи, появился ключ `description` с назначением поля и расшифровкой кодов: типы адреса (все двенадцать, с 1 по 12 — какие из них доступны, зависит от страновой зоны портала) и типы владельца (1 — лид, 3 — контакт, 4 — компания, 8 — реквизит). У поля `countryCode` описание не обещает формат: в документации REST Битрикс24 это поле помечено как неиспользуемое и оставленное для обратной совместимости. У кода 1 расшифровка дополнена подписью из англоязычного интерфейса Битрикс24 — Street address: сам Битрикс24 называет этот тип по-разному в русской и английской версиях, и без оговорки словарь расходился бы с подписью, которую пользователь видит в интерфейсе.

Подписи, которые Битрикс24 отдаёт сам, не изменились. Рядом с `title` та же подпись теперь приходит и в ключе `label` — так же, как у остальных сущностей, поэтому читать подписи можно одним способом на любой сущности. Ключи `description` и `label` добавляются к описанию поля, прежние ключи остаются на месте, поэтому менять ничего не нужно.

Заодно исправлены расшифровки кодов типа адреса в документации операций создания, чтения, изменения и удаления адреса: там были указаны неверные значения, а код 13 не существует вовсе — набор типов заканчивается на 12, и какие из них доступны, зависит от страновой зоны портала.

### NEW-0804-14: поля товаров приходят с подписями, описаниями и словарём формата описания

[GET /v1/products/fields](/docs/entities/products/fields) описывал двадцать одно поле товара только типом и признаком «только для чтения» — ни подписи, ни описания. Понять по такому ответу, что `measure` это единица измерения, а `vatId` — ставка НДС, было нельзя.

Теперь каждое из двадцати одного поля несёт `label` на языке портала, а поля, у которых есть что добавить к подписи, — ещё и `description`: где взять список допустимых значений (`GET /v1/currencies`, `GET /v1/product-sections`, `GET /v1/catalogs`, `GET /v1/users`), как работает сортировка и что задаёт формат описания. У поля `descriptionType` появился словарь `enum` со значениями `text` и `html`.

Ключи добавляются к описанию поля, прежние `type` и `readonly` не меняются, поэтому менять ничего не нужно. Свойства каталога `PROPERTY_<N>` по-прежнему приходят с подписью из настроек портала.

### NEW-0804-15: все 45 полей предложения приходят с подписью и описанием

[GET /v1/quotes/fields](/docs/entities/quotes/fields) отдавал подпись у тридцати полей из сорока пяти. Пятнадцать оставшихся — ровно базовые: `id`, `title`, `dealId`, `contactId`, `companyId`, `amount`, `currency`, `assignedById`, `createdBy`, `comments`, `isManualOpportunity`, `beginDate`, `closeDate`, `createdTime`, `updatedTime` — описывались только типом и признаком «только для чтения». Описания (`description`) не было ни у одного поля.

Теперь подпись есть у всех сорока пяти полей, и у каждого появилось описание: назначение поля, где взять список допустимых значений (`GET /v1/currencies`, `GET /v1/users`, `GET /v1/deals` и другие), поведение при записи. Отдельно названы расхождения имён, на которых легко ошибиться: сумма в Битрикс24 называется `opportunity`, валюта — `currencyId`, а даты начала и закрытия — `begindate` и `closedate` целиком в нижнем регистре.

У поля `stageId` словаря значений нет намеренно: набор стадий настраивается на портале, поэтому в описании стоит ссылка на справочник `GET /v1/statuses?filter[entityId]=QUOTE_STATUS` — статический словарь устарел бы.

Ключи добавляются к описанию поля, прежние `type` и `readonly` не меняются, поэтому менять ничего не нужно.

### NEW-0804-16: поля товаров каталога приходят с подписями и описаниями

[GET /v1/catalog-products/fields](/docs/entities/catalog-products/fields) описывал все сорок два поля товара каталога только типом и служебными признаками — ни подписи, ни описания. Понять по такому ответу, чем `purchasingCurrency` отличается от валюты цены, а `quantityTrace` — от `canBuyZero`, было нельзя.

Теперь каждое из сорока двух полей несёт `label` на языке портала, а тридцать восемь полей — ещё и `description`. В описаниях названо то, что раньше приходилось выяснять на практике: `iblockId` задаётся только при создании и не даёт перенести товар между каталогами; `iblockSection` принимается только при записи, а на чтении основной раздел приходит скаляром `iblockSectionId`; `available` и `bundle` вычисляет Битрикс24; `recurSchemeLength`, `recurSchemeType` и `trialPriceId` работают только в коробочной версии Битрикс24 при продаже контента. Где значение берётся из справочника, указан эндпоинт — `GET /v1/catalogs`, `GET /v1/catalog-sections`, `GET /v1/currencies`, `GET /v1/users`.

У полей `previewTextType` и `detailTextType` набор значений приходит машиночитаемым словарём `enum` (`text` и `html`) — так же, как у формата описания товара, а не прозой внутри описания.

Ключи добавляются к описанию поля, прежние `type`, `readonly`, `createOnly` и `nullable` не меняются, поэтому менять ничего не нужно.

### FIX-0804-17: схема полей шаблона реквизитов объявляет inShortList булевым

**Было**

[GET /v1/requisite-presets/:presetId/fields/schema](/docs/entities/requisite-presets/preset-fields/schema) описывал поле `inShortList` типом `char`, хотя чтение строк того же шаблона отдаёт `true`/`false`, а запись принимает `true`/`false`. Схема противоречила данным, которые она описывает, и клиент, полагавшийся на объявленный тип, готовился разбирать односимвольную строку.

**Стало**

В том же ответе `inShortList.type` приходит как `boolean`. Остальные ключи описания поля (`isRequired`, `isReadOnly`, `title` и прочие) не изменились, типы остальных полей — тоже.

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

Менять ничего не нужно: данные и раньше приходили булевыми. Проверка, сравнивающая `inShortList.type` со строкой `char`, перестанет совпадать — сверяйте с `boolean`.

### NEW-0804-18: скачивание записей звонков и вложений таймлайна

Появились два эндпоинта для файлов CRM, до которых нельзя было добраться через скачивание файла Диска.

[GET /v1/activities/:activityId/files/:fileId/download](/docs/entities/activities/file-download) отдаёт файл дела, в том числе запись звонка. До этого сделать это через API было нельзя: файл дела не является объектом Диска, его идентификатор живёт в отдельном пространстве, пересекающемся с идентификаторами Диска, а ссылка из ответа дела приходит с пустым параметром авторизации — запрос по ней возвращает страницу входа с кодом `200`. Эндпоинт добавляет авторизацию сам, проверяет, что файл действительно принадлежит названному делу, и отдаёт поток байтов.

[GET /v1/timelines/:commentId/files/:fileRef/download](/docs/entities/timelines/file-download) отдаёт вложение комментария таймлайна. В `fileRef` принимается любой из двух идентификаторов: ID привязки, который показывает интерфейс портала, и ID объекта Диска — ключ объекта в поле `files` ответа комментария. Первый считает доступ через сам комментарий, поэтому дотягивается до вложений, которые скачивание файла Диска отдавать отказывается; второй идёт через личные права на Диске. Сначала читается сам комментарий, затем пробуется ID объекта Диска, и только если его нет в списке файлов комментария — ID привязки; указывать выбор не нужно.

Оба эндпоинта требуют скоуп `crm` и никогда не возвращают адрес для скачивания: в нём содержится код авторизации, поэтому наружу уходит только содержимое. Файл, принадлежность которого названному делу или комментарию не подтверждается, получает `404` — это касается и вложения, которое висит на записи другого типа, например на задаче с тем же номером, — без этой проверки эндпоинт позволял бы перебирать файлы портала, поскольку сам Битрикс24 в таком случае отвечает страницей под кодом `200`, а не отказом.

Проверка адреса распространяется и на перенаправления: эндпоинт проходит их сам, сверяя каждый шаг, ограничивает цепочку по длине и по времени и не идёт по перенаправлению на внутренний адрес — такой ответ становится `502`. Это же касается [скачивания файла Диска](/docs/entities/files/download), которое пользуется той же обвязкой.

`404` на скачивании вложения означает именно неверную ссылку. Временная причина — лимит запросов Битрикс24, недоступность портала, сработавшая защита от повторяющихся ошибок — приходит как она есть: `429` либо `502`/`503` с заголовком `Retry-After`. Разница практическая: по `404` повторять запрос бессмысленно, по `429`/`5xx` — нужно, с задержкой. Срок в `Retry-After` считается по тому вызову Битрикс24, который действительно упёрся в лимит.

Контракт [скачивания файла Диска](/docs/entities/files/download) не менялся: те же параметры, те же ответы. Общей обвязкой оно унаследовало только проверку адреса и перенаправлений, описанную выше.

### FIX-0804-19: сообщение бота без текста отбивается понятной ошибкой, а не мнимым успехом

**Было**

[POST /v1/bots/:botId/messages](/docs/bots/messages/send) с текстом в неузнанном поле — например `{"dialogId": "…", "text": "привет"}` — уходил в Битрикс24 без содержимого, и тот отвечал `422` с кодом `EMPTY_MESSAGE` и текстом «Message can't be empty». Понять из такого ответа, что дело в имени поля, было нельзя: текст-то передан.

Хуже вело себя обновление. [PATCH /v1/bots/:botId/messages/:messageId](/docs/bots/messages/update) в той же ситуации отвечал `200 {"result": true}`, а текст сообщения не менялся — мнимый успех, после которого интегратор считал правку применённой.

**Стало**

Оба запроса проверяют содержимое до обращения к Битрикс24 и при его отсутствии отвечают `400` с кодом `MESSAGE_REQUIRED`. В тексте ошибки перечислены нераспознанные ключи тела и указано, что текст сообщения кладётся в поле `message`. Пустой массив в `attach` и пустая строка в `message` содержимым не считаются; блок `attach` без текста — считается, как и число (`0` это текст «0»).

Обёртка `fields` — способ передать тело в родном виде Битрикс24, и «обёртка передана» теперь понимается одинаково на всех шагах обработки. Значение, обёрткой не являющееся (`false`, `0`, пустой массив), обёрткой и не считается: `{"dialogId": "…", "fields": false}` получает тот же `400` с кодом `MESSAGE_REQUIRED`, а `{"message": "привет", "fields": []}` отправляет текст, а не теряет его.

Это тот же код и та же формулировка, что у соседнего [POST /v1/chats/:dialogId/messages](/docs/chats/messages/send): один контракт на одинаковую ошибку в двух родственных эндпоинтах.

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

Запрос с текстом в поле `message` работает как раньше. Запрос, который раньше получал `422 EMPTY_MESSAGE`, теперь получает `400 MESSAGE_REQUIRED` с указанием, что исправить. Поле `text` синонимом `message` не становится: на чтении содержимое сообщения действительно называется `text`, но принимать оба имени молча значило бы развести контракт с эндпоинтом чатов.

## 2026-08-03

### FIX-0803-1: переоткрытие архивированного тикета тоже очищает штамп решения

**Было**

Переоткрытие тикета из статуса `ARCHIVED` в активный (`NEW`/`REVIEWING`/`AWAITING_USER`/`NEEDS_REVIEW`) — через `PATCH /v1/feedback/:id` или `POST /v1/feedback/:id/comments` — не сбрасывало `resolvedAt`/`resolvedBy`, если тикет ранее был решён и затем заархивирован. На чтении (`GET /v1/feedback/:id`) такой тикет выглядел активным и решённым разом. Сброс срабатывал только для источников `RESOLVED`/`WITHDRAWN`.

**Стало**

`ARCHIVED` присоединён к `RESOLVED`/`WITHDRAWN`: переоткрытие из любого закрытого статуса в активный очищает `resolvedAt`/`resolvedBy`. На `PATCH`-пути очищается и `resolution` (в том числе причина архива) — активный тикет решения не несёт; явный `resolution` в том же запросе имеет приоритет. На пути комментария `resolution` равен телу коммента. Переход **в** `ARCHIVED` штамп по-прежнему сохраняет (архивирование хранит историю решения).

### FIX-0803-2: пропавшие байты вложения отдаются как 404, а не как оборванный ответ

`GET /v1/feedback/{id}/attachments/{attId}/file` и `.../thumb` теперь проверяют наличие байтов до отправки заголовков. Если запись о вложении есть, а самих байтов в хранилище нет (последствие ручной уборки или каскадного удаления), ответом будет обычный `404 NOT_FOUND`.

**Было**

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

**Стало**

`404 { "success": false, "error": { "code": "NOT_FOUND", "message": "Not found" } }` — тот же код, что и для чужого или удалённого вложения. Клиентам, которые уже обрабатывают `404` на этих маршрутах, менять ничего не нужно.

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

### FIX-0803-3: создание сервера честно сообщает, что вернуло уже существующий сервер приложения

Если ключ вызова принадлежит приложению, у которого слот сервера уже занят, [POST /v1/infra/servers](/docs/infra/servers/create) возвращает этот сервер вместо создания нового. Так было и раньше, но узнать об этом из ответа было почти нечем: единственным признаком было недокументированное поле `reused`, а имя в ответе принадлежало существующему серверу, а не запрошенному.

Теперь такой ответ несёт полное раскрытие: `data.reusedReason` со значением `APPLICATION_ALREADY_HAS_SERVER`, `data.requestedName` с эхом переданного вами `name` (всегда, даже если оно совпадает с именем существующего сервера) и массив `warnings` рядом с `data` минимум с одной записью — она называет существующий сервер и предупреждает, что деплой заменит работающий на нём код. Поля `reused` и `deploying` теперь описаны в схеме и в документации.

Точно так же раскрывается и вторая молчаливая потеря: переданные `displayName` и `description` переиспользование **не применяет** — сервер сохраняет собственные имя и описание. Теперь это видно по `data.metaIgnored` и отдельному предупреждению; переименовать сервер можно осознанно через [PATCH /v1/infra/servers/:id](/docs/infra/servers/update).

Если в запросе был передан `source`, а развернуть его в переиспользуемый сервер нельзя (у galaxy-приложения уже есть работающий контейнер, либо это отдельная виртуальная машина), архив отбрасывается — и теперь ответ говорит об этом полем `data.sourceIgnored` и отдельным предупреждением. Раньше архив отбрасывался молча.

**`data.next` в reuse-ответе теперь приходит только тогда, когда перезаписывать нечего.** Раньше поле приходило на любом двухшаговом reuse — в том числе когда возвращённый сервер уже выполнял чужой код, и машинное «следующий шаг — деплой» противоречило подсказке в том же теле. Если сервер может выполнять код, `next` отсутствует намеренно: сначала убедитесь, что сервер тот. Отсутствие `next` — не ошибка.

Два прежних поля изменились по смыслу, хотя менять клиентский код не нужно: `data.hint` на reuse-ответе переписан целиком — вместо «задеплойте сюда» он теперь начинается с `REUSED — no new server was created` и объясняет, чем это чревато; а описание `data.next` в схеме исправлено — раньше оно называло единственной причиной появления поля пустой galaxy-слот, хотя `next` приходит и на reuse-ответе. Прежние поля и коды не изменились, менять ничего не нужно. Но перед вызовом [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) читайте `reused`: деплой заменяет то, что уже работает на сервере.

### NEW-0803-4: справочник значений списочных свойств каталога

Появилась сущность `catalog-product-property-enums` — все возможные варианты свойства-списка торгового каталога: `GET /v1/catalog-product-property-enums`, `GET /v1/catalog-product-property-enums/:id`, `POST /v1/catalog-product-property-enums/search` и `GET /v1/catalog-product-property-enums/fields`. Сущность только для чтения: записывающие операции и агрегация не зарегистрированы и отвечают `404`, а `data.batch` приходит пустым массивом.

Раньше значение списочного свойства у товара можно было получить только как идентификатор варианта: семейство `/v1/products` отдаёт в `PROPERTY_<N>` объект с числовым `value`, а `GET /v1/products/fields` описывает свойство только именем — развернуть идентификатор в текст было нечем. Теперь список вариантов запрашивается напрямую: `GET /v1/catalog-product-property-enums?filter[propertyId]=166&limit=1000` возвращает пары «идентификатор — читаемый текст», по которым строится карта `String(id)` → `value`.

Фильтр `filter[propertyId]` обязателен: справочник читается по одному свойству за раз, запрос без него отклоняется с `400 MISSING_REQUIRED_FILTER` до обращения к Битрикс24 — на списке и поиске. Проверка смотрит на наличие ключа и не распространяется на подвызовы пакетного запроса. Перечисление есть только у свойств с `propertyType: "L"` — у свойства любого другого типа ответ будет пустым списком, а не ошибкой. Пагинация обычная: `limit` + `offset`, конец выборки — по `meta.hasMore`. Потолок — 5000 записей за вызов. Нужен скоуп `catalog`.

Заодно уточнены существующие страницы. `GET /v1/catalog-products/:id` теперь показывает захваченный ответ со списочным свойством и разбор тройки `value` / `valueEnum` / `valueId`, а также форму значения при `listType: "C"` — голый скаляр `"Y"`/`"N"`, то есть состояние галочки, а не id варианта. `GET /v1/products/:id` объясняет, что пустое значение свойства означает либо незаполненное свойство, либо свойство, обслуживаемое только каталожным семейством, и как различить эти случаи одним перекрёстным вызовом. `GET /v1/products/fields` прямо говорит, что дескриптор `PROPERTY_<N>` несёт только название и куда идти за значениями. В справочнике ошибок исправлен перечень сущностей с обязательным фильтром.

### FIX-0803-5: Обращения в поддержку работают при заморозке баланса

**Было**

На аккаунте, замороженном из-за баланса, все ручки `/v1/feedback` возвращали `402 ACCOUNT_FROZEN` — сообщить о проблеме из продукта было нельзя ровно в тот момент, когда это нужнее всего.

**Стало**

По ключу доступна вся переписка: `POST /v1/feedback` (создать), `GET /v1/feedback` (список), `GET /v1/feedback/{id}` (открыть обращение) и `POST /v1/feedback/{id}/comments` (ответить команде). Все четыре читают и пишут только ваши же данные. Запись вложений (`POST /v1/feedback/attachments`), их скачивание и `PATCH /v1/feedback/{id}` остаются под гейтом заморозки, как и остальные `/v1`-эндпоинты.

### FIX-0803-6: include возвращает полную связанную запись, а не только метаданные

**Было**

При `?include=<relation>` во вложенном объекте приходили только метаданные связи — без `id` и полей связанной записи.

**Стало**

`include` возвращает полную связанную запись (`id` + поля), как и описано в контракте — отдельный запрос за связанной сущностью больше не нужен.

### FIX-0803-7: методы дашборда Открытых линий в раскатке возвращают METHOD_NOT_YET_AVAILABLE

**Было**

На портале, куда обновление ещё не приехало, методы дашборда Открытых линий отдавали сырой `422 BITRIX_ERROR` — по нему нельзя было отличить «метод раскатывается» от реальной ошибки интеграции.

**Стало**

Такой ответ распознаётся и возвращается как `422 METHOD_NOT_YET_AVAILABLE` с версией релиза — понятный сигнал, что метод пока не доступен на этом портале, а не сбой интеграции. Ответ не меняется и при регулярном опросе: такие вызовы больше не засчитываются в защиту от петли ошибок, поэтому вместо понятного `422` не приходит `429 ERROR_LOOP_DETECTED` (для методов, которые не раскатываются, защита работает как раньше).

### FIX-0803-8: запись исходников в хранилище возвращает точные коды ошибок вместо общего 500

**Было**

Клиентские сбои записи в хранилище исходников маскировались общим `500 SOURCE_STORAGE_ERROR` — по нему нельзя было понять, что делать.

**Стало**

Причина различима: при недостатке средств на балансе — `402 BILLING_INSUFFICIENT`, при временном сбое выдачи ключей доступа к хранилищу — `503 STORAGE_STS_UNAVAILABLE` (можно повторить запрос). Таблица кодов на странице source-storage дополнена.

### FIX-0803-9: список значений в фильтре statuses отклоняется чистым 400, а не 500

**Было**

`GET /v1/statuses` со списком значений в фильтре (`{поле: {$in: [...]}}` или массив) уходил в Bitrix24, и метод справочника отвечал по-разному в зависимости от поля: по `id` и `name` — внутренней ошибкой, которая доезжала до клиента как `502 BITRIX_UNAVAILABLE`, по `entityId`, `statusId`, `semantics` и `sort` — ошибкой «значение должно быть строкой», а по `categoryId` — успешным ответом с записями чужой воронки.

**Стало**

Список значений отклоняется до вызова Bitrix24 с `400 UNSUPPORTED_FILTER` по любому полю фильтра: метод справочника не поддерживает его нигде. Принимается одно точное значение (`{поле: значение}`); несколько значений запрашивайте отдельными вызовами или через `POST /v1/batch`.

### FIX-0803-10: /v1/tasks/:taskId/time принимает ключ со скоупом task

**Было**

Эндпоинт учёта времени задачи возвращал `403 INSUFFICIENT_SCOPE` ключу со скоупом `task` — работал только `tasks`, хотя это алиасы одного разрешения.

**Стало**

`task` и `tasks` трактуются как алиасы (как на всех остальных task-эндпоинтах) — ключ с любым из них проходит.

### NEW-0803-11: одношаговое создание galaxy-приложения принимает `healthPath`

Тело [POST /v1/infra/servers](/docs/infra/servers/create) с полем `source` теперь принимает необязательное поле `healthPath` — путь, по которому проверяется готовность приложения внутри контейнера. Валидация та же, что у [POST /v1/infra/servers/{id}/deploy](/docs/infra/deploy/deploy): строка до 500 символов, начинается со `/`. Значение по умолчанию — `/`.

Раньше `healthPath` был объявлен только в теле деплоя, поэтому одношаговый вызов, который платформа сама рекомендует в `GET /v1/me`, отвечал `400 UNKNOWN_PARAM` — а описание в `/v1/me` при этом называло `healthPath` поддерживаемым на галактическом пути. Поле принято именно там, где рекомендация его обещает; на создании отдельного сервера оно игнорируется.

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

Ничего менять не нужно — поле необязательное. Если раньше приходилось разбивать вызов на два шага только ради `healthPath`, теперь достаточно одного.

### FIX-0803-12: `/start` и `/wake` у galaxy-приложения объясняют, почему они не применимы

**Было**

[POST /v1/infra/servers/{id}/start](/docs/infra/lifecycle/start) и [POST /v1/infra/servers/{id}/wake](/docs/infra/lifecycle/wake) проверяли статус раньше типа сервера, поэтому galaxy-приложение вне списка разрешённых статусов (например `RUNNING`, `ERROR` или `STOPPED`) получало текст про standalone-сервер: «Server is RUNNING; /start requires one of SLEEPING, ERROR, PROVISIONING». Формально верно и бесполезно: перечисленные статусы тоже не помогли бы, а о том, что у приложения-контейнера вообще нет облачной машины, не говорилось ничего.

**Стало**

Проверка типа идёт первой — как в `/reboot`. Для galaxy-приложения в таком статусе `message` называет причину («это galaxy-приложение, у него нет облачной машины»), а `userMessage` называет операции, которые действительно работают: `POST /v1/infra/servers/{id}/deploy` (годится в любом из этих статусов) и `POST /v1/infra/servers/{id}/reboot` (только для запущенного или упавшего приложения). Код ошибки не меняется — по-прежнему `SERVER_WRONG_STATE` (422) с полями `currentState` и `availableActions`.

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

Ничего менять не нужно: HTTP-статус и код те же, изменился только текст. Ответ для статусов ИЗ списка разрешённых остался прежним побайтово — в частности, `/start` и `/wake` для спящего galaxy-приложения по-прежнему отвечают задокументированным `VM_MISSING`.

### FIX-0803-13: причина падения galaxy-приложения называется прямо в `provisionError`

**Было**

Когда приложение собиралось, запускалось и тут же падало, `provisionError` содержал только обобщённый вывод: «приложение не удержалось, смотрите логи» или, если сработал признак нехватки памяти, «вероятно, превышен лимит памяти галактики». Настоящая причина — например `TypeError: webidl.util.markAsUncloneable is not a function` из-за несовместимой версии среды — лежала в `buildLog`, а в списке серверов виден только `provisionError`. Поэтому по короткому тексту нельзя было понять, дело в памяти или в коде, и совет «перейдите на отдельный сервер» отправлял по ложному пути.

**Стало**

К той же формулировке дописывается строка из логов контейнера: `… Actual cause from the container logs: <строка>`. Строка выбирается из хвоста, который платформа снимает при отказе: сначала типизированное исключение или код ошибки (`TypeError: …`, `EADDRINUSE`, `FATAL ERROR: … heap out of memory`), затем известные причины сборки, затем последняя строка с признаком ошибки. Хвост проходит ту же очистку, что и `buildLog` — внутренние пути хоста заменяются на `<build-context>`. Если полезной строки в хвосте нет, текст остаётся прежним. Формулировка про память сохраняется и дополняется причиной: признак `oom` иногда срабатывает и там, где память ни при чём, и тогда приклеенная строка — единственная правда, которую видит читатель.

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

Ничего менять не нужно. Прежние подстроки в тексте сохранены, поэтому клиент, который сопоставлял их, продолжает работать; появился только дописанный хвост. Полный лог по-прежнему доступен в `buildLog` (`GET /v1/infra/servers/{id}`).

### FIX-0803-14: `/reboot` спящего galaxy-приложения запускает починку хоста и говорит об этом

**Было**

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

**Стало**

Перед тем же отказом `422 SERVER_WRONG_STATE` платформа запускает фоновую починку туннеля хоста и, если починка действительно началась, добавляет в ответ необязательный объект `hint` с полями `reason`, `recovery` (какую операцию повторить) и `retryAfterSeconds` (нижняя граница ожидания, не обещание). Если починка не началась — сработал рубильник, туннель на самом деле жив, ремонт уже идёт или машина заблокирована для пробуждения, — `hint` отсутствует: сообщать о запуске, которого не было, нельзя. То же поведение добавлено в перезапуск из кабинета.

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

Ничего менять не нужно: код и HTTP-статус те же, `hint` аддитивен. Клиенту, который читает `hint`, достаточно повторить `POST /v1/infra/servers/{id}/deploy` через названное время.

### FIX-0803-15: zip-архив в исходниках galaxy-приложения больше не падает на распаковке

**Было**

Платформа определяет формат архива по его первым байтам и заявляет `.zip` поддерживаемым, но на галактическом пути (`POST /v1/infra/servers` с полем `source` и `POST /v1/infra/servers/{id}/deploy` для galaxy-приложения) архив передавался на распаковку без подготовки хоста. Если распаковщик на хосте отсутствовал, деплой падал с текстом вида `exec: "unzip": executable file not found in $PATH` — из него не следовало ни что делать, ни что тот же архив в `.tar.gz` прошёл бы.

**Стало**

Перед загрузкой zip-архива платформа доустанавливает распаковщик на хосте (тот же шаг уже выполнялся на отдельном сервере). Шаг идемпотентен: если распаковщик уже есть, он ничего не делает, и на повторных деплоях времени не занимает. Если установить не удалось, архив не загружается вообще, а деплой завершается отказом с честной причиной и подсказкой прислать тот же исходник как `.tar.gz`; текст доступен в `buildLog` и в `provisionError`.

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

Ничего менять не нужно. Деплои с `.tar.gz` идут прежним путём без изменений.

### NEW-0803-16: Версия исходников принимает до 500 МБ, а деплой по нашей ссылке линкуется к версии на любом ключе

**Было**

Архив можно было сохранить версией только до 200 МБ, при том что тот же архив разрешалось передать прямо в теле деплоя до 500 МБ. Крупный проект деплоился единственным способом — целиком в теле запроса.

Отдельно: деплой формы `{"source": {"url": "…"}}` по ссылке, полученной из [GET /v1/infra/servers/:id/sources/:versionId/download](/docs/source-storage), не связывался с версией, если сервер принадлежит персональному ключу `vibe_api_*`. В ответе приходило `data.source.autoSaved: false` и `skippedReason: "external-url-or-toggles-off"`, а у версии оставались пустыми `linkedDeployId` и `deployStatus`.

**Стало**

Потолок версии исходников — 500 МБ на обоих приёмных эндпоинтах: [POST /v1/infra/servers/:id/sources](/docs/source-storage) и [POST /v1/apps/:id/sources](/docs/source-storage). Тело по-прежнему принимается потоком, поэтому размер архива не влияет на скорость приёма. Заявленная в `Content-Length` длина сверх потолка отбивается кодом `413` до чтения тела. Значение публикуется в `capabilities.apps.sourceStorage.limits.maxBlobBytes` у `GET /v1/me` — теперь `524288000`.

Деплой по нашей ссылке связывается с версией независимо от типа ключа-владельца сервера: ответ несёт `data.source.autoSaved: true` и `savedVersionId`, а у версии заполняются `linkedDeployId` и `deployStatus`.

**Затронутые эндпоинты:** [POST /v1/infra/servers/:id/sources](/docs/source-storage), [POST /v1/apps/:id/sources](/docs/source-storage), [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy), `GET /v1/me`

### FIX-0803-17: отзыв доступа гасит все ключи связки, а не только последний

**Было**

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

**Стало**

Отзыв доступа гасит все ключи, которые пользователь выдал приложению для этого портала, включая ключи прежних авторизаций. Ключ, переживавший отзыв, теперь отвечает `401 KEY_INACTIVE`. Ключи других сотрудников того же портала не затрагиваются. Партнёру достаточно штатной обработки `401 KEY_INACTIVE` — предложить пользователю повторно авторизоваться.

## 2026-08-02

### FIX-0802-1: подкоманда batch со своим start=-1 больше не отдаёт выдуманный total

Подкоманда `POST /v1/batch` со своим `params.start: -1` больше не получает выдуманное количество записей. Значение `-1` — это инструкция Битрикс24 «не считай коллекцию», поэтому счёта в ответе портала нет; конверт же брал за размер то, что оказалось под рукой — эхо `result_total` от методов, которые в этом режиме отдают `0` рядом с полной страницей, либо просто длину первой страницы. Затронуты обе ветки: подкоманда с `limit` до 50 и подкоманда с `limit` больше 50, которая читается отдельной автопагинацией.

**Было**

`{"entity":"activities","action":"list","params":{"start":-1}}` → `meta.<id>.total: 0` и `data.totals.<id>: 0` рядом с 50 записями, `meta.<id>.hasMore: false`.
`{"entity":"deals","action":"list","params":{"limit":5000,"start":-1}}` → 50 записей, `meta.<id>.total: 50`, `meta.<id>.hasMore: false` — то есть на запрос 5000 записей приходил уверенный ответ «их всего 50».

**Стало**

На такой подкоманде ключей `total` нет ни в `meta.<id>`, ни в `data.totals` — счёт не заказывался, и придумывать его нечем. `hasMore` определяется полнотой страницы: заполненная до запрошенного лимита → `true`, короткая → `false`. Полнота считается против того ограничения, которое реально ушло в Битрикс24, поэтому `{"limit":10,"start":-1}` при 10 записях в ответе — это полная страница, и `hasMore` там теперь `true`, а не `false`. Обход не строит план дочитывания из выдуманного размера и возвращает непрерывный префикс.

Признак «счёт не заказан» читается по нормализованному значению: `start` приводится к целому числу так же, как это делает Битрикс24 (усечение к нулю, строка читается по числовому префиксу), поэтому `-1`, `"-1"`, `-1.5`, `"-1abc"`, `"-1 x"` — одно и то же. Положительное смещение (`start: 100`) — обычный счётный курсор, `total` по нему приходит как раньше.

Значение, которое числом не является вовсе (`"abc"`, пустая строка, `null`, объект, массив, `true`), в Битрикс24 больше не уходит: оно читается как непереданное. Страница от этого не меняется — «начать с нуля» и «`start` не передан» это одна и та же страница, — но такая подкоманда попадает под общий выбор платформы и может прийти без `total`.

Отдельно: подкоманда, завершившаяся ошибкой, больше не публикует `data.totals.<id>` рядом с этой ошибкой.

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

Клиент, который передавал `start: -1` и читал `total`, получал заведомо неверное число: у методов формы «эхо `result_total: 0`» это был ноль, у остальных — длина страницы. Если цикл чтения останавливался по `meta.hasMore`, он обрывался на первой странице. Проверяйте наличие ключа `total` (`meta.<id>.total !== undefined`), а продолжение обхода ведите по `meta.<id>.hasMore`. Нужен точный счёт — не передавайте `start: -1`: этим значением подсчёт отменяет сам клиент, поэтому вернуть по такой подкоманде нечего, и `withTotal: true` его не восстановит.

### BC-0802-2: доступ коробочного портала больше не выдаётся безусловно

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

**Было**

Коробочный (self-hosted) портал считался коммерческим всегда: отметка ставилась при подключении портала, а не по факту оплаты. Ни подписка Маркетплейса Битрикс24, ни её окончание на доступ к Вайбкоду не влияли.

**Стало**

Доступ коробки определяется её подпиской Маркетплейса Битрикс24 — как у облачного портала. Ответы меняются там, где раньше доступ был всегда: [POST /v1/infra/servers](/docs/infra) у портала без действующей подписки отвечает `402` с кодом отказа вместо создания сервера, а `capabilities.servers.create` в [GET /v1/me](/docs/quickstart) приходит недоступной, с причиной. Те же правила действуют на создании агентов и ботов, которые заводят сервер.

Платная лицензия Битрикс24 сама по себе доступ к Вайбкоду не даёт: лицензия — про коробку, подписка — про Маркетплейс. Портал с оплаченной коробкой, но без подписки, попадает под отказ.

Пока состояние подписки прочитать не удалось, доступ **не** ограничивается: отсутствие сигнала не приравнивается к отсутствию подписки.

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

- `wasEverCommercial` в [GET /v1/me](/docs/quickstart) у коробочных порталов перестаёт быть односторонним: у портала, за которым не нашлось ни подписки, ни платежей, значение однократно меняется с `true` на `false` — прежнее ставилось при подключении, а не по наблюдению.
- `placements.bindPrerequisite` в том же ответе у коробки начинает описывать подписочную модель (другой набор `errorCodes`, другой `note`) — раньше портал без определённого региона описывался как международный.

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

Проверять `capabilities.servers.create` в [GET /v1/me](/docs/quickstart) перед созданием инфраструктуры и обрабатывать `402` на создании — код и текст отказа приходят в теле ответа. Если подписка оформлена, а отказ приходит, поможет принудительное обновление: `GET /v1/me?refresh=tariff`. Не полагаться на монотонность `wasEverCommercial` для коробочных порталов.

## 2026-08-01

### FIX-0801-1: тело ответа AI-эндпоинтов не несёт служебных полей платформы

**Было**

В ответах `POST /v1/chat/completions` на моделях Битрикс24 поле `system_fingerprint` отдавало служебный идентификатор инфраструктуры платформы. Рядом с объявленными полями приходили и другие служебные поля, которых нет в документации, — как в самом конверте ответа, так и внутри `choices` и `choices[].message`. У потоковых ответов и у `POST /v1/embeddings` эти поля не наблюдались, но проверка там тоже не стояла.

**Стало**

`system_fingerprint` отдаётся нейтральным значением `vibecode`. Если апстрим отпечатка не прислал — поля в ответе нет, как и раньше. Служебные поля убраны из конверта ответа и из объектов `choices`; внутри `choices[].message` убран контейнер `provider_specific_fields`. В служебном событии ошибки внутри потока объект `error` сохраняет поля `message`, `type`, `param`, `code`, `retryAfter` и `retryable`; служебные поля рядом с ними убраны.

Объявленный контракт не изменился: `id`, `object`, `created`, `model`, `choices`, `usage` у чата и `object`, `data`, `model`, `usage` у эмбеддингов приходят как прежде — включая расширения провайдера внутри `usage`, поля рассуждения внутри `choices[].message` и вызовы инструментов. Служебное событие ошибки внутри потока по-прежнему приходит и по-прежнему несёт `error`. Клиенту менять ничего не нужно.

Правка касается только тела ответа моделей Битрикс24. В служебном событии ошибки внутри потока текст `error.message` теперь приводится к публичному имени модели, а если апстрим положил в `message` не строку — поле не возвращается вовсе. Текст ошибок в обычных ответах платформы (`4xx`, `5xx`) не изменился.

### FIX-0801-2: деплой galaxy-приложения больше не сообщает об обрыве после фактически успешного обновления

**Было**

Если соединение с площадкой обрывалось в середине [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy), платформа перепроверяла состояние приложения один раз и, если ответ на эту проверку не приходил, отдавала `502 GALAXY_DEPLOY_INTERRUPTED`. В типичном случае обновление в этот момент ещё шло и завершалось успешно — уже через несколько секунд приложение отвечало на `health`, работало на новой версии и с новыми переменными окружения. Отличить такой мнимый отказ от настоящего можно было только вручную, повторный вызов деплоя разворачивал ту же версию заново.

**Стало**

После обрыва платформа перепроверяет состояние приложения не однократно, а в течение ограниченного времени, и если новая версия поднялась — отвечает `success`, как если бы обрыва не было. `502 GALAXY_DEPLOY_INTERRUPTED` остаётся только для случаев, когда за это время подтверждения так и не появилось. Время ожидания подобрано под окно, в течение которого платформа обязана ответить, поэтому длительность запроса в худшем случае не растёт.

Тело этой ошибки дополнительно несёт `error.retryable: true` — признак, по которому клиент может отличить её от неустранимого отказа, не разбирая текст. Прежние поля (`error.code`, `error.message`, `error.hint`) не изменились.

### BC-0801-3: публикация проверяет авторизацию приложения раньше свежести снапшота

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

**Было**

[POST /v1/apps/:id/publish](/docs/apps/publish) сначала проверял свежесть снапшота исходников, и только потом — авторизацию приложения. У вызывающего без авторизации ответ зависел от постороннего условия: при устаревшем снапшоте приходил `409 SNAPSHOT_REQUIRED`, при свежем — `400 NO_USER_TOKEN`. Чередование читалось как «проверка токена то проходит, то нет», хотя авторизации не было ни в одном из случаев.

**Стало**

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

Изменение затрагивает случаи, где раньше отвечал не тот код: **нет авторизации И снапшот устарел или отсутствует** — раньше `409 SNAPSHOT_REQUIRED`, теперь `400 NO_USER_TOKEN`; **нет авторизации И итоговое имя для каталога длиннее лимита** — раньше `400 TITLE_TOO_LONG_FOR_CATALOG`, теперь `400 NO_USER_TOKEN` (статус тот же, код другой). Если авторизация есть, но токен не удалось продлить, ответ по-прежнему `400` и приходит после гейта свежести. Публикация на self-hosted-аккаунте через ключ разработчика авторизации приложения не требует и этой проверкой не затронута.

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

Если ваш обработчик реагировал только на `409 SNAPSHOT_REQUIRED` и повторял сохранение исходников в цикле, добавьте ветку на `400 NO_USER_TOKEN` — в ней нужно авторизовать приложение, а не сохранять исходники ещё раз. Поле `error.hint` в этом ответе описывает действие.

### NEW-0801-4: подсказки в ответах публикации: hint при NO_USER_TOKEN и presentedAt при SNAPSHOT_REQUIRED

Ответ `400 NO_USER_TOKEN` у [POST /v1/apps/:id/publish](/docs/apps/publish) теперь несёт объект `error.hint` с полями `requiredAction` (что именно сделать, чтобы авторизовать приложение), `docsUrl` и `oauthDocsUrl`. Форма объекта совпадает с `error.hint` у `409 SNAPSHOT_REQUIRED` на этом же эндпоинте.

Ответ `409 SNAPSHOT_REQUIRED` дополнен полем `error.hint.lastSnapshot.presentedAt` — время, когда версию последний раз предъявили сохранением. Именно от него считается `ageMinutes`, поэтому по паре полей видно, почему версия признана устаревшей. Поле `timestamp` рядом по-прежнему означает время создания версии.

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

### FIX-0801-5: повторное сохранение тех же исходников открывает публикацию

**Было**

Проверка свежести перед [POST /v1/apps/:id/publish](/docs/apps/publish) отсчитывала окно от времени создания версии. Повторное сохранение тех же байтов возвращало `HTTP 201` с `deduplicated: true`, но новую версию не создавало и время создания не двигало, поэтому publish продолжал отвечать `409 SNAPSHOT_REQUIRED`. Вызывающий, у которого исходники не менялись, попадал в цикл `publish` → `409` → `POST /v1/apps/:id/sources` → `publish` → `409`, который не завершался: единственным способом обновить снапшот было изменить содержимое архива.

**Стало**

Сохранение отмечает версию как заново предъявленную, и окно свежести отсчитывается от этой отметки. Дедуплицированное сохранение открывает публикацию наравне с настоящим. Время создания версии (`data.timestamp`) не меняется, поэтому имя файла в депо и место версии в политике хранения остаются прежними.

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

Менять ничего не нужно. Рецепт из подсказки к `409` — «сохрани исходники и повтори» — теперь работает и когда исходники не менялись.

### BC-0801-6: оборвавшийся exec больше не отвечает успехом с exitCode -1

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

**Было**

Если поток [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) завершался, не прислав статус выхода, ответ приходил как `success: true` с `exitCode: -1`. Исход команды на сервере при этом неизвестен, то есть успехом такой ответ не был. В потоковом режиме (`?stream=true`) поток в этом случае просто молча закрывался.

**Стало**

На отдельной виртуальной машине (`kind: "STANDALONE"`) такой ответ приходит как `success: false` с кодом `EXEC_NO_EXIT` и объектом `hint`, указывающим на проверку состояния сервера. В `data` возвращается накопленный к моменту обрыва вывод (`stdout`, `stderr`); полей `exitCode`, `duration` и `truncated` там нет — их значения неизвестны, и подставлять вместо них нули значило бы утверждать то, чего платформа не знает. В потоковом режиме приходит событие `error` с этим же кодом. У galaxy-приложения этот случай пока приходит по-старому.

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

Обработайте `EXEC_NO_EXIT` наравне с прочими кодами ошибок. Если код читал `data.exitCode` без проверки `success`, теперь он получит `undefined` вместо `-1` — ветвитесь по `success`. Команду можно повторить, если она идемпотентна; если нет, сначала посмотрите состояние сервера через [GET /v1/infra/servers/:id/logs](/docs/infra/deploy/logs).

### FIX-0801-7: тело JSON-ответа /exec и /deploy начинается с открывающей скобки

**Было**

В JSON-режиме (без `?stream=true`) [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) и [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) удерживают соединение, отправляя пробелы каждые 15 секунд. Эти пробелы шли **перед** JSON-документом, поэтому у команды длиннее 15 секунд тело ответа начиналось с пробелов. Клиенты со строгой проверкой формата отказывались его разбирать — команда на сервере при этом отрабатывала успешно.

**Стало**

Пробелы удержания соединения идут **внутри** уже открытого JSON-объекта, поэтому тело с первого байта — `{`. Набор полей ответа не изменился.

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

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

### FIX-0801-8: дата-время без часового пояса больше не сдвигается для порталов вне Москвы

**Было**

Значение даты-времени без явного смещения — например deadline 2026-07-15T13:00:00 — уходило в Битрикс24 как есть и читалось в часовом поясе владельца веб-хука портала, а не клиента. Интеграция из Берлина, записавшая 13:00, получала в хранилище 10:00 UTC вместо 11:00 UTC: минус час летом и минус два зимой. Значение выглядело правдоподобно, поэтому порча дат платежей, дедлайнов и встреч замечалась не сразу.

**Стало**

Клиент может объявить свой часовой пояс заголовком X-Vibe-Timezone (IANA-имя, например Europe/Berlin; браузер получает своё значение из Intl.DateTimeFormat().resolvedOptions().timeZone). Даты-время без смещения штампуются смещением этого пояса, действовавшим на саму дату значения, — переходы на летнее время учитываются по каждому значению. Это касается полей, которые действительно хранят время суток: часть полей Битрикс24 называет датой-временем, а хранит как дату, и там смещение сдвинуло бы сам день, поэтому такие поля не трогаются. Значения с явным Z или смещением не переписываются никогда. Без заголовка (или с нераспознанным поясом) поведение прежнее — существующие интеграции не затронуты.

Заголовок влияет только на запись. Битрикс24 отбрасывает суффикс часового пояса внутри фильтра, поэтому платформа его срезает и значения фильтра всегда читаются в поясе портального аккаунта. С включённым заголовком запись «2026-07-15T13:00:00» и последующий фильтр по тому же литералу не совпадут: передавайте в фильтр тот момент времени, который хотите сравнить, либо задавайте диапазон с запасом на смещение.

### FIX-0801-9: удаление бота идемпотентно: «бота уже нет» — это успех, а не 502

**Было**

[DELETE /v1/bots/:botId](/docs/bots/management/delete) отвечал `502 BOT_DELETE_PARTIAL` и сохранял запись в базе Вайбкод при ЛЮБОЙ ошибке Битрикс24 — в том числе когда Битрикс24 сообщал, что такого бота у него уже нет. Целевое состояние было достигнуто, а вызов считался провалившимся, и запись оставалась в `GET /v1/bots` навсегда: обычным удалением её было не убрать, помогал только `?force=true`.

**Стало**

Если Битрикс24 отвечает, что бота у него нет, удаление считается успешным: запись удаляется, ответ `200` содержит новое поле `data.alreadyAbsentOnB24: true`. Дополнительно, когда Битрикс24 отклоняет отмену регистрации по иной причине, Вайбкод один раз проверяет, есть ли бот на портале: если бот уже снят — тоже успех, если снятие подтвердить не удалось — прежний `502 BOT_DELETE_PARTIAL` с сохранением записи. При `?force=true` ответ на «бота нет» теперь тоже несёт `alreadyAbsentOnB24: true` вместо `forced: true` — сироты на стороне Битрикс24 в этом случае не возникает. В теле `502` появилось поле `error.incidentCode` — шестизначный код для обращения в поддержку.

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

Менять ничего не нужно. Сценарий «удалил бота, получил ошибку, бот остался в списке» больше не возникает; `?force=true` остаётся только для порталов, которые недоступны навсегда. Если код ветвится на `data.forced`, учтите, что в случае «бота на Битрикс24 уже нет» теперь приходит `data.alreadyAbsentOnB24`.

### FIX-0801-10: сообщения об ошибках Битрикс24 приходят без HTML-разметки

**Было**

Валидационный текст, полученный от Битрикс24, форвардился в поле `error.message` вместе с прицепленным тегом `<br>`. Клиент, выводящий сообщение как текст — а JSON-контракт ровно это и предполагает, — показывал пользователю буквальный тег после каждой ошибки, на его родном языке. То же касалось поля `error.validation[].message`.

**Стало**

Разметка убирается на границе ответа: `<br>` в любом написании становится переводом строки. Именно переводом, а не пробелом — Битрикс24 склеивает через этот тег ошибки по разным полям, и граница между ними сохраняется. Локализация не меняется: текст остаётся на языке портала и не переводится. Угловые скобки внутри пользовательских данных (адрес почты в тексте ошибки, знак сравнения) не затрагиваются — убирается именно тег переноса строки, а не разметка вообще. То же касается ответов `POST /v1/batch`, где ошибка приходит по каждому подвызову. Если вы вырезали `<br>` на своей стороне, эту обработку можно снять.

Заодно введены два технических ограничения: сообщение длиннее 8192 символов усекается, а из массива `error.validation` отдаётся не больше 100 элементов. Оба нужны потому, что и текст, и число полей приходят от портала и ничем не ограничены; на реальных ответах ни то, ни другое не срабатывает.

Охват: `error.message` и `error.validation[]` во всех конвертах ошибок V1, ответы `POST /v1/batch` и `POST /v1/{entity}/batch`, `meta.pageErrorSample` при автопагинации, `POST /v1/bots`. У ответа `POST /v1/chats/messages/bulk` при этом поля ошибок приведены к общей форме `{code, message}` — раньше объект ошибки портала форвардился как есть, со своими ключами `error`/`error_description`.

Отдельно: счётные фразы в письмах на французском и португальском теперь считают по правилам CLDR — ноль относится к единственному числу («0 jour», не «0 jours»). И англоязычная подсказка про модуль «Универсальные списки» больше не содержит русского названия на международных порталах.

### NEW-0801-11: место встраивания CALL_CARD — панель в карточке звонка

**Было**

`GET /v1/placements/available` не отдавал `CALL_CARD`, а `POST /v1/placements/bind`
отвечал `VALIDATION_ERROR` на этот код — хотя Битрикс24 его поддерживает.

**Стало**

`CALL_CARD` есть в справочнике и принимается на привязку. Приложению нужен скоуп
`telephony`: Битрикс24 отдаёт это место только приложениям с таким правом, иначе
привязка отклоняется с «Placement not found». Тот же скоуп теперь требуется и для
соседнего `TELEPHONY_ANALYTICS_MENU` — раньше он был в справочнике без права, и
привязка молча не удавалась на стороне Битрикс24.

### FIX-0801-12: четыре тихих отказа: дела, склады, файлы, пользовательские поля

**Было**

Четыре запроса отвечали кодом 200 и возвращали не то, о чём их просили.

Дела: поля `providerParams` и `settings` объявлены типом `object`, но пустое значение приходило пустым массивом. Тип поля зависел от наполнения, поэтому клиент, собранный по схеме, падал на десериализации именно тех записей, где значение пустое.

Склады: [GET /v1/warehouses](/docs/entities/warehouses/list) и [GET /v1/warehouses/:id/stock](/docs/entities/warehouses/stock) со смещением, не кратным 50, отдавали первую страницу — `offset=1`, `offset=2` и `offset=3` возвращали одни и те же записи, при этом `hasMore` сообщал, что дальше есть ещё. Обход по смещению зацикливался на первой странице.

Файлы и папки: фильтр по полю, которое Битрикс24 не умеет фильтровать (`createdBy`, `size`, `updatedBy`), молча отбрасывался, и вместо отобранных записей приходило всё содержимое папки. Несуществующее имя поля вело себя так же.

Пользовательские поля: `DELETE /v1/userfields/:entity/:id` и `DELETE /v1/items/:entityTypeId/userfields/:id` с заголовком `Content-Type: application/json` и пустым телом отвечали ошибкой `FST_ERR_CTP_EMPTY_JSON_BODY` — запрос не доходил до обработчика. Без заголовка тот же запрос работал.

**Стало**

Дела: пустое значение `providerParams` и `settings` приходит пустым объектом, объявленный тип верен всегда. Непустые значения не изменились.

Склады: смещение построчное — `offset=1&limit=3` возвращает вторую, третью и четвёртую записи. Если запрошенное окно не покрывается одной страницей Битрикс24, `data` придёт пустым, а в `meta.warnings` — код `OFFSET_BEYOND_FETCHED_PAGE`.

Файлы и папки: фильтр по неподдерживаемому полю отвергается ошибкой `400 UNSUPPORTED_FILTER` со списком полей, по которым фильтровать можно: `id`, `name`, `code`, `storageId`, `type`, `folderId` (для папок — `parentId`), `deletedType`, `createdAt`, `updatedAt`, `deletedAt`. Навигация по дереву не затронута: родительская папка остаётся в списке разрешённых, поэтому обе формы — параметром `?folderId=` и фильтром `?filter[folderId]=` — работают как раньше.

Пользовательские поля: удаление принимается с заголовком `Content-Type: application/json` и без него. Некорректный JSON по-прежнему отвергается, с кодом `INVALID_JSON_BODY`.

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

Ничего менять не нужно. Три оговорки на случай, если ваш код полагался на прежнее поведение: проверка `Array.isArray` больше не отличает пустое значение дела от заполненного (проверяйте число ключей); обход складов по смещению теперь честно двигается по строкам, а не по страницам; фильтр файлов и папок по полю вне списка выше теперь вернёт ошибку вместо всего содержимого папки.

### NEW-0801-13: служебные поля задачи получили названия, описания и словарь допустимых значений

[GET /v1/tasks/fields](/docs/entities/tasks/fields) теперь отдаёт человекочитаемое `label` и `description` для двадцати служебных полей Битрикс24, у которых раньше вместо названия стояло само имя поля: `NOT_VIEWED`, `DURATION_TYPE`, `GUID`, `CHAT_ID`, `CHECKLIST`, `FAVORITE`, `IS_MUTED`, `IS_PINNED`, `IS_PINNED_IN_GROUP`, `ALLOW_CHANGE_DEADLINE`, `ALLOW_TIME_TRACKING`, `NEW_COMMENTS_COUNT`, `SERVICE_COMMENTS_COUNT`, `FORUM_ID`, `FORUM_TOPIC_ID`, `EXCHANGE_ID`, `EXCHANGE_MODIFIED`, `OUTLOOK_VERSION`, `SITE_ID`, `XML_ID`.

Вместе с этим в схеме полей появились два ключа, которые Битрикс24 присылал и раньше, а мы отбрасывали: `values` — словарь допустимых значений в виде массива `[{ "value": "Y", "label": "Да" }]`, и `default` — значение, которое портал подставит, если поле не передано. Они приходят у всех полей, где портал их отдаёт, а не только у перечисленных выше — например словарь `Y`/`N` теперь виден и у `MULTITASK`, `TASK_CONTROL`, `SUBORDINATE`, `ADD_IN_REPORT`, `REPLICATE`. Подписи в `values` формирует сам портал и локализует своими настройками, поэтому у поля с кодовыми значениями подписи может не быть — так у `DURATION_TYPE` приходят только коды `secs`, `mins`, `hours`, `days`, `weeks`, `monts`, `years` (опечатка `monts` — со стороны Битрикс24, портал принимает именно это написание).

`values` — отдельный ключ, он не заменяет `items`: `items` по-прежнему отдаёт сырой перечислимый справочник у полей типа `enumeration` в формате `[{ "ID": "1", "VALUE": "Первый" }]`. Гарантия — на уровне ключа: у каждого всегда своя форма, поэтому читайте нужный по имени. На сегодняшних порталах у одного поля приходит только один из двух, но одновременное присутствие не запрещено.

Прежние вызовы работают без изменений: новые ключи аддитивны, набор полей и их типы не менялись. Пять полей — `FAVORITE`, `IS_MUTED`, `IS_PINNED`, `NEW_COMMENTS_COUNT` и `NOT_VIEWED` — описывают отношение к задаче того пользователя, от имени которого работает ключ, а не свойство самой задачи: другой ключ на том же портале увидит другие значения.

## 2026-07-31

### BC-0731-1: приём исходников требует Content-Length

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

Эндпоинты приёма исходников — [POST /v1/apps/{id}/sources](/docs/source-storage) и [POST /v1/infra/servers/{id}/sources](/docs/source-storage) — теперь принимают тело только с заголовком `Content-Length`. Запрос без него (передача по частям, `Transfer-Encoding: chunked`) получает `411` с кодом `MISSING_CONTENT_LENGTH`.

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

Подавляющее большинство клиентов заголовок и так шлют: его проставляют `curl --data-binary`, `fetch` с телом-буфером и любой HTTP-клиент, отправляющий файл целиком. Затронуты только клиенты, которые сознательно стримят тело неизвестной длины.

Заодно повторное сохранение одного и того же архива теперь стоит дороже: совпадение по содержимому определяется после приёма тела, поэтому ответ приходит чуть медленнее, а объём засчитывается в операции хранилища. Результат прежний — `deduplicated: true` и та же версия.

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

Отправлять архив целиком (`--data-binary @file` в curl, буфер или файл в теле запроса), а не потоком неизвестной длины. Если клиент стримит тело сам — посчитать размер заранее и выставить `Content-Length`.

### FIX-0731-2: имя тарифа в GET /v1/me — из справочника платформы и по-русски

**Было**

Поле `data.tariff.name` бралось из сохранённого снимка тарифа: имя лицензии, как его отдал портал, а если портал имени не дал — значение внутреннего словаря без учёта языка. На части порталов это не было названием тарифа вовсе: приходило либо русское «Демо» на нерусскоязычном портале, либо сам код лицензии, например `pro100`, выданный за человекочитаемое название.

**Стало**

Название разрешается в момент ответа. Если код тарифа известен справочнику редакций платформы, поле берётся оттуда и по-русски — `Демо-период`; имя, сообщённое порталом, при этом не используется, поэтому подпись может измениться и там, где всё было в порядке. Если код справочнику неизвестен, поле по-прежнему несёт имя от портала — как есть, на его языке. И только когда имени нет вовсе либо вместо него приходит сам код лицензии, поле равно `null`: код лицензии в качестве названия больше не подставляется никогда. Тип поля не менялся — строка или `null`, сам код по-прежнему в `data.tariff.code`.

Подписи известных редакций заодно уточнились: `Демо` → `Демо-период`, `Проект` → `Проект — архивный бесплатный`, `NFR` → `NFR — партнёрская лицензия`.

### FIX-0731-3: поиск пользователей сервера возвращает только активных сотрудников

**Было**

[GET /v1/infra/servers/:id/b24-users](/docs/infra/access/b24-users) мог возвращать неактивных пользователей и пользователей, которые не являются сотрудниками.

**Стало**

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

### NEW-0731-4: приложение само решает, включать ли авто-высоту iframe во встройке

У приложения появилось необязательное поле `placementResizeEnabled` (по умолчанию `false`). Оно управляет тем, отдаёт ли платформа при открытии встройки страницу-обёртку, которая подстраивает высоту iframe под контент вашего приложения.

Поле возвращается в [GET /v1/apps](/docs/apps/list) и [GET /v1/apps/:id](/docs/apps/get) и принимается в [PATCH /v1/apps/:id](/docs/apps/update). Прежние вызовы работают без изменений: у всех существующих приложений значение `false`, то есть поведение открытия встройки прежнее.

**Включайте только вместе с правкой на стороне приложения.** Обёртка открывает приложение во вложенном iframe на источнике платформы, поэтому у приложения появляется новый источник-предок. Если приложение отдаёт собственный заголовок `Content-Security-Policy` с директивой `frame-ancestors`, добавьте в неё источник платформы — иначе браузер откажется открывать приложение. Приложения без своей директивы `frame-ancestors` правок не требуют.

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

Чтобы приложение сообщало платформе свою высоту, оно постит родительскому окну сообщение `{ type: 'vibe:resize', height }` — контракт описан в разделе [Приложение в Битрикс24](/docs/infra/app-runtime).

### FIX-0731-5: пустое тело с заголовком JSON больше не отклоняется на маршрутах инфраструктуры

**Было**

Операция, которой тело не нужно, отвечала 400 с кодом `FST_ERR_CTP_EMPTY_JSON_BODY`, если клиент присылал заголовок `Content-Type: application/json` без тела. Так поступают клиенты, которые ставят этот заголовок на любой запрос — например axios и PowerShell `Invoke-RestMethod`. Отказ происходил на разборе тела, то есть раньше проверки ключа, поэтому по ответу нельзя было понять, что не так с доступом. Затрагивало [`DELETE /v1/infra/servers/:id/access-tokens/:tokenId`](/docs/infra/access-tokens), [`POST /v1/infra/servers/:id/wake`](/docs/infra/lifecycle/wake) и остальные операции сервера без тела.

**Стало**

Пустое тело принимается как `{}`, и операция отвечает по существу — 401 на неверном ключе, 404 на несуществующем сервере, 200 при успехе. Прежний обход, когда тело `{}` передавалось явно, продолжает работать. Заодно неразбираемое тело на этих маршрутах теперь даёт код `INVALID_JSON_BODY` вместо `FST_ERR_CTP_INVALID_JSON_BODY` — тот же код, что уже возвращали соседние операции того же сервера, включая `deploy`, `exec` и `lock`.

## 2026-07-30

### NEW-0730-1: withTotal в списочных вызовах пакетного запроса

Списочный вызов внутри [POST /v1/batch](/docs/batch) принимает в `params` параметр `withTotal` — тот же, что у одиночного [GET /v1/{entity}](/docs/entity-api). `withTotal: false` означает «количество не нужно»: подсчёт у Битрикс24 не заказывается, а `data.totals.<id>` и `meta.<id>.total` в ответе не приходят. Границей листания остаётся `meta.<id>.hasMore`.

Границы применимости стоит знать до того, как параметр окажется в коде. Он действует на вызовах с `action: "list"` — и на тех, у которых `limit` не больше 50 и `offset` равен нулю (именно такие уезжают в Битрикс24 одной командой пакета), и на тех, у которых `limit` больше 50. У `action: "search"` и у пакета одной сущности `POST /v1/{entity}/batch` параметр инертен — подсчёт заказывается как раньше, `total` приходит.

Правило присутствия `total` там же, что у одиночного вызова: если подсчёт не заказан, а страница пришла короче полной, точное количество всё равно приходит — оно известно из самой страницы.

У вызова, который подсчёт не заказывал, `meta.<id>.hasMore` выводится из полноты страницы, а не из отсутствующего количества: пришла полная страница — записи ещё есть, пришла короткая — коллекция закончилась. Цикл «читать, пока `hasMore`» доходит до конца и без `total`; если размер коллекции ровно кратен размеру страницы, последний вызов вернёт пустой список — это штатный признак конца, а не ошибка.

Вместе с этим у вызова, который подсчёт не заказывал, `data.totals.<id>` и `meta.<id>.total` перестают приходить и на позиционированной странице — при `offset` больше нуля. Так ведут себя и одиночные эндпоинты: количество не должно то появляться, то исчезать внутри одного обхода.

### FIX-0730-2: meta.total приходит на короткой странице, даже когда подсчёт не заказывался

**Было**

Если вызов списка не заказывал подсчёт, `meta.total` в ответе отсутствовал всегда — в том числе там, где количество было известно точно. Страница короче запрошенного `limit` означает, что коллекция на ней и закончилась, то есть число записей равно числу отданных строк, — но поле всё равно не приходило.

**Стало**

В этом случае `meta.total` приходит и содержит точное число. Правило простое: `total` появляется только на вызове с `offset = 0` и только если страница пришла короче запрошенного. Пустой результат — тоже число: `"total": 0`.

Явный `withTotal=false` в запросе по-прежнему означает «поля `total` не будет» — обещание про этот параметр не изменилось. Настройка `totalDefault` на API-ключе и платформенное умолчание точный `total` из короткой страницы не запрещают: про них такого обещания не давалось. Отсюда следствие, о котором стоит знать заранее: два одинаковых запроса от двух разных ключей могут вернуть ответы разной формы.

Правило действует и на списочных вызовах внутри [POST /v1/batch](/docs/batch).

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

Менять ничего не нужно. `meta.total` остаётся необязательным полем: проверяйте его наличие в конкретном ответе, а не предполагайте. Границей цикла листания остаётся `meta.hasMore` — арифметика по `meta.total` для этого не годится ни до, ни после изменения.

### FIX-0730-3: бесплатный тариф больше не получает 402 PLAN_NOT_ALLOWED_ON_TRIAL при деплое в галактику

**Было**

`POST /v1/infra/servers` с `{ name, source, runtime, start }` на портале с galaxy-размещением возвращал `402 PLAN_NOT_ALLOWED_ON_TRIAL` (`details.allowedPlans: ["bc-micro"]`), хотя `GET /v1/me` в `capabilities.servers.create` отдавал `available: true` с тем же `bc-micro`. Плана в запросе не было вовсе: платформа сама поднимала общий galaxy-хост на своём (не-`bc-micro`) плане, и этот внутренний выбор проверялся тем же списком планов, что и VM, создаваемая пользователем. Каждое galaxy-приложение при этом расходовало единственный слот бесплатного тарифа, поэтому второй деплой упирался в `402 TRIAL_PORTAL_LIMIT`.

**Стало**

Одношаговый create-and-deploy работает на бесплатном тарифе. Общий galaxy-хост занимает единственный VM-слот, план ему выбирает платформа (минимальный стабильный план сегмента), а контейнеры-приложения на этом хосте слот не расходуют — их число ограничено только ёмкостью хоста. Список планов из `capabilities.servers.create.limits.allowedPlans` теперь относится только к VM, которую вы создаёте сами; `deployment.galaxyApp._rules` (правило `QUOTA`) описывает это явно. Для `placement: "dedicated"` ограничения бесплатного тарифа не изменились.

### FIX-0730-4: /v1/me не предлагает эндпоинты токенов доступа, когда раздел выключен

**Было**

Блок `data.infra.preview` ответа [GET /v1/me](/docs/keys-auth/me) всегда содержал адреса `mintUrl`, `listUrl`, `revokeUrl` и `refreshUrl` — в том числе когда раздел токенов доступа выключен на платформе и все четыре эндпоинта отвечают `503 FEATURE_DISABLED`. Соседние блоки `data.capabilities.servers.preview` и `data.deployment.preview` в том же ответе сообщали `{"available": false, "reason": "FEATURE_DISABLED"}`, то есть один ответ противоречил сам себе.

**Стало**

`data.infra.preview` следует тому же признаку, что и два соседних блока. При включённом разделе рядом с адресами приходит `"available": true`, при выключенном — `{"available": false, "reason": "FEATURE_DISABLED"}` без адресов. Правило `data.api._rules` про проверку деплоя через режим `api-bearer` называет эту проверку и запасной путь — шаги `healthcheck` и `tunnel_routing` в отчёте [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy).

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

Клиент, который читал адреса из `data.infra.preview` безусловно, при выключенном разделе получит блок без них. Проверяйте поле `available` перед обращением к адресам — вызовы по ним и раньше отвечали `503 FEATURE_DISABLED`.

### FIX-0730-5: заголовок X-Vibe-User-Id больше не смешивает идентификаторы

**Было**

Заголовок `X-Vibe-User-Id`, который платформа передаёт приложению на каждом запросе, документировался как числовой ID пользователя Битрикс24. На части маршрутов входа в него попадал внутренний идентификатор Вайбкода без какого-либо признака или значение `0` — заглушка «пользователь не опознан». Приложение отличить это от настоящего ID не могло. Опасен именно внутренний идентификатор, начинающийся с цифр: Битрикс24 приводит строку к числу, поэтому `47a4cff2-…` превращалось в `47` — валидный ID постороннего сотрудника портала. Приложение, подставлявшее заголовок в `DIALOG_ID` метода `im.message.add`, отправляло личное сообщение не тому человеку.

**Стало**

Значение заголовка всегда одно из двух: либо строка из одних цифр — ID пользователя портала Битрикс24, либо значение с явным префиксом, если ID в портале у посетителя нет: `net_` — пользователь Битрикс24 Нетворк вне портала, `share:` — анонимный посетитель по гостевой ссылке, `vibe:` — пользователь Вайбкода, чей ID в портале определить не удалось. Значение `0` не отправляется вовсе: если идентичность неизвестна, заголовка просто нет. При входе через кабинет Вайбкода платформа теперь определяет настоящий ID пользователя в портале и присылает именно его — там, где раньше приходил внутренний идентификатор.

Если приложение подставляет заголовок в параметры методов Битрикс24 (`USER_ID`, `DIALOG_ID`, `RESPONSIBLE_ID` и подобные) — проверяйте, что значение состоит только из цифр. Значение с префиксом означает, что у посетителя нет ID в портале, и передавать его в Битрикс24 нельзя. Для логов, аналитики и собственного ACL заголовок годится в любом виде. Полный контракт — [Что приходит в приложение](/docs/infra/app-runtime).

### BC-0730-6: галактика не создаётся на бесплатном тарифе Битрикс24

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

**Было**

Аккаунт Битрикс24 на бесплатном тарифе с включённым режимом галактик получал галактику под свои приложения. Одношаговое создание `POST /v1/infra/servers` с полем `source` разворачивало приложение контейнером на общем хосте, а `GET /v1/me` в блоке `deployment` описывал galaxy-контракт.

**Стало**

На бесплатном тарифе новая галактика не создаётся. Пока у аккаунта нет галактики, каждое приложение разворачивается на отдельной виртуальной машине, а создание с полем `source` возвращает `400 SOURCE_AT_CREATE_GALAXY_ONLY`. `GET /v1/me` в этом состоянии не отдаёт `deployment.galaxyApp`, ставит `deployment.primary` в `standalone` и объясняет причину в новом поле `deployment.placementNote`.

Аккаунт, у которого галактика уже есть, продолжает разворачивать приложения в ней — ограничение касается только создания новой. Коммерческий тариф Битрикс24 снимает ограничение целиком.

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

Разворачивайте в два шага, как на обычном сервере: [`POST /v1/infra/servers`](/docs/infra/servers/create) с `provider`, `name`, `plan`, `region` (без `source`), дождитесь `status: "running"` и `blackholeStatus: "CONNECTED"`, затем [`POST /v1/infra/servers/:id/deploy`](/docs/infra/deploy/deploy) с `source`, `runtime`, `start`. Модель размещения определяйте по `GET /v1/me` — по наличию блока `deployment.galaxyApp`, а не по режиму портала.

### FIX-0730-7: POST /v1/apps честнее сообщает о причине отказа на коробочном портале

**Было**

При установке приложения на коробочном портале без активной подписки «BitrixGPT + Маркетплейс» [POST /v1/apps](/docs/apps/create) возвращал непрозрачный `502 CONNECTOR_APP_INSTALL_FAILED`: причина отказа наружу не выдавалась вовсе, а сам код обещал временный сбой — повтор вызова выглядел осмысленным, хотя помочь не мог.

**Стало**

Отказ по подписке распознаётся и на коробочном портале: `POST /v1/apps` возвращает `403` с кодом, называющим причину. Аккаунт в подписочном регионе (Россия, Беларусь) без активной подписки получает `B24_MARKET_SUBSCRIPTION_REQUIRED` — вместе с понятным сообщением и ссылкой на оформление в `error.details.upgradeUrl`. Аккаунт на тарифной модели доступа, а также портал, на котором REST недоступен, получает `INT_TARIFF_REQUIRED` (нужен коммерческий тариф Битрикс24) без `error.details.upgradeUrl`: подписки, которую можно оформить, там нет. Прочие отказы установки через коннектор классифицируются как прежде.

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

Менять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал `502 CONNECTOR_APP_INSTALL_FAILED`, стоит дополнительно ловить `403 B24_MARKET_SUBSCRIPTION_REQUIRED` / `INT_TARIFF_REQUIRED` — первый приходит на аккаунте подписочного региона, второй на тарифной модели доступа и на портале с недоступным REST — и подсказывать пользователю оформить подписку на портале либо перейти на коммерческий тариф Битрикс24: этот отказ терминальный, повторять запрос бессмысленно.

### NEW-0730-8: новый отказ 429 TIMEOUT_QUARANTINE: метод, который перестал отвечать, ставится на паузу

Если один и тот же метод несколько раз подряд не ответил вашему аккаунту Битрикс24 за отведённое вызову время, Вайбкод перестаёт отправлять к нему запросы и отвечает `429` с кодом `TIMEOUT_QUARANTINE`, заголовком `Retry-After` и полем `error.retryAfter`. Пауза действует на пару «аккаунт + метод»: остальные методы работают как обычно, и от того, каким ключом сделан вызов, она не зависит.

Это отказ на стороне Вайбкод, а не лимит Битрикс24: запрос до аккаунта не дошёл, поэтому ничего не изменилось — повтор безопасен даже для методов записи. Пауза снимается сама: время от времени один вызов пропускается для проверки, и первый успешный ответ снимает её немедленно. Вмешательство не требуется, отдельного эндпоинта для снятия нет.

В ответе появилось машиночитаемое поле `error.scope`: у этого отказа `"portal"` — пауза общая для ВСЕХ ключей аккаунта, включая чужие интеграции. У соседнего отказа `OPERATION_TIME_LIMIT` (Битрикс24 приостановил метод, исчерпавший бюджет рабочего времени) оно равно `"apiKey"` — там приостановлен только вызывающий ключ. Те же два значения `error.scope` уже приходят в отказе по квоте обращений `FEEDBACK_QUOTA_EXCEEDED`, словарь общий. Различие даёт ответ на вопрос «чинить свой код или ждать вместе с аккаунтом», не разбирая текст ошибки. Рядом приходит `error.hint` с действием — на русском. Оба кода теперь описаны в [справочнике ошибок](/docs/errors). Количество чужих ключей, их имена и объём их неудач в ответе не приходят — это данные других клиентов аккаунта.

Что делать: дождаться срока из `Retry-After` и повторить, добавив к паузе случайную задержку, — а не крутить повтор в цикле. Интервал повторов при этом не сокращайте: пока пауза действует, один вызов раз в 5 минут пропускается как проба восстановления, и агрессивный повтор занимает этот слот собой — метод остаётся закрытым для всего аккаунта дольше, чем если бы вы просто подождали. Если метод не отвечает стабильно, облегчите вызов: меньше полей в `select`, меньше страница, более узкий фильтр или более узкий интервал дат. Тяжёлый запрос и есть причина, по которой аккаунт не успевает ответить, — пауза снимется тем вызовом, который аккаунт сумеет выполнить.

Код может прийти на любом вызове, который Вайбкод проксирует в Битрикс24 под именем метода Битрикс24: на одиночных чтениях и записях — как `429` с заголовком, а на подвызовах батча, которые Вайбкод исполняет отдельными запросами (поиск и список с `limit` больше 50), — внутри ответа `200` строкой `data.errors[<id>]` вида `{ "code": "TIMEOUT_QUARANTINE", "message": …, "retryAfter": …, "scope": "portal", "hint": … }`: конверт объединяет разные вызовы, поэтому заголовка `Retry-After` для отдельного подвызова у него нет, срок приходит полем. В батче одной сущности ([POST /v1/{entity}/batch](/docs/batch)) отказ приходит так же внутри `200`, но элементом массива `data`: `{ "error": { "code": "TIMEOUT_QUARANTINE", "message": …, "retryAfter": …, "scope": "portal", "hint": … } }`. Сам конверт [POST /v1/batch](/docs/batch) под паузу не попадает: он объединяет разные методы, и его собственная задержка не говорит о том, какие из них перестали отвечать. Опрос событий бота ([GET /v1/bots/{botId}/events](/docs/bots)) тоже не попадает — у него свой ответ на таймаут, с подсказкой про восстановление подписки. В поиске с разбиением по датам ([POST /v1/{entity}/search](/docs/entity-api)) отказ приходит либо тем же `429`, либо — если часть окон успела прочитаться — в `meta.windowErrorSample.code` при ответе `200`: это признак неполной выдачи.

Защита включается постепенно, по аккаунтам, поэтому этот код увидят пока не все.

### FIX-0730-9: отказ OPERATION_TIME_LIMIT в батче приходит со своим кодом и сроком, а не под общим кодом

**Было**

Битрикс24 приостанавливает метод, исчерпавший бюджет рабочего времени, примерно на 5 минут, и Вайбкод отбивает такие вызовы на входе, зная, что пауза ещё действует. На одиночном вызове этот отказ приходил как `429` с кодом `OPERATION_TIME_LIMIT`, заголовком `Retry-After` и полями `retryAfter` и `scope`. А внутри батча тот же отказ терял и код, и срок: на подвызовах [POST /v1/batch](/docs/batch), которые Вайбкод исполняет отдельными запросами (поиск и список с `limit` больше 50), он приходил как `data.errors[<id>]` с общим кодом `AUTO_PAGINATION_FAILED`, а в батче одной сущности ([POST /v1/{entity}/batch](/docs/batch)) — как `data[i].error` с кодом `CALL_FAILED` и текстом `Internal error`. Ответ был `200`, поэтому отличить приостановленный метод от внутреннего сбоя было нечем, а срок повтора не приходил вовсе — оставалось повторять вслепую по методу, который аккаунт держит закрытым.

**Стало**

Обе батчевые поверхности отдают тот же отказ, что одиночный вызов: `{ "code": "OPERATION_TIME_LIMIT", "message": …, "retryAfter": …, "scope": "apiKey", "hint": … }` — в `data.errors[<id>]` у [POST /v1/batch](/docs/batch) и в `data[i].error` у [POST /v1/{entity}/batch](/docs/batch). Конверт объединяет разные вызовы, поэтому заголовка `Retry-After` для отдельного подвызова у него нет — срок приходит полем `retryAfter`. Поле `scope` равно `"apiKey"`: приостановлена связка «ваш ключ + этот метод», другие методы работают, и другие ключи аккаунта тот же метод вызывать могут. Контраст — `TIMEOUT_QUARANTINE` со `scope: "portal"`, где пауза общая для всего аккаунта. Локализованного поля `userMessage` в `200`-конверте нет ни у одного отказа, поэтому нет и здесь. Ограничение снято и из [справочника ошибок](/docs/errors).

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

Менять ничего не нужно: коды сузились с общих до конкретного, а поля добавились. Если ваш код ветвился на `AUTO_PAGINATION_FAILED` или `CALL_FAILED`, чтобы поймать приостановленный метод, теперь для этого есть `OPERATION_TIME_LIMIT` и срок в `retryAfter` — дождитесь его и повторите, а не крутите повтор в цикле.

### NEW-0730-10: машинная схема описывает 51 эндпоинт, который работал, но в ней отсутствовал

Все они отвечали и раньше — но клиент, который строит вызовы по `/v1/openapi.json` (кодогенератор, ИИ-агент, наш собственный справочник), их не видел и не мог о них узнать.

Товарные позиции — работа с отдельной строкой и её схемой полей: `GET|PATCH|DELETE /v1/{deals,leads,quotes,invoices}/{id}/products/{rowId}` и `GET /v1/{deals,leads,quotes,invoices}/{id}/products/fields`. То же для смарт-процессов: `/v1/items/{entityTypeId}/{id}/products/{rowId}` и `.../products/fields`.

Схемы полей: `GET /{сущность}/fields` появился у 22 сущностей, у которых Битрикс24 не отдаёт метод схемы (заказы, документы, шаблоны документов, платежи, позиции корзины, статусы заказа, каталоги, разделы и цены каталога, свойства товаров, бронирования, события и группы, подразделения, сайты и страницы, шаблоны/активности/роботы бизнес-процессов, линии телефонии, конфигурации открытых линий, узлы оргструктуры). Ответ там — набор полей, объявленный обёрткой, без пользовательских (UF) полей: в описании операции это сказано прямо, чтобы никто не ждал большего.

Открытые линии: `GET|POST /v1/openline-configs`, `PATCH|DELETE /v1/openline-configs/{id}`, `POST /v1/openline-configs/search` и read-only `POST /v1/openline-configs/batch` (в батче доступны только `list`, `get`, `fields` — записи идут через перечисленные выше операции). У списка описан его настоящий конверт: `total` — размер выданного окна, а не всего набора, поэтому цикл постраничного чтения ограничивают по `hasMore`.

Бронирования: `GET /v1/bookings` и `POST /v1/bookings/search`. Окно дат (`dateFrom`, `dateTo`) описано как обязательное — без него метод Битрикс24 молча вернул бы пустой список, поэтому обёртка отклоняет такой вызов.

Конвертация лида: `POST /v1/leads/{id}/convert` — в описании операции сказано, что она не идемпотентна (повторный вызов создаст второй набор сущностей).

**Затронутые эндпоинты:** `GET /v1/openapi.json`

### FIX-0730-11: текст ошибки проверки BYOK-ключа больше не содержит сам ключ

**Было**

Если провайдер отвечал на проверку учётных данных ошибкой и повторял в ней присланный ключ, этот текст возвращался вызывающему и сохранялся в поле lastError, которое отдаёт GET /v1/ai/credentials. Скрывались только ключи вида sk-…, адреса и пары «логин:пароль@хост», поэтому ключи прочих провайдеров попадали в ответ и в поле дословно — и были доступны любому участнику портала.

**Стало**

Присланный ключ и адрес прокси удаляются из текста ошибки по значению, независимо от их формата: и в ответе POST /v1/ai/credentials, PATCH /v1/ai/credentials/{id}, POST /v1/ai/credentials/{id}/test, и в сохраняемом lastError на их месте стоит редакция. Коды ошибок и статусы не изменились.

### FIX-0730-12: многостраничный список отдаёт начало выборки вместо ошибки, когда подсчёт не удался

**Было**

Многостраничный вызов списка — [GET /v1/{entity}](/docs/entity-api) и [POST /v1/{entity}/search](/docs/entity-api) с `limit` больше 50, а также их списочные подзапросы в [POST /v1/batch](/docs/batch) — начинался с подсчёта записей. Подсчёт коллекции стоит Битрикс24 несоразмерно дорого, и если он срывался (тайм-аут, лимит запросов, ошибка портала), вызов возвращал ошибку целиком — без единой записи, хотя первая страница уже была получена.

Параметр `withTotal=false` на таких вызовах не действовал: подсчёт заказывался всё равно, `meta.total` приходил.

**Стало**

Подсчёт выполняется, только когда без него не обойтись, и его срыв больше не отменяет ответ. Если записи получены, а подсчёт не удался, приходит `200` с непрерывным началом выборки: `meta.hasMore` равен `true`, `meta.total` отсутствует, а в `meta.pageErrorSample` лежат `code` и `message` с причиной. Два новых значения `code` — оба коды самого Вайбкод, а не Битрикс24:

- `PAGE2_COUNT_FAILED` — подсчёт не удался: тайм-аут, лимит запросов или ошибка портала.
- `LAZY_COUNT_NO_PROGRESS` — обход остановлен: следующая страница не принесла ни одной новой записи, хотя в коллекции их больше, чем уже отдано.

В пакетном запросе то же самое приходит в `data.meta.<id>`: `hasMore` равен `true`, `pageErrorSample` заполнен, а `total` и `data.totals.<id>` отсутствуют — количество не сосчитано, и число отданных строк его не заменяет.

Заодно `withTotal=false` начал действовать при `limit` больше 50 — на [GET /v1/{entity}](/docs/entity-api), на [POST /v1/{entity}/search](/docs/entity-api) и на вызовах `action: "list"` внутри [POST /v1/batch](/docs/batch): теперь `meta.total` не приходит ни при каком исходе. Границы применимости у `action: "search"` внутри пакета и у пакета одной сущности `POST /v1/{entity}/batch` не изменились. Без этого параметра количество приходит как раньше.

Оговорка про нагрузку: при `limit` больше 50 этот параметр — про форму ответа, а не про стоимость. Подсчёт платформе всё равно нужен, чтобы спланировать обход, поэтому дешевле вызов не станет, а точное количество, которое короткая первая страница отдаёт бесплатно, будет отброшено. Подсчёт параметр отменяет только при `limit` не больше 50.

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

Проверьте, как код узнаёт о неполном ответе. Раньше признаком служила сама ошибка: цикл с повтором по `429` и `5xx` срабатывал сам собой. Теперь неполнота уезжает в тело успешного ответа — обработчик ошибок не сработает, а заголовка `Retry-After`, который приходил вместе с `429`, в таком ответе нет.

Признак неполноты — `meta.pageErrorSample` рядом с `meta.hasMore`. Продолжить обход можно двумя способами, и порядок между ними не произвольный.

Курсором — это первый выбор. `meta.nextAfterId` передаётся обратно как `filter[>id]`, смещение при этом остаётся нулевым, и продолжение снова идёт тем же путём, на котором подсчёт заказывается не всегда. Приходит курсор не везде: только у сущностей с курсорным обходом и только при сортировке строго по `id` по возрастанию.

Смещением — запасной путь. Новый `offset` равен исходному плюс число отданных записей, но вызов с ненулевым `offset` уходит на счётный путь и заказывает ровно тот подсчёт, который только что не удался. После `PAGE2_COUNT_FAILED` это с высокой вероятностью тот же тайм-аут, поэтому повторяйте с паузой, а не в плотном цикле.

У [GET /v1/tasks](/docs/entities/tasks/list) курсора нет — задачи идут без курсорного обхода, и `nextAfterId` в их ответах не приходит. Для них смещение остаётся единственным способом продолжить, со всеми оговорками выше.

Границей цикла листания по-прежнему остаётся `meta.hasMore`, а не арифметика по `meta.total`: поле было необязательным и до этого изменения.

### FIX-0730-13: спека объявляет обязательные поля на создании и перестала требовать их на обновлении

**Было**

Создание через POST /v1/bizproc-robots, POST /v1/bizproc-activities и POST /v1/folders отклонялось, если не передать code, name и handler (для папки — name), а спека объявляла эти поля необязательными: клиент или SDK, сгенерированный по ней, получал отказ на первом же вызове. Обратная сторона той же причины: описание тела запроса было общим для создания и обновления, поэтому там, где обязательные поля всё же были объявлены — POST /v1/activities, POST /v1/documents, POST /v1/bizproc-templates — их требовал и PATCH, хотя частичное обновление одного поля API принимает.

**Стало**

Требование объявлено на операции создания, а не в общем описании тела. POST перечисляет поля, без которых откажет рантайм; PATCH их не требует и принимает частичное обновление, как и раньше на деле. Поведение не изменилось — изменилось описание, которое теперь соответствует обеим операциям. Те же поля помечены обязательными и на двух других описательных поверхностях: GET /v1/folders/fields с аналогами и GET /v1/guide.

## 2026-07-29

### FIX-0729-1: комментарий к задаче больше не отбивается по правам ключа

**Было**

[POST /v1/tasks/:taskId/comments](/docs/entities/task-comments/create) на порталах с новой карточкой задач мог вернуть `403` с сообщением портала «Недостаточно прав доступа: отсутствует необходимый scope» — даже когда у ключа есть право `task`, а соседние вызовы задач в ту же секунду отвечают `200`. Формулировка уводила в тупик: она читалась как «выдайте приложению доступ к задачам», хотя набор прав определяется при выпуске ключа, а не действиями пользователя.

**Стало**

Права на комментарий выдаются ключу в обеих формах, которых требуют старый и новый маршрутизаторы Битрикс24, поэтому вызов проходит. Ключам, выпущенным раньше, набор прав досылается на месте при первом таком отказе, и запрос повторяется — вмешательство не нужно. Если после этого портал всё равно отказывает, ответ `403` теперь прямо называет причину (у вебхука ключа набор прав уже, чем у самого ключа) и подсказывает переиздать ключ — вместо пересказа сообщения портала.

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

Менять ничего не нужно. Клиент, ловивший этот `403` как постоянную ошибку, теперь получает `201`.

### FIX-0729-2: изменение доступа к приложению на общем хосте доезжает до его показа в Битрикс24

**Было**

Для приложения на общем хосте (`kind=GALAXY_APP`), встроенного в интерфейс Битрикс24, изменение списка доступа доезжало до показа не всегда. Пользователь, у которого доступ отозвали, мог продолжать видеть приложение на своём месте встройки — доступ к самому приложению при этом уже был закрыт.

Это касалось смены политики доступа и правки списка через [PATCH /v1/infra/servers/:id/access-policy](/docs/infra/access/access-policy), [POST /v1/infra/servers/:id/access](/docs/infra/access/access-add) и [DELETE /v1/infra/servers/:id/access/:accessId](/docs/infra/access/access-delete).

**Стало**

Изменение доступа отражается на показе: приложение пропадает из интерфейса Битрикс24 у тех, кто доступ потерял, и появляется у тех, кому его выдали.

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

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

### FIX-0729-3: агенты и боты больше не засыпают по простою

**Было**

[PATCH /v1/infra/servers/:id/sleep](/docs/infra/lifecycle/sleep) принимал любое значение `sleepAfterMinutes`, включая серверы, созданные под агента или бота (`createdVia` agent или bot).

**Стало**

Для сервера с `createdVia` agent или bot и ненулевым `sleepAfterMinutes` эндпоинт отвечает `400` с кодом `AGENT_IDLE_SLEEP_FORBIDDEN`. Значение `null` (никогда не засыпать) по-прежнему принимается.

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

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

### BC-0729-4: сессия в Authorization должна принадлежать приложению из X-Api-Key

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

**Было**

Сессия (`vibe_session_*`) выписывается одному приложению на одном аккаунте Bitrix24, но при вызове `/v1/*` эта привязка не проверялась. Ключ авторизации приложения B принимал сессию, выписанную приложению A, и запрос выполнялся под правами ключа B — то есть чужая сессия открывала доступ к данным через ключ другого приложения.

**Стало**

`/v1/*` проверяет, что сессия в `Authorization: Bearer` принадлежит тому же приложению и тому же аккаунту Bitrix24, что и ключ в `X-Api-Key`. Несовпадение — `403` с кодом `SESSION_APP_MISMATCH`. Ключ авторизации приложения, у которого приложение отвязано или удалено, сессию больше не принимает. Личных ключей `vibe_api_*` изменение не касается: сессию в `Authorization` они не читают — ни до него, ни после.

Вызовы без `Authorization` (только по ключу) не затронуты. Сессия, предъявленная с ключом своего приложения, работает как раньше.

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

Проверьте, что оба заголовка относятся к одному приложению: `X-Api-Key` — ключ авторизации того приложения, которое выписало сессию через `POST /v1/oauth/token`. Если сервис обслуживает несколько приложений, храните пару «ключ + сессия» вместе и не берите их из разных мест.

Проверка включена с выпуска этого изменения, без переходного периода: она закрывает доступ к данным по чужой сессии. Ошибка `403 SESSION_APP_MISMATCH` описана в справочнике ошибок.

### FIX-0729-5: приложение на личном ключе снова получает X-Vibe-Authorization

**Было**

Приложение, размещённое в Black Hole на личном ключе (`vibe_api_*`), теряло заголовок `X-Vibe-Authorization` примерно через минуту после открытия плейсмента. Первые запросы проходили с сессией, дальше она пропадала и не возвращалась ни через перезагрузку страницы, ни через повторное открытие приложения — только через новое открытие плейсмента, и опять на минуту.

Причина: сессия, которую плейсмент выписывает пользователю, привязана к приложению через его адрес (`appUrl`), а восстанавливалась она только по цепочке «сервер → ключ → приложение». У личного ключа приложения нет, поэтому восстановление отвечало «сервер не найден», и Gateway запоминал этот отказ. `X-Vibe-User-Id` при этом продолжал приходить, поэтому со стороны приложения это выглядело как «пользователь есть, а токена нет».

**Стало**

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

Приложения на ключе авторизации (`vibe_app_*`) работают как раньше — у них цепочка «сервер → ключ → приложение» есть, и она остаётся главной.

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

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

### NEW-0729-6: управляющий ключ владельца, аккаунт которого ждёт удаления, получает 503

**Было**

Заморозка аккаунта на время запроса на удаление данных действовала на веб-кабинет и на
обычные ключи приложения, но не на управляющие ключи: владелец с аккаунтом в состоянии
ожидания удаления продолжал выпускать, ротировать и удалять ключи через `/v1/keys`.

**Стало**

Управляющий ключ владельца, аккаунт которого находится в процессе удаления данных,
получает `503` с кодом `ACCOUNT_PENDING_ERASURE` и заголовком `Retry-After: 3600` — так
же, как это давно работает для обычных ключей приложения. Если запрос на удаление
отменён, ключ начинает работать снова без перевыпуска.

### FIX-0729-7: деплой galaxy-приложения проверяет доступность и отдаёт шаги

**Было**

Успешный деплой galaxy-приложения через [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) возвращал `success: true, status: "running"` без `data.steps[]` и без проверки того, что приложение действительно отвечает по HTTP. Контейнер, который поднялся, но не слушал порт, всё равно рапортовался как успех — отличить рабочий деплой от сломанного было нельзя. Плюс [GET /v1/infra/servers/:id](/docs/infra/servers/get) для такого приложения показывал `runtime: null` и порт по умолчанию — развёрнутый рантайм и порт не сохранялись.

**Стало**

Успешный ответ несёт `data.steps[]`: шаг `{ step: "build", status: "ok" }` и — когда проба выполнялась — шаг `{ step: "healthcheck", status: "ok" | "warning", httpCode, healthPath }`. `status: "ok"` означает, что приложение ответило 2xx/3xx на `data.appUrl`; `warning` — ответило 4xx/5xx (всё ещё достижимо, деплой прошёл). Контейнер, который поднялся, но не ответил по HTTP на своём порту, теперь честно завершается `502 GALAXY_APP_START_FAILED` (то же семейство, что крах после старта — в теле остаётся хвост `buildLog`), а не рапортуется как успех. Необязательное поле `healthPath` (по умолчанию `/`) в теле деплоя задаёт путь пробы. `GET /v1/infra/servers/:id` теперь отражает развёрнутый `runtime` и порт.

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

Приложение, которое отвечает по HTTP на порту деплоя, ничего не заметит. Приложение, которое стартует, но не начинает отвечать в окне пробы, получит `502 GALAXY_APP_START_FAILED` вместо ложного успеха — убедитесь, что оно слушает порт, указанный при деплое, и что `healthPath` возвращает ответ. Шаг `build` в `data.steps[]` и сохранение `runtime`/порта в `GET` доступны сразу; сама HTTP-проба (шаг `healthcheck` и `502 GALAXY_APP_START_FAILED`) раскатывается — включается на платформе постепенно, поэтому до её активации деплой ведёт себя как раньше (без пробы).

### FIX-0729-8: серверы просыпаются после погашения долга и на постоплатном счёте

**Было**

Портал с постоплатным биллингом, ушедший в минус до лимита овердрафта, глушил серверы и помечал их «заморожено биллингом». После пополнения счёта API снова отвечал 200, но серверы так и оставались выключенными: `POST /v1/infra/servers/{id}/wake` возвращал отказ (`SERVER_WAKE_BLOCKED`), и снять пометку можно было только обращением в поддержку. На предоплатном биллинге тот же сценарий отрабатывал нормально.

**Стало**

Как только баланс перестаёт быть отрицательным, пометка снимается и серверы поднимаются автоматически — одинаково на предоплате и постоплате. Частичное пополнение, оставляющее баланс в минусе, снова открывает API, но серверы держит выключенными: они возвращаются к работе, когда долг закрыт полностью. Серверы, остановленные по другим причинам (истёк доступ, остановка вручную), пробуждение не затрагивает.

### FIX-0729-9: деплой не рапортует ложный успех после отката усиленного юнита

**Было**

Деплой на `POST /v1/infra/servers/:id/deploy` мог отрапортовать успех (`healthcheck:ok`, `hardening:warning`), обслуживая при этом ответ **чужого** процесса, занявшего порт приложения. Это происходило, когда усиленный (hardened) юнит падал, деплой автоматически откатывался на обычный юнит, но откат не освобождал порт — а на первой же проверке чужой держатель порта отвечал `200`. В результате выкладка сообщала об успехе, хотя новая версия порт так и не заняла и в продакшене продолжала работать предыдущая.

**Стало**

Если после отката порт по-прежнему держит тот же процесс, что заблокировал усиленный юнит (откатанный юнит порт не занял), деплой завершается ошибкой `healthcheck:error` с явным сообщением о том, что порт всё ещё занят, вместо ложного `healthcheck:ok`. Штатный откат, при котором порт занимает уже новый экземпляр приложения, по-прежнему завершается успехом.

### FIX-0729-10: кэш метаданных учитывает портал и схемы полей

**Было**

Кэш [GET /v1/statuses](/docs/entities/statuses/list) был описан как привязанный к личному ключу, а схемы `GET /v1/{entity}/fields` не были отражены в разделе кэширования. Клиент не видел в документации, какие повторные запросы получают `X-Cache: HIT` и как запросить свежую схему полей.

**Стало**

[GET /v1/statuses](/docs/entities/statuses/list) описан как кэш портала на 5 минут. `GET /v1/{entity}/fields` описан как кэш схемы полей на 5 минут с учётом портала, ключа авторизации, сущности, параметров пути, параметров запроса и языка ответа. Обход кэша через `Cache-Control: no-cache` доступен для всех этих чтений, а для `/fields` также доступен `refresh=true`.

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

Код менять не нужно. Повторные чтения метаданных меньше нагружают очередь портала, а заголовки `X-Cache` и `X-Cache-Bypass-Reason` показывают, был ли использован кэш.

### NEW-0729-11: дельта последних диалогов по параметру updatedAfter

[GET /v1/chats/recent](/docs/chats/discovery/recent) принимает `updatedAfter` — момент в формате ISO 8601, начиная с которого нужно вернуть изменённые диалоги. Это отдельный режим ответа: `data` приходит плоским массивом диалогов, а `meta` несёт `mode` со значением `delta` и `returned` с их числом.

Размером выборки в этом режиме управляет сервер — читается одна страница до 200 диалогов. Переданный `limit` на неё не влияет и возвращается обратно в `meta.requestedLimit` вместе с применённым `meta.appliedLimit`. Когда дельту не удалось подтвердить полной — Битрикс24 сообщил, что за отданной страницей есть ещё диалоги, либо форму ответа не удалось разобрать, — в `meta` приходит `truncated` со значением `true`: в этом случае не сдвигайте `updatedAfter`, а получите полный список постраничным режимом с курсором `lastMessageDate`. Число возвращённых строк признаком полноты не является.

Граница включительна — диалог, у которого `dateUpdate` равен переданному моменту, попадает в ответ. Дата обязана нести явное смещение или `Z`: значение вида `2026-06-29 10:00:00` читается по-разному в зависимости от часового пояса сервера и отклоняется с `400 INVALID_PARAMS`. Тем же кодом отклоняется сочетание `updatedAfter` с `offset` или `lastMessageDate` — постраничная навигация и дельта используют разные курсоры.

### NEW-0729-12: транзиентный 503 на границе платформы теперь говорит, через сколько повторять

Когда все реплики бэкенда на мгновение недоступны — например в момент редеплоя galaxy-приложения — граница платформы отвечает `503 SERVICE_UNAVAILABLE` на префиксах `/api/` и `/v1/`. В теле было только человекочитаемое «Retry in a few seconds», и машинного признака повторяемости не было: клиент не мог отличить секундную дыру от постоянной недоступности и либо ронял задачу, либо ретраил наугад.

Теперь этот ответ несёт HTTP-заголовок `Retry-After: 5` и поле `retryAfter: 5` внутри объекта `error` — рядом с `code` и `message`, ровно так же, как это делает сама платформа для своего транзиентного 503. Прежние вызовы не меняются: код `SERVICE_UNAVAILABLE` и статус остались теми же, добавлены заголовок и поле. Текст сообщения теперь дополнительно ссылается на заголовок — на него по-прежнему не следует опираться, ветвитесь по `error.code`. Полный список кодов — [/docs/errors](/docs/errors).

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

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

### BC-0729-13: пакетный запрос отклоняет сортировку у сущностей, чей метод Битрикс24 её не умеет

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

**Было**

Одиночный список сущности и её `POST /v1/departments/search` уже отвечали `400 INVALID_SORT_FIELD`, если метод Битрикс24 за списком не принимает порядок сортировки. Подзапрос общего `POST /v1/batch` этой проверки не имел: та же сортировка уходила в метод, тот её выбрасывал, и подзапрос возвращал успех с несортированным списком. Получалось расхождение на одном и том же запросе — через одиночный маршрут отказ, через пакетный молчание.

**Стало**

Подзапрос `POST /v1/batch` с действием `list` или `search` проверяет то же самое и отвечает `400 INVALID_SORT_FIELD` в `errors` своего подзапроса; остальные подзапросы выполняются как обычно. Отказ приходит до обращения к Битрикс24. Проверяются оба написания — и `sort`, и `order`. Это касается двух сущностей: подразделений (`departments`) и телефонных линий (`telephony-lines`). Хранилищ это НЕ касается — их метод сортировку умеет, и отказ у них снят отдельной записью этого же выпуска.

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

Если подзапрос к одной из этих двух сущностей передавал сортировку — уберите её: она никогда не применялась, список приходил в порядке Битрикс24. Нужен свой порядок — сортируйте полученный список на своей стороне. Подзапросы без сортировки, а также `limit`, `offset`, `select` и фильтр, работают как раньше. Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что параметр молча игнорировался, — сохранять было бы нечего.

### BC-0729-14: `defaultOperatorData` у открытых линий стал объектом, пустое значение — `null`

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

**Было**

Поле объявлялось массивом, и незаданное значение приводилось к `[]`. Настоящий тип другой: Битрикс24 отдаёт объект вида `{ "NAME": …, "AVATAR": … }`, когда данные оператора по умолчанию заданы. То есть [GET /v1/openline-configs](/docs/openlines/config/list) и `GET /v1/openline-configs/:id` обещали в `fields` массив, а при заполненном значении присылали объект.

**Стало**

Тип поля — `object`; незаданное значение приходит как `null`, а не как `[]`. Заполненное значение приходит объектом, как и раньше. Соседние `kpiFirstAnswerList` и `kpiFurtherAnswerList` — настоящие массивы строк, они по-прежнему приводятся к `[]`.

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

Код, который считал длину или перебирал это поле (`defaultOperatorData.length`, `.map`, `.forEach`), сломается на `null` — замените проверку на `if (config.defaultOperatorData) { … }` и читайте поля объекта напрямую. Если вы ориентировались на `fields` и ждали массив — сверьтесь с новым типом `object`.

### BC-0729-15: телефонные линии: запись `serverName`, сортировка, фильтр и смещение больше не теряются молча

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

**Было**

`serverName` в схеме числился обычным изменяемым полем, но Битрикс24 его не хранит и не возвращает: [POST /v1/telephony-lines](/docs/telephony/lines/create) с этим полем отвечал `201`, а значение исчезало; `PATCH` того же поля упирался в ошибку самого Битрикс24. Отбор, порядок и смещение вели себя так же тихо: метод Битрикс24 за этим списком не принимает входных параметров вовсе, поэтому `?order[number]=desc`, `?name=…`, `?filter[number]=…`, `?offset=50` и те же значения в теле `POST /v1/telephony-lines/search` отбрасывались, а список приходил `200` — и выглядел отсортированным, отфильтрованным и перелистнутым, хотя не был ничем из этого. В подзапросе `POST /v1/batch` тем же образом терялась сортировка.

**Стало**

Все четыре случая стали явной ошибкой до обращения к Битрикс24. Запись `serverName` — `400 READONLY_FIELD`; любая сортировка — `400 INVALID_SORT_FIELD`; любой фильтр — `400 UNSUPPORTED_FILTER`; ненулевое смещение — `400 UNSUPPORTED_OFFSET`. Отказ фильтру приходит на списке, в поиске, в `POST /v1/telephony-lines/aggregate` и в подзапросах обоих пакетных запросов — общего `POST /v1/batch` и `POST /v1/telephony-lines/batch`; отказ сортировке и смещению — на списке, в поиске и в подзапросе общего пакетного запроса (агрегат этих параметров не читает). В общем `POST /v1/batch` отказ приходит в `errors` своего подзапроса, а остальные подзапросы выполняются как обычно; если отклонены все подзапросы, запрос отвечает `400`, и разбор по подзапросам остаётся в `errors`. В `POST /v1/telephony-lines/batch` иначе: нарушение контракта отбора отклоняет весь пакет одним `400` с номером подзапроса в сообщении, ни один подзапрос не выполняется. Само поле `serverName` осталось видимым в `GET /v1/telephony-lines/fields` с признаком «только для чтения», так что понять его назначение по-прежнему можно.

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

Если вы передавали `serverName` при создании или обновлении линии — уберите поле: значение всё равно никогда не сохранялось. Если полагались на сортировку, фильтр или смещение — ни одно из них никогда не применялось; список внешних линий приложения приходит целиком одной страницей, поэтому сортируйте, отбирайте и разбивайте его на страницы на своей стороне. Обычный список без этих параметров, а также `limit` и `select`, работают как раньше. Параллельная поддержка прежнего поведения не предусмотрена: прежнее поведение состояло в том, что параметр молча игнорировался, — сохранять было бы нечего.

### NEW-0729-16: список хранилищ можно сортировать

Сортировка по полям хранилища работает на [GET /v1/storages](/docs/entities/storages/list) и в [POST /v1/storages/search](/docs/entities/storages/search): `?sort=-id`, `?order[name]=desc` и те же значения в теле поиска. Минус перед именем поля означает убывание.

Порядок выдачи меняют `id`, `name`, `entityType`, `entityId`, `rootFolderId` — по каждому из них проверено на живом аккаунте, что по возрастанию и по убыванию приходят разные выдачи. Поля `code` и `module` метод тоже принимает без ошибки, но на проверенных аккаунтах значение этих колонок одинаково у всех хранилищ, поэтому порядок по ним не меняется — не полагайтесь на них как на сортировку.

Раньше любая сортировка хранилищ отклонялась с `400 INVALID_SORT_FIELD`. Отказ был ошибочным: он был выведен из описания метода Битрикс24, а метод порядок сортировки принимает и применяет — это проверено на живом портале, где выдача по возрастанию, по убыванию и без сортировки различаются. Если вы обходили этот отказ, сортируя список у себя, обходной путь можно убрать; он продолжает работать.

Заодно порядок выдачи стал устойчивым. К вашей сортировке добавляется `id` последним ключом, а список без сортировки теперь приходит по возрастанию `id` — раньше он приходил в неопределённом порядке аккаунта. Это не косметика: список отдаётся страницами по 50, и при сортировке по неуникальному полю (например по названию) строка с тем же значением могла на границе страниц попасть в две страницы сразу или пропасть из выдачи вовсе. Теперь порядок полный и однозначный, поэтому и постраничный обход повторяем. Если вы сортировали по `id` сами, ваше направление сохраняется — второй ключ не добавляется.

Разбиение на страницы у хранилищ идёт постранично по 50 записей, поэтому при сортировке запрашивайте нужный объём одним вызовом (`limit` до 5000), а не листайте страницы вручную.

### FIX-0729-17: перезапуск galaxy-приложения через /reboot

**Было**

Для galaxy-приложения (`kind=GALAXY_APP`) у [POST /v1/infra/servers/:id/reboot](/docs/infra/lifecycle/reboot) не было рабочего сценария: вызов возвращал `422 VM_MISSING` (у контейнера нет собственной виртуальной машины), а единственным путём восстановления зависшего приложения оставалось удаление — оно стирает постоянный том `/data`.

**Стало**

`/reboot` перезапускает контейнер приложения как «пинок» самовосстановления — постоянный том `/data` при этом сохраняется. Вызов принимается в статусе `running` или `error` и возвращает совещательный вердикт: `restarted` (контейнер перезапущен) и `healthy` (контейнер запустился и перестал перезапускаться — проверка контейнера, не HTTP-ответа приложения), а при `healthy: false` — поле `hint` о том, как залить исправленную версию. Перезапуск не снимает состояние ошибки — крашащееся приложение авторитетно чинится редеплоем исходников через [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy). Новые коды ошибок для этого сценария: `GALAXY_APP_REBOOT_USE_AGENT_CONTROLS` (409 — приложением агента или бота управляют из его панели), `GALAXY_APP_BUSY` (409 — на хосте выполняется другая команда, в ответе `Retry-After`), `GALAXY_HOST_UNREACHABLE` (502 — хост недоступен). Перезагрузка обычного сервера не изменилась.

### NEW-0729-18: totalDefault — умолчание по meta.total на самом ключе

У API-ключа появилась настройка `totalDefault`: заказывать ли подсчёт количества спискам этого ключа, когда сам запрос не передал `withTotal`. Значение `true` — присылать `meta.total`, `false` — не присылать, `null` — наследовать платформенное умолчание. По умолчанию у всех ключей `null`.

Настройка правится в кабинете на странице ключей и через [PATCH /v1/keys/:id](/docs/management-keys) полем `totalDefault` (управляющий ключ `vibe_live_`). Перевыпуск ключа через `POST /v1/keys/:id/rotate` настройку сохраняет — как и режим доступа. Изменение попадает в журнал аудита.

Действующее значение видно в [GET /v1/me](/docs/keys-auth/me) — блок `totalDefault` показывает всю цепочку: `key` (настройка ключа), `platform` (платформенное умолчание), `effective` (что получится, если запрос не передаст `withTotal`) и `source` — откуда взялось действующее значение.

Настройка нужна там, где менять код интеграции дороже, чем один раз переключить ключ: она задаёт умолчание сразу всем спискам этого ключа. Точечно её перекрывает параметр запроса `withTotal`.

### NEW-0729-19: meta.nextAfterId — курсор следующей страницы при сортировке по id

Ответы [GET /v1/{entity}](/docs/entity-api) и [POST /v1/{entity}/search](/docs/entity-api) получили необязательное поле `meta.nextAfterId` — идентификатор последней отданной записи, строкой.

Поле приходит, когда выполнены три условия сразу: у сущности числовой идентификатор и поддержка курсорного листания, сортировка запроса — строго `id` по возрастанию, и `meta.hasMore` равен `true`. На последней странице поля нет: идти уже некуда. Сегодня условиям отвечают сделки, лиды, контакты, компании, предложения и элементы смарт-процессов.

Значение передаётся обратно тем же фильтром, которым курсорное листание делалось и раньше: `filter[>id]=<nextAfterId>` при сортировке `id` по возрастанию. Нового параметра запроса не появилось — поле лишь избавляет от чтения идентификатора из последней строки ответа вручную.

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

### NEW-0729-20: withTotal — списку можно не заказывать подсчёт количества

Списочные вызовы приняли необязательный параметр `withTotal`. У [GET /v1/{entity}](/docs/entity-api) это параметр запроса ровно с двумя допустимыми значениями — `true` и `false`; у [POST /v1/{entity}/search](/docs/entity-api) — поле тела с булевым значением. Любая другая запись читается как «параметр не передан», ошибки не будет.

`withTotal=false` — просьба не считать количество. Там, где платформа может её выполнить, подсчёт у Битрикс24 не заказывается и `meta.total` в ответе отсутствует. Там, где обойтись без подсчёта нельзя, параметр не действует и `meta.total` приходит как раньше. Поэтому наличие поля проверяйте, а не предполагайте.

Листать надо по `meta.hasMore` — он считается по полноте страницы и доводит цикл «читай, пока `hasMore`» до конца независимо от того, был ли подсчёт. При сортировке строго по `id` по возрастанию ответ дополнительно несёт `meta.nextAfterId`, который передаётся обратно в `filter[>id]`.

Если параметр не передан, значение берётся из настройки ключа, а при её отсутствии — из платформенного умолчания. Действующее сейчас значение и всю эту цепочку показывает блок `totalDefault` в [GET /v1/me](/docs/keys-auth/me).

Когда точное количество действительно нужно, спрашивайте его прямо: `POST /v1/{entity}/aggregate` с функцией `count` отдаёт число одним вызовом, без выгрузки записей. Считать количество постраничным обходом коллекции не надо — это десятки вызовов вместо одного и самый дорогой способ узнать одну цифру.

### FIX-0729-21: meta.hasMore в списках считается по полноте страницы, а meta.total может отставать до минуты

**Было**

`meta.hasMore` в ответах [GET /v1/{entity}](/docs/entity-api) и [POST /v1/{entity}/search](/docs/entity-api) выводился из `meta.total`: «есть ещё» означало «`offset` плюс отданные строки меньше общего количества». Пока количество считалось на каждый вызов, это совпадало с правдой.

**Стало**

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

`meta.hasMore` на таких ответах считается по полноте страницы: пришла полная страница — «возможно, есть ещё»; пришла неполная — список закончился. Цикл «читай, пока `hasMore`» по-прежнему доходит до конца всегда. Если коллекция ровно кратна `limit`, последний шаг вернёт пустой список — это штатный признак конца.

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

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

Менять ничего не нужно, если листание идёт по `meta.hasMore` — это рекомендованный способ. Если код опирается на `meta.total` как на точную границу цикла или проверяет «отдано не больше, чем total», переключитесь на `meta.hasMore`. Точное количество на момент запроса даёт `POST /v1/{entity}/aggregate` с функцией `count`.

## 2026-07-28

### NEW-0728-1: приложение стёртого автора больше не выдаёт новые пользовательские токены

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

Возврат из [GET /v1/oauth/callback](/docs/keys-auth/oauth) в этом случае приходит на ваш `redirect_uri` с `?error=app_unavailable` — той же формой, что уже используют `token_exchange_failed`, `invalid_domain` и `profile_fetch_failed`. [POST /v1/oauth/placement-session](/docs/keys-auth/oauth) отвечает `403` с кодом `APP_UNAVAILABLE` — тем же кодом отвечает и обработчик размещения, куда Битрикс24 открывает виджет приложения (раньше там приходил `401` с общим `USER_AUTH_REQUIRED`, который читался как проблема авторизации пользователя).

Уже выданные токены и сессии этого приложения не продлеваются. Ранее работавшие вызовы не затронуты: пока автор активен, поведение обоих эндпоинтов прежнее.

### NEW-0728-2: снятие зависшего лока стало кросс-репличным; ответ DELETE /lock несёт broadcast и localLock

[DELETE /v1/infra/servers/:id/lock](/docs/infra/deploy/lock) теперь рассылает снятие лока на **все реплики** платформы, поэтому снимает зависший лок и тогда, когда он держится на другой реплице (частый случай под горизонтальным масштабированием). Ответ дополнен полями `broadcast` (снятие разослано по флоту, best-effort) и `localLock` (держался ли лок на этой реплице). Поле `released` теперь описывает только текущую реплику и **не является подтверждением снятия по всему флоту** — при зависшем локе на другой реплице `released` может быть `false`, хотя лок реально снят; не опрашивайте эндпоинт в цикле до `released: true`, повторите операцию. Прежние вызовы работают без изменений (поля добавлены аддитивно). Дополнительно зависший `exec`-лок теперь гарантированно снимается серверным авто-сбросом вскоре после истечения TTL.

Ответ [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) при `502 EXEC_BUSY` для galaxy-приложения теперь несёт `error.hint` с честным путём восстановления (общий exec-канал хоста; эскалация к платформенной команде — `DELETE /lock` тут не помогает, так как блокирует мьютекс агента). Ответ [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) при `409 GALAXY_APP_BUSY` дополнен `error.hint`, `retryable: true`, `retryAfter` и заголовком `Retry-After`.

### NEW-0728-3: указатели на открытые линии, чек-листы задач и ТЗ приложений в ответах самоописания

Ответ [GET /v1/guide](/docs/keys-auth/guide) дополнен указателем `data.appBlueprints` — ссылка на документацию готовых ТЗ приложений и условие ответа `403 BLUEPRINTS_DISABLED`.

Для ключа со скоупом `imopenlines` в ответ добавлен блок `data.openLines`: описание раздела, ссылки на все семь страниц документации и разграничение двух групп эндпоинтов. Настройка линий и действия оператора доступны на любом портале. Статистика дашборда до прихода обновления Битрикс24 отвечает `422 METHOD_NOT_YET_AVAILABLE`, а без права на просмотр статистики — `403 B24_TARIFF_RESTRICTION`.

В ответе [GET /v1/me](/docs/keys-auth/me) блок `api._rules` получил три новых указателя — на [чек-листы задач](/docs/entities/tasks/checklist), [открытые линии](/docs/openlines) и [ТЗ приложений](/docs/app-blueprints).

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

### FIX-0728-4: деплой в галактику восстанавливает оборванный туннель хоста

**Было**

Деплой галакси-приложения на хост, туннель которого молча оборвался под нагрузкой сборки (в том числе «фантомно-CONNECTED» хост — флаг завис, а туннель уже мёртв), зацикливался на `GALAXY_HOST_UNREACHABLE` / `GALAXY_DEPLOY_INTERRUPTED`: платформа не чинила туннель сама, и повторные попытки клиента били в тот же мёртвый туннель.

**Стало**

Такой обрыв на пути деплоя теперь запускает фоновый ремонт туннеля хоста, поэтому честный повтор попадает уже на восстановленный туннель и деплой доезжает. Коды ошибок и их «повторяемая» семантика не изменились — меняется только поведение (самовосстановление).

### BC-0728-5: единый конверт 404 для несуществующих маршрутов /v1

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

> Прежняя форма тела не возвращается — переходного периода с двойным форматом нет, изменение действует с даты публикации.

**Было**

Запрос на несуществующий путь или неподдерживаемый HTTP-глагол под `/v1/` отвечал телом веб-сервера вне единого конверта API:

```json
{
  "message": "Route GET:/v1/dealz not found",
  "error": "Not Found",
  "statusCode": 404
}
```

**Стало**

Тот же запрос отвечает в едином конверте V1 с новым кодом `ROUTE_NOT_FOUND`. HTTP-статус прежний — `404`:

```json
{
  "success": false,
  "error": {
    "code": "ROUTE_NOT_FOUND",
    "message": "Route GET:/v1/dealz not found. Check GET /v1/guide for available endpoints and verbs."
  }
}
```

`ROUTE_NOT_FOUND` означает «такого маршрута или глагола не существует» — сверьте путь со списком в `GET /v1/guide`. Не путайте с `ENTITY_NOT_FOUND` и доменными кодами вида `*_NOT_FOUND`: там маршрут существует, не найден запрошенный объект. Вне `/v1/` форма тела 404 не изменилась.

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

Ветвитесь по `error.code`, а не по форме тела. Клиент, который разбирал поля `message`, `error` и `statusCode` прежнего тела на путях `/v1/`, должен перейти на единый конверт `success` и `error.code`.

### BC-0728-6: календарь: неизвестное имя в select отвечает ошибкой, а не пустым объектом

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

**Было**

[GET /v1/calendar-events](/docs/entities/calendar-events/list) (а также `GET /v1/calendar-events/{id}`, `POST /v1/calendar-events/search` и calendar-events-подвызовы `POST /v1/batch`) при незнакомом имени поля в `select` (например `dateFrom` — такого поля нет, реальное имя `from`) молча возвращали объекты, состоящие из одного `id`. Клиент считал, что сузил трафик, а на деле терял данные.

**Стало**

Незнакомое имя поля в `select` отвечает `400 UNKNOWN_SELECT_FIELD` с перечнем допустимых имён (`Available: …`). Имена в формате Битрикс24 (`DATE_FROM`) и алиасы дат (`updatedAt`) по-прежнему принимаются и проецируют канонический ключ — ошибку вызывают только имена, которые не разрешаются ни в одно поле схемы. На остальных сущностях поведение прежнее: незнакомое имя даёт предупреждение в `meta.warnings`, без ошибки.

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

Брать имена полей из [GET /v1/calendar-events/fields](/docs/entities/calendar-events/fields) и убрать из `select` несуществующие имена (`dateFrom`/`dateTo` → `from`/`to`). Клиенты, не передающие `select` или передающие корректные имена, не затронуты.

### NEW-0728-7: календарь: поля occurrenceIndex и version

У событий календаря появились два поля только для чтения. `occurrenceIndex` — порядковый номер вхождения в развёрнутой серии повторяющегося события, с нуля: строки серии делят один `id`, и пара `id` + `occurrenceIndex` однозначно идентифицирует строку набора. `version` — монотонный счётчик изменений события, растёт при каждом изменении и не зависит от региональных настроек аккаунта. Рецепт диффа: запросите [GET /v1/calendar-events](/docs/entities/calendar-events/list) с `select=id,version`, сравните пары со своим снимком и дочитайте изменённые события по `id`.

### NEW-0728-8: чаты: эхо ограничения limit в meta

Три эндпоинта чатов — [GET /v1/chats/recent](/docs/chats/discovery/recent), [GET /v1/chats/:dialogId/messages](/docs/chats/messages/list) и `GET /v1/chats/:dialogId/users` — при срезании переданного `limit` до допустимого диапазона дополняют ответ полем `meta` с парой `requestedLimit` и `appliedLimit`: сколько запросили и сколько применено. Когда `limit` в диапазоне, `meta` не добавляется — конверт прежний. Для чтения сообщений задокументирован реальный потолок облачного Битрикс24 — не больше 50 записей за вызов независимо от `limit`, продолжение читается курсором `lastId`.

### NEW-0728-9: взаимные алиасы полей дат updatedAt и createdAt

Поля дат в каталоге сущностей носят два семейства имён: одни сущности объявляют `updatedAt` и `createdAt`, другие — `updatedTime` и `createdTime`. Теперь члены пары принимаются на вход взаимозаменяемо: в `filter` и `select` — на всех сущностях, где объявлен парный ключ, в `sort` — на сущностях с camelCase-схемой полей. Например, `updatedTime` на сущности с полем `updatedAt` работает как `updatedAt`, и наоборот. Канонические имена полей в ответах не меняются — алиас действует только на входе.

### NEW-0728-10: POST-алиас поиска по Базе знаний

Поиск документов Базы знаний 2.0 принимает и `POST /v1/note/documents/search` с JSON-телом `{ "query": "...", "limit": 20 }` — для агентов, которые ожидают поиск POST-запросом по аналогии с остальными сущностями. Канонической остаётся форма [GET /v1/note/documents/search](/docs/note/documents/search) с query-параметрами. Обе формы принимают только `query` и `limit`, при передаче параметра и в теле, и в query приоритет у тела.

### NEW-0728-11: лента: limit до 200 записей за запрос

[GET /v1/posts](/docs/feed/posts/list) принимает `limit` от 1 до 200. Страница ленты Битрикс24 фиксирована в 50 записей — при `limit` больше 50 платформа склеивает до четырёх страниц в один ответ. Значение больше 200 отвечает прежним `400 INVALID_LIMIT`. В `meta` добавлено поле `returned` — фактическое число записей в ответе, а `meta.nextOffset` при многостраничном чтении выводится из окна ответа, чтобы цепочка страниц продолжалась как раньше.

### FIX-0728-12: календарь: честные offset и hasMore, детерминированный порядок

**Было**

[GET /v1/calendar-events](/docs/entities/calendar-events/list) при любом `offset` возвращал начало одного и того же набора: запрошенный диапазон приходит от Битрикс24 одним массивом без пагинации, поэтому каждая «страница» повторяла первую, `meta.hasMore` оставался `true`, а хвост набора за пределами первой страницы был недоставаем.

**Стало**

Полный набор сортируется детерминированно — по началу события `from`, при равенстве по `id`, затем по `occurrenceIndex` — и из него отдаётся честное окно от `offset` до `offset + limit`. `meta.total` — число вхождений в наборе: повторяющиеся события развёрнуты по-вхожденно, строки серии делят один `id`. `meta.hasMore` отвечает `true` только пока за окном остаются записи. Порядок элементов в ответе стал детерминированным и может отличаться от прежнего.

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

Обход набора через `offset` теперь отдаёт весь диапазон. Клиентам, которые обходили дубли страниц самостоятельно, ничего менять не нужно — дублей больше нет.

### FIX-0728-13: select принимает объявленные имена Битрикс24 и предупреждает о незнакомых полях

**Было**

Параметр `select` понимал только канонические имена полей из `GET /v1/{entity}/fields`. Имя в другом написании — исходное имя Битрикс24 (`DATE_FROM`, `UF_DEPARTMENT`) или другой регистр — молча выпадало из проекции: поля не было в ответе без какого-либо признака ошибки, а запрос из одних таких имён вырождался в объекты с единственным полем `id`. Пакетные вызовы `select` не применяли вовсе: и глобальный [POST /v1/batch](/docs/batch), и пакетный вызов одной сущности `POST /v1/{entity}/batch` возвращали полные объекты.

**Стало**

`select` принимает канонические имена без учёта регистра, объявленные исходные имена Битрикс24 (`DATE_FROM` проецирует `from`, `UF_DEPARTMENT` — `departmentId`) и взаимные алиасы полей дат — поле приходит в ответе под каноническим ключом. Незнакомое имя больше не теряется молча: список, поиск и получение записи по `id` дополняют ответ массивом `meta.warnings` с записями `{ "code": "UNKNOWN_SELECT_FIELD", "field": "<имя>" }` — до 10 предупреждений на ответ. Оба пакетных вызова применяют `select` к операциям списка и поиска так же, как одиночные эндпоинты; получение записи по `id` внутри глобального пакета `select` не применяет. Глобальный вызов дополнительно отдаёт предупреждения о незнакомых именах в `meta` по каждому своему вызову. Пакетный вызов одной сущности предупреждений не несёт и жёсткого отклонения незнакомых имён не выполняет — незнакомое имя там по-прежнему просто отсутствует в ответе.

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

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

### FIX-0728-14: быстрый отказ оконного поиска при таймауте портала

**Было**

Поиск `POST /v1/{entity}/search` с широким диапазоном дат разбивается на временные окна. Таймаут Битрикс24 на первом окне пропускался, и остальные окна выполнялись каждое со своим таймаутом: до ответа проходило 60–75 секунд, после чего приходил `503 BITRIX_TIMEOUT`. Если поздние окна успевали, возможен был частичный `200` с неполными данными после той же минуты ожидания.

**Стало**

Таймаут первого окна (`BITRIX_TIMEOUT`) завершает запрос сразу: ответ `503` с кодом `BITRIX_TIMEOUT` и заголовком `Retry-After` приходит примерно за 15 секунд, остальные окна не выполняются. Частичный `200` при таймауте первого окна больше невозможен — это осознанный размен: окна одинаковы по форме, таймаут первого предсказывает таймауты остальных, а частичный ответ после минуты ожидания провоцировал волны повторов. Таймаут любого последующего окна обрабатывается как раньше — окно пропускается, ответ может быть частичным.

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

Повторяйте запрос по заголовку `Retry-After`. Клиенты, которые полагались на частичный ответ при перегрузке портала, теперь получают быстрый `503` — сузьте диапазон дат или повторите позже.

### FIX-0728-15: Galaxy-деплой из .zip: честная причина ошибки извлечения вместо EMPTY_BUILD_CONTEXT

Деплой Galaxy-приложения из .zip теперь возвращает реальную (санитизированную) причину ошибки извлечения архива в `buildLog` 502-ответа, а не вводящее в заблуждение «EMPTY_BUILD_CONTEXT»/общее сообщение.

### FIX-0728-16: отбой лимитера времени операций Битрикс24 возвращается как `OPERATION_TIME_LIMIT` с `Retry-After`

**Было**

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

**Стало**

Такой отбой возвращается кодом `OPERATION_TIME_LIMIT` — тем же, что отдаёт портал, — с
`Retry-After`, посчитанным от известного срока снятия ограничения, и понятным текстом в
`userMessage`. Повторы отключены: ограничение адресное — на тройку «портал + ключ +
метод», ровно так же, как его накладывает сам Битрикс24, — и до истечения срока платформа
отбивает вызовы этого метода сама, не обращаясь к порталу. Остальные методы портала, как и
тот же метод под другим ключом, не затронуты. Прочие отказы `429` (в том числе
`RATE_LIMITED` и `QUEUE_OVERFLOW`) не изменились.

Соблюдайте `Retry-After`: раньше срока тот же вызов пройти не может. Тяжёлые чтения стоит
разредить по времени или сузить — меньше полей, меньше страница, `POST /v1/batch`.

### FIX-0728-17: Значение * в select возвращает все поля

**Было**

Привычная для Битрикс24 запись `select: ["*"]` (и `["*", "UF_*"]`) на одиночных эндпоинтах приводила к обратному результату: `*` не совпадает ни с одним объявленным полем, поэтому в ответе оставался только `id`. Признака ошибки не было — запись просто приходила пустой.

**Стало**

`*` и `UF_*` (в любом регистре) распознаются как запрос «вернуть все поля»: отбор полей не применяется, приходит полная запись. Незнакомое имя, переданное рядом со звёздочкой, не отклоняется — ответ дополняется предупреждением `UNKNOWN_SELECT_FIELD`. Это работает одинаково в списке, поиске, получении записи по `id` и в обоих пакетных вызовах — [POST /v1/batch](/docs/batch) и `POST /v1/{entity}/batch`. У событий календаря, где незнакомое имя поля отвечает ошибкой `400`, значение `*` ошибкой не считается.

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

Ничего менять не нужно. Клиент, переносивший запросы из портального кода Битрикс24 вместе с `select: ["*"]`, начнёт получать полные записи вместо объектов с одним `id`.

### FIX-0728-18: оконный поиск останавливается, набрав запрошенное, и сообщает о неполном окне

**Было**

`POST /v1/{entity}/search` с широким диапазоном дат разбивает диапазон на временные окна и объединяет их результаты. Обход шёл до конца диапазона даже тогда, когда запрошенных `limit` записей уже набрано: поиск с `limit: 50` по диапазону в несколько месяцев вычитывал весь диапазон целиком — отвечал долго и создавал на аккаунте Битрикс24 нагрузку, несопоставимую с размером ответа. Если внутри одного окна подходящих записей оказывалось больше, чем отдаёт одно чтение окна, окно возвращало только начало своего набора, и делало это молча: ни признака в ответе, ни способа дочитать остаток (оконный поиск отвергает `offset > 0` кодом `UNSTABLE_OFFSET_PAGINATION`).

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

**Стало**

Обход окон прерывается, как только набрано больше уникальных записей, чем запрошено в `limit`. В ответе по-прежнему не больше `limit` записей, а признак «есть ещё» несёт `hasMore`. При досрочной остановке `meta.total` равен количеству набранного, то есть это нижняя оценка числа подходящих записей, а не полный счёт по диапазону — ровно так уже вёл себя этот поиск на аккаунтах с пакетным чтением окон, теперь поведение единое.

Неполная выдача больше не молчит — ни по одной из двух причин, независимо от того, читаются окна по одному или пакетами, **на сущностях, чей метод списка Битрикс24 отдаёт постранично**. Ответ дополняется предупреждением `{ "code": "WINDOW_TRUNCATED", "field": "…", "message": "…" }` в массиве `meta.warnings`, где `field` — поле диапазона, по которому шло разбиение. Код предупреждения один и тот же в обоих случаях — ветвиться по нему можно, не разбирая текст; `message` называет сработавшую причину: одно окно держало больше записей, чем отдаёт одно чтение окна, либо набраны предельные 5000 записей и оставшиеся окна не отправлялись.

Исключения названы прямо. Три сущности предупреждения не получат никогда, потому что их метод списка Битрикс24 не отдаёт постранично: файлы и папки (`/v1/files`, `/v1/folders`) и рабочие группы (`/v1/workgroups`). Там запрос идёт одиночным вызовом, `limit` в Битрикс24 не передаётся вовсе, и аккаунт отдаёт свою страницу примерно на 50 записей: окно, в котором лежит 200 дисковых объектов, вернёт 50 и промолчит, как и до правки. У страниц (`/v1/pages`), сайтов (`/v1/sites`) и событий календаря (`/v1/calendar-events`) метод списка тоже не постраничный, но он отдаёт весь запрошенный набор за один вызов — там предупреждение просто недостижимо, а неполноты не возникает.

Дополнительно снижена нагрузка, которую этот поиск создаёт на аккаунте Битрикс24: вырожденная нижняя граница по `id` больше не уходит в Битрикс24. Снимаются две формы, и опираются они на разное. `>=` со значением 0 или меньше и `>` с отрицательным значением исключают только отрицательные `id` — они тавтологичны при одном допущении неотрицательности. `>` ровно с нулём (`filter[>id]=0` — именно её передают в начале обхода курсором) исключает запись с `id = 0`, то есть дополнительно опирается на автоинкрементную нумерацию записей Битрикс24 от единицы; это целевой случай правки, и он снимается сознательно. Граница `>=id=1` сохраняется — гард намеренно узкий и смотрит только на значения 0 и меньше.

Состав выдачи не меняется ни на одной сущности, где Битрикс24 действительно применяет фильтр по `id`. Известное исключение — воронки (`/v1/categories`): среди них есть запись с `id: 0` («Общая»), но метод списка воронок игнорирует `filter` целиком, поэтому и до, и после правки в ответ приходит полный набор воронок. Форма ответа прежняя.

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

Не считайте `meta.total` точным числом подходящих записей на широком диапазоне дат — при досрочной остановке это нижняя оценка; ориентируйтесь на `hasMore`. Дочитать оконный поиск постранично нельзя (`offset > 0` отвергается), поэтому «есть ещё» закрывается либо большим `limit` (до 5000), либо более узким диапазоном дат.

Проверяйте `meta.warnings` на `WINDOW_TRUNCATED`: это признак неполной выдачи, и пагинацией он тоже не лечится — сузьте диапазон дат или добавьте фильтров, чтобы поиск перестал упираться в потолок. Одного `hasMore` для этого недостаточно: оконное разбиение работает только при `offset === 0`, поэтому запрос следующей страницы уйдёт уже не оконным и даст другой результат. Поиск по узкому диапазону, который не разбивается на окна, не затронут.

Учтите границу оконного поиска: сортировка применяется внутри окна, а не поверх объединения окон. Окна строятся от старых к новым и склеиваются в этом же порядке, после чего результат режется до `limit`; глобальной сортировки по объединению нет. Поэтому запрос с сортировкой по убыванию и небольшим `limit` по широкому диапазону дат возвращает самые СТАРЫЕ подходящие записи, а не самые новые. Само поведение не новое, но досрочная остановка закрывает сортировку после слияния как способ починки (записи поздних окон больше не вычитываются), поэтому называем это прямо. Нужен настоящий «последние N по дате» — либо сузьте диапазон так, чтобы разбиение на окна не включалось, либо возьмите диапазон одним запросом с большим `limit` и отсортируйте на своей стороне.

## 2026-07-27

### NEW-0727-1: фавикон приложения одной строкой — /_gw/icon

Фавикон во вкладке браузера теперь ставится одной статической строкой без id сервера:

```html
<link rel="icon" href="/_gw/icon">
```

`/_gw/icon` — платформенный путь на домене приложения; он всегда отдаёт текущую загруженную иконку. Одна загрузка [POST /v1/infra/servers/:id/icon](/docs/infra/app-icon) управляет и карточкой в каталоге Bitrix24, и фавиконом: перезалили иконку — фавикон обновится сам (~5 минут), пересобирать приложение не нужно. Свой статический файл иконки в приложение класть больше не нужно. Строка совместима с созданием приложения одним запросом (id не требуется).

Дополнительно ответ [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) и создания приложения одним запросом [POST /v1/infra/servers](/docs/infra/servers) теперь возвращает запись в `warnings[]`, если иконка ещё не загружена — с точным эндпоинтом для загрузки (иконка грузится отдельным запросом, id известен только после создания). Тот же `warnings[]` по-прежнему подсказывает, если не заданы `displayName`/`description`. Успешный деплой без иконки или названия больше не выглядит завершённым молча.

### NEW-0727-2: source-at-create: ошибка SOURCE_AT_CREATE_GALAXY_ONLY теперь несёт подсказку с путём к галактике

Отказ [POST /v1/infra/servers](/docs/infra/servers/create) с `source`, который не удалось разместить в галактике (на портале в режиме «обе стратегии» без открытого galaxy-хоста, либо на портале только со standalone), теперь дополнительно несёт `error.hint` — с понятным путём: как получить galaxy-хост и/или как задеплоить в два шага на выделенный сервер. Код и текст ошибки не изменились.

### FIX-0727-3: galaxy-приложения: PATCH /sleep и PATCH /port теперь отвечают 400 — управляйте ими со страницы «Галактики»

**Было**

Для приложения, размещённого в галактике (`GALAXY_APP`), [PATCH /v1/infra/servers/:id/sleep](/docs/infra/lifecycle/sleep) возвращал `200` и записывал `sleepAfterMinutes`, а [PATCH /v1/infra/servers/:id/port](/docs/infra/deploy/port) отвечал 404/409 — расходясь с задокументированным контрактом `/v1/me` (`deployment.galaxyApp`), где ни одно V1-действие жизненного цикла к galaxy-приложению не применяется.

**Стало**

Оба вызова для galaxy-приложения отвечают `400` с `error.code = "GALAXY_APP_USE_GALAXY_ROUTE"` и ничего не меняют. Настраивайте авто-сон приложения через маршрут галактики; порт у galaxy-приложения закреплён за хостом и не задаётся. Для обычных (standalone) серверов поведение `/sleep` и `/port` не изменилось.

### FIX-0727-4: galaxy-приложение восстанавливается после обрыва туннеля хоста

**Было**

Если у galaxy-хоста обрывался защищённый туннель (сервер в статусе `RUNNING`, но связь потеряна), развёртывание и выполнение команд galaxy-приложения возвращали `502 GALAXY_HOST_UNREACHABLE`, а запрос логов — пустой ответ с подсказкой. Хост оставался недостижим до ручного ремонта: повтор того же запроса упирался в ту же ошибку сколь угодно долго.

**Стало**

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

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

Код ошибки и форма ответа не изменились — `502 GALAXY_HOST_UNREACHABLE` (для развёртывания и выполнения команд) по-прежнему помечен как повторяемый, а логи по-прежнему отдают пустой список с подсказкой. Изменилось только то, что теперь повтор приводит к успеху, а не к вечной ошибке. Продолжайте повторять запрос по своей обычной политике на этот код и на пустой ответ логов.

**Затронутые эндпоинты:** [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy), [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec), [GET /v1/infra/servers/:id/logs](/docs/infra/deploy/logs)

### NEW-0727-5: предупреждение, когда changelog деплоя некуда опубликовать

Ответ [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) теперь возвращает запись в `warnings[]`, если в запросе передан `changelog`, а деплой не создал новую версию исходников. Заметка о релизе привязана к версии, поэтому без неё текст никуда не сохраняется и в ленту канала приложения в мессенджере Bitrix24 не уходит.

Причина видна в поле `source` того же ответа: хранилище исходников выключено (`feature-disabled-platform` или `feature-disabled-portal`), сохранение не удалось (`save-failed`) либо загруженные байты совпали с предыдущей версией. Раньше такой деплой отвечал обычным успехом, и узнать, что заметка потерялась, было нечем. Предупреждение приходит и в JSON-режиме, и в событии `done` при `?stream=true`.

### FIX-0727-6: Список bizproc-templates без явного select возвращает все поля, включая id

**Было**

`GET /v1/bizproc-templates` и `POST /v1/bizproc-templates/search` без явного `select` возвращали только поле `documentType`. Без `id` клиент не мог выполнить последующие `update`/`delete` — список был бесполезен без второго запроса с явным `select`.

**Стало**

Оба вызова без `select` возвращают полный набор объявленных полей записи (`id`, `moduleId`, `entity`, `documentType`, `autoExecute`, `name`, `description`, `modified`, `isModified`, `userId`). Явный `select` работает как прежде. Форма запроса не изменилась.

### NEW-0727-7: загрузка файла в документ Базы знаний возвращает assetMarkdown сразу

Загрузка файла через [POST /v1/note/documents/{documentId}/files](/docs/note/files/upload) раньше отдавала только `{ id }`, поэтому за готовым блоком для вставки в документ приходилось идти вторым запросом в `GET /v1/note/documents/{documentId}/files/{id}` либо собирать разметку `[[image fileId=N]]` руками.

Теперь ответ несёт весь объект файла — `id`, `documentId`, `name`, `size`, `mimeType`, `assetType`, `assetMarkdown` — то есть ту же форму, что отдаёт GET. Загрузили картинку, взяли `assetMarkdown` из ответа, вставили в текст документа и вызвали PATCH — второй запрос больше не нужен.

Изменение аддитивное: поле `id` осталось на месте и с тем же значением, поэтому клиент, который читает только его, продолжает работать без правок. Если конкретный портал вернёт объект без `assetMarkdown`, лишних полей платформа не придумывает — в ответе будет то, что пришло от Битрикс24.

### FIX-0727-8: границы `ttlSeconds` у токенов доступа в машинной схеме совпали с поведением

**Было**

Схема OpenAPI для [POST /v1/infra/servers/{id}/access-tokens](/docs/infra/access-tokens/create) обещала `ttlSeconds` в диапазоне от 60 секунд до 30 суток. Платформа же с самого начала принимала от 300 секунд до 315 360 000 (десять лет) и отклоняла всё за этими пределами кодом `400 INVALID_TTL`. Поэтому клиент или генератор клиентских библиотек, взявший минимум прямо из схемы, получал жёсткий отказ на значении, которое схема сама и предлагала, а вариант «Бессрочно» из интерфейса выглядел недоступным через API. Текстовая документация всё это время была верна — расходилась только машинная схема.

**Стало**

Схема берёт границы и значение по умолчанию из тех же констант, которыми проверяется запрос, поэтому разойтись им больше нечем: минимум 300, максимум 315 360 000, по умолчанию 86 400.

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

Поведение эндпоинта не менялось — менялось только то, что о нём написано в машинной схеме. Если вы генерировали клиента по OpenAPI и он валидировал `ttlSeconds` на своей стороне, перегенерируйте его: прежний клиент отклонял бы корректные значения больше 30 суток и разрешал бы заведомо отказные меньше 300 секунд.

### NEW-0727-9: Приложение в плейсменте может авто-ресайзить свой iframe

Приложение, встроенное в плейсмент, теперь может сообщать платформе высоту своего контента, и платформа растит iframe под неё — раньше высоту фиксировал Битрикс24 и высокий контент обрезался. Приложение постит сообщение родительскому окну: `window.parent.postMessage({ type: 'vibe:resize', height: <пиксели> }, '*')`. Принимается тип `vibe:resize` или `vibe:setHeight` с числовым полем `height`; `targetOrigin` должен быть `'*'` — браузер сверяет его с непосредственным родителем окна. Пересчитывайте высоту при изменении контента, например через `ResizeObserver`. Полное описание и рекомендации — раздел «Авто-высота iframe» руководства по рантайму приложения.

Функция активируется на стороне платформы по аккаунтам Битрикс24; если ресайз пока не срабатывает, для вашего аккаунта она ещё не активна.

### FIX-0727-10: offset в списках считается по записям, а не по страницам

**Было**

Битрикс24 отдаёт списки страницами по 50 и трактует смещение как номер страницы, а не как число записей. Мы передавали `offset` как есть, поэтому он молча округлялся вниз до кратного 50: `offset=0`, `offset=7` и `offset=49` возвращали одну и ту же первую страницу — без ошибки и без предупреждения. Обход выборки шагом меньше 50 записей зацикливался на первой странице, а шаг ровно в 50 работал и создавал впечатление, что параметр исправен.

**Стало**

`offset` считается по записям на generic-списках: `GET /v1/{entity}`, `POST /v1/{entity}/search`, `POST /v1/batch` (действие `list`) и `GET /v1/{entity}/{id}/activities`. `offset=7` начинает выборку с 8-й записи. Смещение и `limit` независимы: `?limit=2&offset=51` вернёт ровно две записи, начиная с 52-й. Битрикс24 по-прежнему отдаёт страницами по 50 — Вайбкод запрашивает страницу, покрывающую нужную позицию, и отбрасывает лишнее начало; цена — не более одной дополнительной страницы у Битрикс24 на запрос.

Заодно исправлены два следствия. `meta.hasMore` учитывает смещение: раньше у сущностей, чей список Битрикс24 отдаёт под именованным ключом — сделки, контакты, компании, лиды, задачи, заказы, товары, счета и другие, всего два десятка, — он сравнивал только длину страницы с общим количеством и оставался `true` на последней странице при ненулевом `offset`. `GET /v1/{entity}/{id}/activities` теперь уважает `limit`: метод Битрикс24 не принимает ограничение выборки, поэтому раньше приходила вся страница целиком независимо от запрошенного значения.

Если выборка на запрошенной позиции оказалась пустой из-за фильтрации на стороне Битрикс24, ответ несёт `meta.warnings` с кодом `OFFSET_BEYOND_FETCHED_PAGE` — раньше это выглядело как пустой список без объяснения.

Для глубокой навигации по большим выборкам курсор по ключу (`filter[>id]` с `order[id]=asc`) по-прежнему надёжнее смещения: он не зависит от глубины и устойчив к параллельным изменениям.

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

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

Два момента стоит проверить. Первый — `GET /v1/{entity}/{id}/activities` с явным `limit`: раньше приходила вся страница (до 50 записей) независимо от значения, теперь придёт ровно запрошенное количество. Если код полагался на то, что за один вызов вернётся больше запрошенного, увеличьте `limit` или пройдите выборку постранично. Второй — обход выборки шагом меньше 50: раньше он зацикливался на первой странице, теперь идёт вперёд, поэтому цикл, который «выручал» лишний выход по счётчику, начнёт возвращать новые записи.

Остаточные исключения, где смещение НЕ построчное. Во-первых, четыре сущности generic-слоя, у которых метод Битрикс24 округляет смещение до границы страницы, а расширить выборку сверх запрошенного размера нельзя, не рискуя потерять записи: `calendar-events`, `calendar-sections`, `telephony-lines`, `workgroups`. Смещение там осталось прежним. Во-вторых, отдельные эндпоинты с собственными обработчиками, которые в это изменение не входили: `/v1/warehouses`, `/v1/bookings`, `/v1/posts`, `/v1/requisite-links`, `/v1/lists`, `/v1/timeline-logs` и история стадий в `/v1/crm-extras`. Их поведение не изменилось.

У четырёх сущностей generic-слоя выше смещение осталось прежним, но `meta.hasMore` у них стало точнее: раньше на последней странице при ненулевом смещении он мог сказать «больше нет», хотя записи оставались.

### FIX-0727-11: статус закрытого тикета обратной связи больше не показывается как «в работе»

**Было**

Тикет, попавший под детекцию probe-кампании, показывался автору со статусом `NEW` независимо от того, что с ним реально происходило. Фильтр списка работает по настоящему статусу, поэтому закрытый тикет одновременно попадал во вкладку «Решено» и рисовался бейджем «в работе» — один и тот же тикет противоречил сам себе. Затронуты [GET /v1/feedback](/docs/feedback) и `GET /v1/feedback/{id}`.

**Стало**

Маскируется только то состояние, для которого маска и заводилась: авто-архив. Закрытый тикет отдаёт `RESOLVED`, тикет в работе — свой реальный статус, а авто-архив по-прежнему приходит как `NEW`. Ключи с доступом к обратной связи (management, скоуп `vibe:feedback`) как и раньше видят настоящий статус.

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

Клиент, который читал `status` и ожидал `NEW` у такого тикета, теперь получит его фактический статус — это и есть исправление. Дополнительно: у тикета, отмеченного детекцией и уже закрытого, отзыв (`PATCH {"status":"WITHDRAWN"}`) и ответ автора теперь отклоняются с `409 FEEDBACK_CLOSED`, как у любого закрытого тикета; раньше они проходили, потому что гейт сверялся с замаскированным статусом.

### FIX-0727-12: Connect-ключи: сохранённые права авторитетны — vibe:ai / vibe:search больше не добавляются автоматически

**Было**

Ключ, выданный через VibeCode Connect, при каждом запросе автоматически получал платформенные права `vibe:ai` и `vibe:search`, даже если они не запрашивались и не были согласованы. Такой ключ мог обращаться к AI-эндпоинтам (`/v1/chat/completions`, `/v1/ai/*`) и поиску (`/v1/search`), и расход шёл со счёта аккаунта, к которому привязан портал.

**Стало**

Сохранённые на ключе права теперь авторитетны — платформа не расширяет их автоматически. Ключ, выданный через Connect, обращается к AI- и поиск-эндпоинтам только если соответствующее право реально присутствует в ключе; иначе ответ `403`. Ротация ключа сохраняет этот признак. Обычные ключи, созданные в кабинете, поведения не меняют.

### FIX-0727-13: Приложения: производный ключ и синхронизация прав не выдают больше, чем есть у вызывающего ключа

**Было**

Вызов `POST /v1/apps` авторитетным ключом (выданным через VibeCode Connect или производным от него) минтил парный ключ приложения с полным набором платформенных прав по умолчанию (`vibe:infra`, `vibe:ai`, `vibe:search`, `vibe:storage`) — даже если у самого вызывающего ключа этих прав не было. Так же `PATCH /v1/apps/:id` мог записать в объявление приложения `vibe:*`-право, которого у ключа нет. В обоих случаях производный ключ получал возможность обращаться к AI и поиску за счёт аккаунта, к которому привязан портал.

**Стало**

Производный ключ получает ровно те платформенные права, что реально есть у вызывающего ключа. Если у авторитетного ключа права нет, запрос с ним в теле возвращает `403` `SCOPE_GRANT_REQUIRES_CONSENT`, а список несогласованных прав приходит в `error.details.unconsented`. Права, которые у ключа есть (например согласованный `vibe:storage`), проходят как раньше. Срок жизни производного ключа наследуется от вызывающего. Синхронизация прав приложения на парные ключи больше никогда не добавляет `vibe:*` авторитетному ключу — сужение прав при этом по-прежнему применяется. Обычные ключи, созданные в кабинете, поведения не меняют: платформенные права им по-прежнему выдаются по умолчанию.

### FIX-0727-14: таймаут вызова Битрикс24 стал управляемым, внутренний повтор после таймаута отменён

**Было**

Платформа всегда обрывала HTTP-вызов к Битрикс24 на 15-й секунде, а для методов чтения после обрыва делала одну внутреннюю повторную попытку — итого до ~30 секунд до ответа `503 BITRIX_TIMEOUT`. Повтор при этом запускал второе параллельное выполнение того же вызова на портале: обрыв соединения не останавливает работу Битрикс24 над запросом.

**Стало**

- Лимит времени одного вызова к Битрикс24 настраивается платформой (по умолчанию прежние 15 секунд). На порталах, где Битрикс24 отвечает медленно, платформа может поднять лимит — запросы, которые раньше стабильно завершались `503 BITRIX_TIMEOUT`, доживают до реального ответа и возвращают данные.
- Внутренняя повторная попытка после таймаута отменена для всех методов. `503 BITRIX_TIMEOUT` на чтениях приходит примерно вдвое быстрее (~15 секунд вместо ~30), и запрос больше не выполняется на портале дважды. Повторы по `429` (rate limit) не тронуты.
- Текст ошибки `Bitrix24 did not respond within 15s` подставляет фактический лимит (например, `within 60s`) — не опирайтесь на константу в тексте.

## 2026-07-25

### NEW-0725-1: deploy: необязательное поле changelog

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) принимает новое необязательное поле `changelog` — обычный текст до 2000 символов с описанием, что изменилось в этой версии. При выходе новой версии приложения текст публикуется подписчикам в ленту канала приложения в мессенджере Bitrix24; если поле не передано, в ленту уходит только номер версии. Поле доступно и в JSON-, и в multipart-режиме деплоя. Прежние вызовы деплоя работают без изменений.

### NEW-0725-2: вызовы моделей по короткоживущему токену партнёрской системы

**Было**

Ручки AI-роутера `/v1/chat/completions`, `/v1/embeddings`, `/v1/audio/transcriptions` и `/v1/models` принимали только обычный ключ платформы.

**Стало**

Те же ручки (и их `/v1/ai/...`-алиасы) дополнительно принимают короткоживущий токен нового типа. Его получает партнёрская система Битрикс24 по своему подписанному каналу; токен привязан к порталу и конкретному сотруднику, живёт один час и допущен ровно к этим восьми маршрутам — на любом другом пути ответ `SCOPE_FORBIDDEN`. Расход по такому токену считает сама платформа и списывает синхронно в AI-квоту портала, поэтому при исчерпании квоты вызов отбивается ещё до обращения к модели. Доступны только модели, включённые в программу квоты — перечень отдаёт `/v1/models` под этим же токеном.

Коды отказа, которые теперь может вернуть этот маршрут: `TOKEN_INVALID`, `SCOPE_FORBIDDEN`, `MODEL_NOT_IN_QUOTA_PROGRAM`, `CREDENTIAL_NOT_PLATFORM`, `ACCOUNT_FROZEN`, `PORTAL_DELETED`, `PORTAL_BLOCKED`, `PORTAL_SUSPENDED`. Конверт прежний: `success` и `error` с полями `code` и `message`.

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

## 2026-07-24

### FIX-0724-1: дела: нераспознанное имя поля фильтра теперь отклоняется с 400 UNKNOWN_FILTER_FIELD

**Было**

`GET /v1/activities` и `POST /v1/activities/search` молча отбрасывали неизвестный ключ фильтра: запрос возвращал `200 success` с фильтром, урезанным до пустого — то есть отдавал весь (owner-scoped или вообще весь) набор дел. Например `{"filter":{"ownerTypeId":2,"ownerId":3,"bogusField":123}}` игнорировал `bogusField` и возвращал все дела родительской сделки. Это расходилось с документацией и с поведением других сущностей CRM (компании, счета), где такой фильтр уже отклонялся.

**Стало**

Имя поля фильтра, которого нет в схеме дела — и которое не является пользовательским полем `UF_*`, ключом `id` или спец-токеном — теперь отклоняется до вызова Bitrix24 с `400 UNKNOWN_FILTER_FIELD` и списком доступных полей, как уже делают companies/quotes/contacts. Полный список фильтруемых полей возвращает `GET /v1/activities/fields`.

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

Фильтрация по реальным полям дела (в camelCase или в родном ВЕРХНЕМ регистре Bitrix24), по полям `UF_*`, операторы (`>=`, `<`, `!` и т.п.), диапазоны и AND/NOT работают как прежде. Если вы полагались на молчаливый сброс нераспознанного ключа — уберите его из фильтра.

### NEW-0724-2: /fields бизнес-процессных действий и роботов отдаёт названия и описания полей

**Было**

`GET /v1/bizproc-activities/fields` и `GET /v1/bizproc-robots/fields` описывали каждое поле только типом и флагом readonly, без человекочитаемых подписей.

**Стало**

По каждому из 12 полей теперь приходят `label` и `description` по-русски, что упрощает построение форм и подсказок. Типы полей не изменились.

### FIX-0724-3: Скачивание своих файлов из хранилища больше не возвращает 403

**Было**

`GET /v1/storage/objects/:key` мог вернуть `403` при обращении к объекту, которым владелец ключа законно владеет, но который физически лежит под префиксом другого «семейства» хранилища (например, файлы сервера, видимые в списке разработчика). Объект показывался в списке, но скачать его, получить presigned-ссылку или сделать `HEAD` не удавалось.

**Стало**

Область доступа временных ключей учитывает фактическое семейство объекта (портал при этом остаётся привязан к контексту вызывающего), поэтому скачивание (`?download`), потоковая отдача (`?inline`) и `HEAD` для собственных объектов работают независимо от семейства. Проверка владения не изменилась — по чужому объекту по-прежнему приходит `404`.

### FIX-0724-4: GET /v1/workflows учитывает параметр limit

**Было**

`GET /v1/workflows` принимал `limit`, но молча его игнорировал — всегда возвращалась целая страница запущенных бизнес-процессов (до 50), сколько бы ни запросили.

**Стало**

`limit` уважается: в ответе не больше запрошенного числа записей. Значения больше 50 набираются постранично (потолок — 500); `meta.total` по-прежнему показывает общее число запущенных процессов.

### FIX-0724-5: деплой: приложение в оборачивающей папке архива больше не падает с ENOENT package.json

**Было**

Деплой (`POST /v1/infra/servers/:id/deploy`) на standalone-сервер архива, в котором проект завёрнут в единственную папку верхнего уровня (например, `myapp/package.json` вместо `package.json` в корне), падал на шаге установки:

```
npm error enoent Could not read package.json ... open '/opt/app/package.json'
```

Загруженный архив оставался соседом распакованного содержимого, поэтому авто-выравнивание единственной оборачивающей папки не срабатывало (в корне оказывалось две записи — архив и папка), и `package.json` оставался вложенным.

**Стало**

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

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

Действий не требуется. Плоские архивы (файлы в корне архива) работают как прежде; для гарантии можно паковать плоско: `tar -czf build.tar.gz -C <папка_проекта> .`.

### FIX-0724-6: эндпоинты /v1/users* перестали возвращать 403 и 500 на ключах с доступом user

**Было**

На ключе только для чтения (READONLY) с доступом `user` вызов `GET /v1/users/me` возвращал `403 WRITE_BLOCKED_READONLY_KEY`, хотя это ридовый эндпоинт. Отдельно `GET /v1/users`, `GET /v1/users/:id`, `POST /v1/users/search` и `GET /v1/users/fields` возвращали `500 INTERNAL_ERROR` на порталах, где у одного из пользовательских полей (`UF_*`) пустое определение.

**Стало**

`GET /v1/users/me` работает на ключе только для чтения. Остальные `/v1/users*` возвращают данные и пропускают поле с пустым определением вместо падения.

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

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

### NEW-0724-7: доставка callback bizproc-активити и роботов на Black Hole-приложение

Регистрация bizproc-активити или робота с `handler`, указывающим на ваш деплой-сервер Black Hole, теперь приводит к надёжной доставке callback выполнения (с токеном события, блоком авторизации, кодом и свойствами) в приложение. Раньше такой callback мог не дойти: онлайн-события Битрикс24 не повторяются, а спящий или просыпающийся сервер терял вызов. Платформа перехватывает `handler` при регистрации, ставит вызов в устойчивую очередь и повторяет доставку с будильником сервера и откатами.

Затрагивает [POST /v1/bizproc-activities](/docs/entities/bizproc-activities) и [POST /v1/bizproc-robots](/docs/entities/bizproc-robots) (а также их изменение). Новый код ошибки `SERVER_APP_MISMATCH` (400): сервер Black Hole за указанным `handler` должен принадлежать тому же приложению, что регистрирует активити. Регистрация такого `handler` через `/v1/batch` не поддерживается — используйте одиночный запрос (`BIZPROC_CALLBACK_BATCH_UNSUPPORTED`). Кроме того, `POST /v1/bizproc-robots` теперь заранее требует `code`, `name` и `handler` — при их отсутствии возвращается `400 MISSING_REQUIRED_FIELDS` вместо сырой ошибки Битрикс24 (как уже было у активити).

Возможность раскатывается постепенно и включается по аккаунтам: до включения на вашем аккаунте регистрация проходит как прежде, без управляемой доставки. **После включения меняется поведение batch-регистрации:** попытка зарегистрировать BH-`handler` через `/v1/batch` начинает отклоняться (`BIZPROC_CALLBACK_BATCH_UNSUPPORTED`) — переведите такие регистрации на одиночный `POST /v1/bizproc-activities` или `/v1/bizproc-robots`.

## 2026-07-23

### NEW-0723-1: библиотека чертежей приложений — ТЗ по API-ключу

Готовые технические задания популярных приложений теперь доступны по ключу: `GET /v1/app/blueprints/:slug?locale=ru|en` возвращает сырой markdown ТЗ (`Content-Type: text/markdown`). Эндпоинт требует `Authorization: Bearer <ключ>`; неизвестный или скрытый чертёж — `404 BLUEPRINT_NOT_FOUND`. Ссылку на ТЗ несёт копируемый «Промт для AI» при создании ключа — AI-агент скачивает ТЗ тем же ключом. Прежний анонимный путь `/api/public/blueprints/:slug.md` удалён.

### NEW-0723-2: исходники удалённого сервера: доступ, уборка и честный ответ на вычищенные байты

Исходники переживают сервер — это давняя гарантия платформы, но добраться до них через API было нельзя: весь набор `/v1/infra/servers/:id/sources*` отвечал `404` на удалённый сервер, поэтому владелец не мог ни посмотреть свои версии, ни снять с них тег, ни удалить. Для версии с тегом `published` или `manual` это был тупик: снятие тега — единственный разрешённый способ обойти `409 PROTECTED_BY_TAG`, а именно оно и было недоступно.

Теперь на удалённом сервере работают чтение и уборка: список версий, метаданные версии, скачивание, `tag`, `PATCH`, `DELETE` и `cleanup`. Сохранение новых версий (`POST /sources`) по-прежнему отвечает `404` — мёртвый сервер новых депозитов не принимает.

Чтобы удалённый сервер вообще можно было найти, [GET /v1/infra/servers](/docs/infra/servers/list) принимает `?includeDeleted=true`. По умолчанию выдача не меняется. В каждой строке появилось поле `deletedAt` (`null` у живых серверов).

Отдельно: версия, чьи байты уже вычищены из хранилища, теперь отвечает `410` с кодом `SOURCE_VERSION_BYTES_PURGED` вместо `404`. Разница существенная — `404` утверждал, что версии нет, тогда как запись о ней жива, а восстановление другое: перезалить архив, а не искать его в другом месте. Код приходит на скачивании и на деплое по `{"source": {"versionId": "vN"}}`.

**Затронутые эндпоинты:** [GET /v1/infra/servers](/docs/infra/servers/list), [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy), `GET /v1/infra/servers/:id/sources`, `GET /v1/infra/servers/:id/sources/:versionId/download` — контракт исходников описан на странице [Хранилище исходников](/docs/source-storage)

### FIX-0723-3: деплой galaxy-приложения: обрыв туннеля при сборке больше не маскируется под «host unreachable»

**Было**

Если во время сборки на galaxy-хосте моргал туннель и здорового контейнера этого деплоя в итоге не оказывалось (сборку прервало, контейнер не поднялся, канал exec был занят или приложение крашилось), деплой (`POST /v1/infra/servers/:id/deploy`) возвращал `502 GALAXY_HOST_UNREACHABLE` с советом «повторите, когда хост переподключится». Хост при этом часто был доступен — совет вводил в заблуждение, а вызывающая сторона не видела реальную причину (например, что её приложение падает при старте).

**Стало**

Если после моргания туннеля хост доступен, но деплой не довёл приложение до рабочего состояния, деплой возвращает новый код `502 GALAXY_DEPLOY_INTERRUPTED` — «хост доступен, но деплой прервался до старта приложения: отправьте тот же деплой повторно; если приложение раз за разом не стартует, сначала почините его (команду запуска, порт, зависимости, переменные окружения или лимит памяти)». Это по-прежнему повторяемая ошибка — слот цел, удалять и пересоздавать его не нужно. Код `GALAXY_HOST_UNREACHABLE` теперь остаётся только для действительно недоступного хоста (ни одна попытка проверки до него не достучалась). Если приложение действительно крашится в цикле, это надёжно выявляется уже на повторном деплое обычной проверкой живости (код `GALAXY_APP_START_FAILED`).

### NEW-0723-4: подсказка в ошибке таймаута шага деплоя

Ошибка `DEPLOY_TIMEOUT` у [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) теперь несёт структурированное поле `error.hint` (`reason`, `recovery`, `recoveryAction`), привязанное к зависшему шагу (`error.step`). Для команд пользователя (`install`, `preStart`) подсказка объясняет, что команда не завершилась в отведённое время, и советует сделать её неинтерактивной и завершающейся, а долгоживущие сервисы запускать из команды `start` или в фоне (`docker compose up -d`). Для шага установки рантайма (`runtime` — платформенный шаг, а не команда пользователя) и прочих служебных шагов подсказка указывает на возможный обрыв туннеля и на `POST /v1/infra/servers/:id/repair`. Поле аддитивное: прежние `error.code`, `error.message` и `error.step` не изменились, менять интеграцию не нужно; подсказка приходит и в JSON-режиме, и в SSE-событии `error`.

### FIX-0723-5: ошибка шага деплоя показывает реальную причину, а не безобидное предупреждение

**Было**

При падении шага деплоя поле `data.steps[].stderr` (и, как следствие, `error.message`) могло нести только безобидное предупреждение из одного потока, теряя реальную причину сбоя.

**Стало**

Оба потока возвращаются вместе, с метками `stderr:` и `stdout:`; реальная причина больше не скрывается. Форма ответа и имя поля не изменились.

### FIX-0723-6: сортировка списка по неуникальному полю больше не теряет записи на второй странице

**Было**

Запрос списка или поиск с сортировкой по неуникальному полю (например по датовому `begindate`) при выборке больше 50 записей мог молча вернуть меньше записей, чем есть: на границе страницы часть записей с одинаковым значением поля сортировки терялась. Ответ приходил с кодом 200, без признака неполноты. Затронуты сущности на основе CRM smart-process — сделки, лиды, контакты, компании, предложения, счета и элементы смарт-процессов `/v1/items/{entityTypeId}` — на путях `GET /v1/{entity}`, `POST /v1/{entity}/search`, в подвызовах `POST /v1/batch` и per-entity `POST /v1/{entity}/batch`. Тот же класс нестабильности затрагивал и числовую агрегацию (`POST /v1/{entity}/aggregate` с `sum`/`avg`/`min`/`max`/`groupBy`): выборка записей для агрегата шла без порядка, поэтому на объёмах больше 50 записей часть строк могла теряться и искажать результат.

**Стало**

К сортировке автоматически добавляется вторичный ключ по `id` — порядок становится строго определённым, и постраничная выборка не теряет и не дублирует записи независимо от поля сортировки. Пользовательская сортировка остаётся основным ключом; записи с одинаковым значением поля упорядочиваются по `id` по возрастанию. Менять запросы не нужно.

### FIX-0723-7: переоткрытие тикета комментарием больше не оставляет штамп решения

**Было**

Комментарий команды через `POST /v1/feedback/:id/comments`, возвращающий тикет из `RESOLVED` или `WITHDRAWN` обратно в активный статус (`NEW`, `REVIEWING`, `AWAITING_USER`, `NEEDS_REVIEW`), не сбрасывал `resolvedAt` и `resolvedBy`. Они «зависали» от прошлого закрытия, и на чтении (`GET /v1/feedback/:id`, `GET /v1/feedback`) переоткрытый тикет выглядел одновременно активным и решённым.

**Стало**

Такой комментарий очищает `resolvedAt` и `resolvedBy` — на чтении активный тикет больше не несёт даты решения. `resolution` при этом не очищается: он отражает текст самого комментария. Комментарий, ставящий `RESOLVED`, по-прежнему проставляет штамп; переход в `ARCHIVED` и обычный переход между активными статусами штамп не трогают. Поведение согласовано с уже действовавшим сбросом на `PATCH /v1/feedback/:id`.

### FIX-0723-8: фильтр списков statuses / departments / storages / currencies / products больше не игнорируется молча

**Было**

`GET /v1/statuses`, `/v1/departments`, `/v1/storages`, `/v1/currencies`, `/v1/products` работают через legacy-методы Битрикс24 (`crm.status.list`, `department.get`, `disk.storage.getlist`, `crm.currency.list`, `crm.product.list`), которые молча игнорируют нефильтруемые ключи и операторы. Неизвестное или неподдерживаемое поле фильтра — например `filter[system]` у статусов, `filter[module]` у хранилищ или `filter[price]` у товаров — а также операторы `$gt` / `$contains` / `$ne` возвращали `200` со всей таблицей. Клиент получал полный набор вместо ожидаемого подмножества — тихий отказ с неверными данными.

**Стало**

Для этих сущностей фильтр проверяется до вызова Битрикс24: разрешены только те поля, которые метод действительно фильтрует (проверено вживую). Остальные поля, операторы и пустые множества возвращают `400 UNSUPPORTED_FILTER` с перечнем фильтруемых полей. Разрешённые поля по сущностям: `statuses` — id, entityId, statusId, name, sort, semantics, categoryId; `departments` — id, name, parentId, headId; `storages` — id, name, code, entityType, entityId; `products` — id, name, code, xmlId, active, sectionId, sort. `crm.currency.list` не фильтрует ничего — любой `filter` у `/v1/currencies` возвращает `400` с подсказкой отфильтровать на стороне клиента.

### BC-0723-9: POST /v1/apps больше не возвращает поля prefix и suffix в ответе на создание

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

**Было**

Ответ POST /v1/apps на создание приложения содержал два значения на `vibe_app_` — короткий prefix и полный rawKey. Короткий prefix ошибочно принимали за ключ, и запрос с ним возвращал 401.

**Стало**

Ответ на создание содержит одно значение `vibe_app_` — рабочий rawKey. Поля prefix и suffix остаются в GET /v1/apps и GET /v1/apps/:id для отображения маскированного ключа.

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

Используйте поле rawKey из ответа на создание как X-Api-Key. Маскированный префикс, если он нужен, берите из GET /v1/apps или GET /v1/apps/:id вместо ответа на создание.

### FIX-0723-10: POST /v1/batch сохраняет телефон и почту при создании и обновлении лидов и контактов

**Было**

Через общий POST /v1/batch значения телефона и почты (мультиполя) при создании или обновлении лида либо контакта терялись. Вызов возвращал успех, но поле не сохранялось. Те же данные через одиночный POST /v1/leads или PATCH /v1/contacts/:id и через POST /v1/{entity}/batch сохранялись корректно.

**Стало**

Общий POST /v1/batch сериализует мультиполя так же, как одиночные вызовы. Телефон и почта сохраняются при создании и обновлении.

### FIX-0723-11: Поиск и research больше не отвечают 402 INSUFFICIENT_BALANCE при положительном балансе

**Было**

`POST /v1/search` и `POST /v1/research` с платформенным движком (`bitrix-search`) на персональном ключе могли вернуть `402 INSUFFICIENT_BALANCE` даже при достаточном балансе Vibe на счёте портала. Предварительная проверка баланса искала счёт по владельцу ключа, а счёт с недавних пор один на весь портал — и не находился.

**Стало**

Проверка и списание баланса всегда идут по счёту портала. При положительном балансе запрос выполняется и списывается корректно; `402` возвращается только при реальной нехватке средств. Форма запроса и ответа не изменилась.

### FIX-0723-12: POST /v1/apps честнее сообщает о необходимости подписки Маркетплейса на cloud-shared пути

**Было**

При создании приложения через cloud-shared путь выпуска ключа (единая cloud↔box-модель, раскатка по кольцу порталов) на портале без активной подписки «BitrixGPT + Маркетплейс» [POST /v1/apps](/docs/apps/create) возвращал непрозрачный `502 CONNECTOR_APP_INSTALL_FAILED` без указания причины.

**Стало**

Отказ по подписке теперь классифицируется заранее: до обращения к коннектору `POST /v1/apps` проверяет авторитетное состояние подписки портала и, если оно отсутствует, сразу возвращает `403` с кодом `B24_MARKET_SUBSCRIPTION_REQUIRED` (или `B24_MARKET_TRIAL_USED`, если демо уже использован), понятным сообщением и ссылкой на оформление в `error.details.upgradeUrl`. Если состояние не авторитетно, тот же результат срабатывает при явном отказе коннектора по подписке. Прочие отказы cloud-shared выпуска (модуль не установлен, доступ запрещён портал-админом, прочие ошибки) классифицируются как прежде.

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

Менять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал `502 CONNECTOR_APP_INSTALL_FAILED` при создании приложения, стоит дополнительно ловить `403 B24_MARKET_SUBSCRIPTION_REQUIRED` / `B24_MARKET_TRIAL_USED` и подсказывать пользователю оформить подписку на портале.

### FIX-0723-13: запуск сервера не отвечает ошибкой, если машина уже работает

**Было**

[POST /v1/infra/servers/:id/start](/docs/infra/lifecycle/start) для сервера в состоянии `error`
вызывал запуск машины у облака и любой отказ отдавал как `502 PROVIDER_ERROR`. Если машина к этому
моменту уже работала — например, её подняло автоматическое восстановление после вытеснения, — облако
отвечало отказом «машина уже в состоянии RUNNING», и вызов возвращал ошибку на операции, которая
фактически удалась. Клиент видел `502` и не мог отличить это от настоящего сбоя.

**Стало**

Такой отказ распознаётся как идемпотентный успех: если машина уже работает или находится в
переходном состоянии, вызов возвращает `200` и сервер переходит в `provisioning`, как при обычном
запуске. Настоящие отказы — недостаточно прав, исчерпана квота, машина не найдена — по-прежнему
возвращают `502 PROVIDER_ERROR`.

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

### BC-0723-14: форма deployment.standalone.requiredFields.create в /v1/me стала объектом + документирует slug поля name

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

**Было**

В ответе `GET /v1/me` per-kind под-блок `deployment.standalone.requiredFields.create` был массивом `["provider", "name", "plan", "region"]` — формат `name` не указывался; у соседнего `deployment.galaxyApp.requiredFields.create` поле `name` говорило лишь «required». Имя с кириллицей или заглавными буквами при [POST /v1/infra/servers](/docs/infra/servers/create) отклонялось с `400 INVALID_REQUEST`, но self-discovery об этом ограничении молчал.

**Стало**

`deployment.standalone.requiredFields.create` теперь объект (как соседний `deployment.galaxyApp.requiredFields.create`), и в обоих под-блоках `name` несёт формат: slug из строчных латинских букв по маске `^[a-z][a-z0-9-]*$`, длина 2–63 символа. Человекочитаемую подпись кладите в необязательное поле `displayName`.

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

Плоский `deployment.requiredFields["POST /v1/infra/servers"]` (массив `["provider","name","plan","region"]`) НЕ изменился — если вы читаете его, делать ничего не нужно, набор обязательных полей тот же. Если же ваш код парсил per-kind под-блок `deployment.standalone.requiredFields.create` как массив (`.forEach` / `.includes("name")` / `.length` / `[0]`), перейдите на чтение объекта: ключи — имена полей (`provider`/`name`/`plan`/`region`), значения — их описания.

### NEW-0723-15: GET /v1/contacts/fields получил label и description для всех полей

В ответе [GET /v1/contacts/fields](/docs/entities/contacts/fields) теперь у всех 28 статических полей контакта есть человекочитаемые `label` и `description`. Раньше базовые поля (`name`, `lastName`, `typeId` и другие) приходили только с `type` и `readonly`, без описания смысла. Метки приходят по-русски. Семантику поля можно получить программно из ответа, без сверки со статической документацией. Кроме того, `GET /v1/openapi.json` публикует эти метки и описания (по-английски) как `title` и `description` в схемах `Contact` и `ContactInput`.

### NEW-0723-16: smart-processes: поля relations и linkedUserFields во входной схеме

Поля `relations` (связи с сущностями CRM — parent/child, например привязка смарт-процесса к сделкам) и `linkedUserFields` теперь объявлены во входной схеме и видны в `GET /v1/smart-processes/fields`. Их можно передавать в `POST /v1/smart-processes` и `PATCH /v1/smart-processes/:entityTypeId`, чтобы связать смарт-процесс с другими сущностями CRM и вывести его в пользовательских полях. Фильтрация и сортировка по этим полям не поддерживаются — это вложенные структуры записи, а не поля выборки.

### FIX-0723-17: bizproc-activities и bizproc-robots: тип documentType в /fields исправлен на array

**Было**

`GET /v1/bizproc-activities/fields` и `GET /v1/bizproc-robots/fields` показывали для `documentType` тип `object`, тогда как поле — массив из трёх элементов (`[moduleId, entity, documentType]`), как уже было объявлено у `bizproc-templates`.

**Стало**

Тип `documentType` в `/fields` теперь `array` у всех трёх сущностей — согласованно с реальным контрактом.

### FIX-0723-18: PATCH /v1/bizproc-templates возвращает id числом

**Было**

[PATCH /v1/bizproc-templates/:id](/docs/entities/bizproc-templates/update) возвращал `data.id` строкой (`"1215"`), тогда как `POST` возвращает число (`1215`). Клиент, сравнивавший id из ответа создания с ответом обновления, получал ложное несовпадение.

**Стало**

Ответ `PATCH` возвращает `data.id` числом (`1215`) — так же, как `POST`.

### FIX-0723-19: userfields: тип label в схеме создания исправлен на string

**Было**

OpenAPI-схема `POST /v1/userfields/{entity}` объявляла `label` как `object`. B24 `crm.<entity>.userfield.add` принимает `LABEL` только строкой, поэтому SDK, сгенерированный по спеке (где `label` — объект), отправлял неверный тип и получал ошибку. OpenAPI-спека — публичный контракт: клиенты генерируют по ней SDK, и у тех, у кого тип был «объект», клиент был сломан.

**Стало**

`label` в схеме создания объявлен как `string` (подпись на языке портала по умолчанию). Мультиязычные подписи задаются через `editFormLabel` / `listColumnLabel` / `listFilterLabel` (PATCH после создания). Рантайм не менялся — правка только генерируемой спеки.

### FIX-0723-20: sleep-now для galaxy-приложения теперь отвечает 400 — управляйте им со страницы «Галактики»

**Было**

`POST /v1/infra/servers/:id/sleep-now` для приложения, размещённого в галактике (`GALAXY_APP`), усыплял контейнер и отвечал `200`. Это расходилось с сессионным маршрутом, который такое приложение уже отклонял, и могло рассинхронизировать состояние контейнера с хостом.

**Стало**

Тот же вызов для galaxy-приложения отвечает `400` с `error.code = "GALAXY_APP_USE_GALAXY_ROUTE"` и не меняет состояние: контейнер остаётся `RUNNING`. Управляйте жизненным циклом приложения через маршруты галактики. Для обычных (standalone) серверов поведение sleep-now не изменилось.

### FIX-0723-21: комментарий задачи больше не отдаётся под чужой задачей

**Было**

На старых порталах `GET /v1/tasks/:taskId/comments/:id` возвращал `200` и сам комментарий, даже когда комментарий не принадлежал задаче `:taskId`: один и тот же комментарий отдавался под любой задачей, а поле `taskId` в ответе было простым эхом пути.

**Стало**

Перед выдачей комментарий сверяется с задачей из пути. Если комментарий не принадлежит `:taskId`, эндпоинт отвечает `404` с кодом `NOT_FOUND` и сообщением «Comment not found». Поле `taskId` в ответе теперь совпадает с реальной родительской задачей. Запросы комментария по его настоящей задаче работают как прежде.

### FIX-0723-22: тип компании — единое поле typeId, не companyType

**Было**

Имя поля «тип компании» различалось на слоях. [POST /v1/companies](/docs/entities/companies/create) с полем `companyType` молча игнорировал тип — компания создавалась с типом по умолчанию; сохранить тип можно было только полем `typeId`. Чтение (`GET`, поиск) всегда возвращало тип в поле `typeId`. Фильтр же принимал `companyType`, но не `typeId`.

**Стало**

Тип компании — единое поле `typeId` во всех операциях: создание и изменение, чтение и поиск, фильтр (`filter[typeId]`) и группировка (`groupBy: typeId`). Значения прежние — `CUSTOMER`, `SUPPLIER`, `COMPETITOR` (список: `GET /v1/statuses?filter[entityId]=COMPANY_TYPE`). Поле теперь описано в [GET /v1/companies/fields](/docs/entities/companies/fields).

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

Указывайте тип полем `typeId`. Чтение не меняется — тип всегда приходил в `typeId`. На создании и изменении `companyType` больше не описан (он и раньше не сохранял значение). В фильтре и группировке теперь работает `typeId`, а `companyType` возвращает `400` (`UNKNOWN_FILTER_FIELD` в фильтре, `INVALID_AGGREGATION_FIELD` в группировке) — замените имя на `typeId`.

## 2026-07-22

### BC-0722-1: иконка сервера отдаётся как PNG

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

Иконка сервера теперь отдаётся как PNG 256×256 (`Content-Type: image/png`) — платформа рендерит её из вашего загруженного SVG. Загрузка (`POST /v1/infra/servers/:id/icon`) по-прежнему принимает только SVG и теперь может вернуть `400 ICON_RASTERIZE_FAILED`, если файл не удаётся растеризовать в PNG.

### NEW-0722-2: пробуждение по расписанию (wake-schedules) доступно на всех порталах

CRUD для окон пробуждения — [GET|POST /v1/infra/servers/:id/wake-schedules](/docs/infra/wake-schedules/list), [PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId](/docs/infra/wake-schedules/update) — теперь доступен на всех порталах для отдельных серверов (`kind: "STANDALONE"`): запрос больше не отвечает `403 WAKE_SCHEDULE_DISABLED`. Платформа поднимает спящий сервер к заданному моменту по cron-выражению, дальше запуск задачи делает собственный cron внутри уже поднятой машины. Galaxy-приложения (`kind: "GALAXY_APP"`) пока не участвуют в раскрытии и по-прежнему отвечают `403 WAKE_SCHEDULE_GALAXY_DISABLED`. Ответ `GET /v1/infra/servers/:id` и список серверов теперь содержат аддитивные поля `nextScheduledWakeAt` (время ближайшего пробуждения, ISO 8601 или `null`) и `wakeScheduleCapable` — прежние поля не меняются.

### NEW-0722-3: 409 SERVER_NOT_READY при деплое несёт признак повторяемости

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) в ответе `409 SERVER_NOT_READY`, когда на сервере уже идёт восстановление подключения (запущенное параллельным деплоем или ремонтом), теперь дополнительно возвращает поля `error.retryable: true` и `error.retryAfter` (секунды) и заголовок `Retry-After`. Это машинный сигнал: повтор запроса имеет смысл — подождите указанный интервал и повторите деплой. Прежние клиенты не затронуты: код и текст ошибки прежние, поля добавлены аддитивно.

### NEW-0722-4: Новый эндпоинт активации триала Маркета для портала ключа

`POST /v1/portals/:id/activate-market-trial` активирует одноразовый триал Битрикс24 Маркета для портала, которому принадлежит вызывающий ключ. Раньше активация была доступна только из кабинета — у ключей API программного пути не было.

`:id` обязан совпадать с порталом ключа, иначе `403 PORTAL_MISMATCH`. Тело не требуется. Лимит — 3 запроса в час на портал.

Ответ при успехе: `{ "success": true, "data": { "status": "activated", "trialEndsAt": "..." } }` (или `"status": "already_active"`, если триал/доступ уже есть). Ошибки: `403 WRITE_BLOCKED_READONLY_KEY` (ключ только для чтения), `403 PURPOSE_KEY_FORBIDDEN` (служебный ключ специального назначения не может активировать триал), `404 NOT_FOUND` (портал не найден), `409 ALREADY_ACTIVATED` (триал уже активирован), `409 TRIAL_ACTIVATION_UNAVAILABLE` (триал недоступен для этого портала), `503 TRIAL_ACTIVATION_RETRY` (временная ошибка, повторите позже).

### FIX-0722-5: портал с активной подпиской Маркетплейса больше не получает ложный отказ `MARKETPLACE_REQUIRED`

**Было**

На портале с несколькими держателями ключа разработчика состояние подписки Маркетплейса могло не прочитаться: если первый опрошенный ключ отвечал данными о портале, но без блока подписки (урезанный набор прав), опрос на этом останавливался и до ключа, способного прочитать подписку, дело не доходило. Подписка оставалась неизвестной, и портал с реально оплаченной подпиской получал `402 MARKETPLACE_REQUIRED` на [POST /v1/infra/servers](/docs/infra), а `GET /v1/me` отдавал `capabilities.servers.create.available: false`. Повторный вызов `GET /v1/me?refresh=tariff` отказ не снимал.

**Стало**

Опрос продолжается до ключа, который вернёт блок подписки. Порталу с оплаченной подпиской создание сервера разрешается, `capabilities.servers.create.available` становится `true`. Формат ответов не изменился, действий со стороны клиента не требуется. Состояние обновляется при очередном обновлении тарифа портала (не позже часа) либо сразу по `GET /v1/me?refresh=tariff`.

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

Действий не требуется. Клиент, который ветвился на `402` при создании сервера, продолжает работать: на затронутых порталах этот ответ просто перестаёт приходить.

### FIX-0722-6: поле `region` в ответах об инфраструктуре всегда возвращает идентификатор региона

**Было**

У сервера, созданного и запущенного на международном сегменте, [GET /v1/infra/servers](/docs/infra/servers) и `GET /v1/infra/servers/:id` могли вернуть в поле `region` внутренний идентификатор **зоны размещения**, а не идентификатор региона из каталога. Значение не совпадало ни с одним `id` из [GET /v1/infra/providers/:providerId/regions](/docs/infra/providers), поэтому сопоставить сервер с регионом каталога по этому полю было нельзя, и оно раскрывало детали внутреннего размещения.

**Стало**

`region` всегда содержит нейтральный идентификатор региона из того же пространства имён, что и каталог, — например `bc-eu-central`. Значение совпадает с `id` соответствующей записи `GET /v1/infra/providers/:providerId/regions`, поэтому сервер сопоставляется с регионом каталога напрямую. Зона размещения — внутренняя деталь: при создании сервера указывается регион, конкретную зону внутри него платформа выбирает сама.

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

Ничего, если вы передаёте в `region` значение, взятое из каталога регионов, — этот сценарий не менялся. Если ваш код сравнивал `region` из ответа о сервере со строкой, полученной ранее из ответа о **сервере**, а не из каталога, — сравнение теперь надёжно: оба конца в одном пространстве имён. На вход по-прежнему принимаются и идентификаторы из прежнего каталога.

## 2026-07-21

### FIX-0721-1: имя и описание встроенного поискового движка, имя облачного провайдера

Публичное имя платформенного поискового движка приведено к продуктовому: `GET /v1/search/providers`
и `/v1/me` возвращают `Bitrix24 AI Search` в поле `name` (латинские локали). Идентификатор
провайдера `bitrix-search` не менялся — клиентам ничего делать не нужно.

Из описания того же провайдера снято упоминание цитирования источников: возможность зависит от
движка, привязанного к инстансу, и объявляется машинно в `capabilities.output.citations` того же
ответа.

`GET /v1/infra/providers` на международном сегменте возвращает в поле `name` бренд `Bitrix24 Cloud`
вместо `Bitrix Cloud`. Идентификатор провайдера `bitrix-cloud` не менялся.

**Было**

`"name": "Bitrix AI Search"` · `"description": "Platform AI search with agentic mode and source citations"` · `"name": "Bitrix Cloud"`

**Стало**

`"name": "Bitrix24 AI Search"` · `"description": "Platform AI search with agentic mode"` · `"name": "Bitrix24 Cloud"`

### FIX-0721-2: создание шаблона бизнес-процесса теперь принимает файл шаблона

**Было**

[POST /v1/bizproc-templates](/docs/entities/bizproc-templates) отвечал `422 Incorrect field TEMPLATE_DATA!` при любом теле — создать шаблон было невозможно: поле с содержимым файла `.bpt` не входило в схему сущности и до Битрикс24 не доходило.

**Стало**

Поле `templateData` (файл `.bpt` в виде массива `[имя файла, содержимое в base64]`) принимается и передаётся в Битрикс24, шаблон создаётся. Поле обязательно на создание: без него запрос отклоняется с `400 MISSING_REQUIRED_FIELDS` до обращения к Битрикс24 (раньше приходила сырая ошибка `Incorrect field TEMPLATE_DATA!`). Оно доступно на запись и в описании полей `GET /v1/bizproc-templates/fields`, но не возвращается при чтении.

### NEW-0721-3: Сводный реестр исходников — GET /v1/me/sources

Новый эндпоинт [GET /v1/me/sources](/docs/source-storage) — программный аналог кабинетной страницы «Исходники приложений». Возвращает снапшоты исходников по всем серверам и приложениям, которыми владеет ключ (а ключ администратора аккаунта — по всему аккаунту), с пагинацией (`page`/`limit`/`search`) и стандартным конвертом `{ success, data, total, page, limit }`. Каждая строка несёт указатель для перехода вглубь — `listEndpoint` и `latestDownloadEndpoint` — плюс `reachableViaApi` и, для строк-серверов, `blackholeStatus`. В отличие от [GET /v1/infra/servers](/docs/infra/servers/list), который ограничен серверами вызывающего ключа, этот реестр охватывает и сервер на другом ключе того же владельца.

Ответы server-scoped эндпоинтов исходников ([POST /v1/infra/servers/:id/sources](/docs/source-storage) и соседние list/download/tag/cleanup) теперь описаны в документации; поле `versions[].serverContext` (`{ serverId, serverName, serverDisplayName, linkedApp }`) закреплено в контракте.

### NEW-0721-4: необязательное поле error.b24Code в ответах 422 BITRIX_ERROR

В ответах `422 BITRIX_ERROR` появилось необязательное поле `error.b24Code` — сырой код ошибки Битрикс24 для программной обработки (например, `PERIOD_REQUIRED`, `INVALID_FILTER`). Изменение аддитивно: прежние клиенты, разбирающие только `error.code` и `error.message`, не затронуты.

### NEW-0721-5: Статистика Открытых линий — 6 методов дашборда

Новый раздел API для дашбордов контакт-центра: [POST /v1/openlines/stats](/docs/openlines/stats) (агрегаты за период), [GET /v1/openlines/operators](/docs/openlines/operators) (real-time нагрузка операторов), [POST /v1/openlines/sessions/search](/docs/openlines/sessions), [POST /v1/openlines/sessions/stats](/docs/openlines/sessions/stats), [POST /v1/openlines/sessions/transfers](/docs/openlines/sessions/transfers), [POST /v1/openlines/ratings/search](/docs/openlines/ratings). Требуется скоуп `imopenlines` и право тарифа `report_open_lines` (иначе `403 B24_TARIFF_RESTRICTION`).

**В процессе раскатки** — методы выходят в обновлении Битрикс24 `imopenlines 26.700.0` и доступны не на всех порталах. Пока обновление не приехало на портал, методы возвращают `422 METHOD_NOT_YET_AVAILABLE` с целевой версией в ответе — это признак раскатки, а не ошибка интеграции.

### FIX-0721-6: тарифный отказ Битрикс24 отдаётся как 403 B24_TARIFF_RESTRICTION на всех эндпоинтах

**Было**

Отказ Битрикс24 по тарифному праву приходил как `422 BITRIX_ERROR` с непрозрачным сообщением — отличить его от прочих ошибок Битрикс24 программно было нельзя.

**Стало**

Такой отказ отдаётся как `403` с кодом `B24_TARIFF_RESTRICTION`. Правило общее для всего V1 API, а не только для Открытых линий: любой эндпоинт, вызвавший метод Битрикс24, недоступный на тарифе портала, теперь отвечает этим кодом.

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

Клиенты с обычной обработкой ошибок продолжают работать без изменений — отказ остаётся ошибкой, просто становится точнее. Если вы отдельно ветвились на `422` для тарифных отказов, перенесите ветку на `403` и `error.code === 'B24_TARIFF_RESTRICTION'`. Этот код не означает сбой интеграции: возможность не входит в тариф портала Битрикс24, и повторять запрос бессмысленно до смены тарифа.

### FIX-0721-7: Версионные рантаймы деплоя ставят заявленную версию на Ubuntu 24.04

**Было**

Рантайм `node20` устанавливал Node.js 18 (в репозиториях Ubuntu 24.04 нет Node 20), а `python311` и RAG-рантаймы (`node20-rag`, `python311-rag`) падали на шаге установки — нужных пакетов в дистрибутиве нет. В ответе `GET /v1/infra/runtimes` поле `packages` показывало `postgresql-14`, хотя ставилась PostgreSQL 16.

**Стало**

`node20` ставит Node.js 20 (с проверкой мажорной версии), `python311` — Python 3.11, RAG-рантаймы — PostgreSQL 16 с расширением pgvector в базе приложения. Поле `packages` отражает фактическую версию (`postgresql-16`). Публичные идентификаторы рантаймов и формат запроса `/deploy` не изменились.

### FIX-0721-8: статус ремонта сервера корректен при опросе

**Было**

Опрос [GET /v1/infra/servers/:id/repair-status](/docs/infra/lifecycle/repair-status) при многоузловом бэкенде мог кратковременно вернуть `idle`, даже когда ремонт ещё шёл, — если запрос попадал на другой обслуживающий узел, чем тот, что выполняет ремонт. Клиент, опрашивающий статус в цикле, мог из-за этого ошибочно решить, что ремонт завершился, ещё до его старта.

**Стало**

Эндпоинт надёжно возвращает реальный прогресс ремонта (`running` / `done` / `failed`) независимо от того, на какой узел попал опрос.

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

Форма ответа не изменилась, действий со стороны клиента не требуется.

### FIX-0721-9: приложение на standalone-сервере больше не работает с правами администратора

**Было**

Приложение, задеплоенное на standalone Black Hole сервер, запускалось с правами администратора и без изоляции. Любая уязвимость в самом приложении (например, выполнение произвольного кода) сразу давала полный контроль над всей виртуальной машиной: доступ к ключам подключения сервера, к служебным настройкам и к системным файлам.

**Стало**

Приложение работает под выделенной непривилегированной учётной записью и видит только свой каталог (`extractTo`, по умолчанию `/opt/app`), которым владеет. Системные каталоги защищены от записи, повышение привилегий запрещено. Порт ниже 1024 по-прежнему работает — платформа выдаёт для него отдельное разрешение.

Команды деплоя (`install`, `preStart`) и `/exec` по-прежнему выполняются с правами администратора — здесь ничего не изменилось, `sudo` не нужен.

Ничего менять не требуется: если приложению для запуска действительно нужны права администратора, деплой автоматически возвращает прежний режим, завершается успешно и добавляет предупреждение с причиной. Чтобы сразу пропустить эту попытку (актуально для nginx в качестве команды запуска, MySQL через системный сокет и запуска через Docker), передайте `"hardening": "off"` в теле деплоя.

В `data.steps[]` появились два новых значения `step`: `service_user` — передача каталога деплоя непривилегированной учётной записи, и `hardening` — предупреждение о возврате приложения к прежнему режиму. Клиентам, которые разбирают шаги по имени, стоит их учесть.

### BC-0721-10: битый кандидат в image_url больше не роняет весь запрос

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

**Было**

Массив `content` мог нести несколько частей `image_url`. Если хотя бы одна из них
содержала не изображение — например HTML-страницу с ошибкой, закодированную в Base64
и объявленную как `image/png`, — платформа передавала её модели как есть. Модель не
могла её декодировать, и весь запрос завершался ошибкой `502` с кодом
`ai_provider_unavailable`, даже когда остальные изображения были корректны.

Отдельно: части с неподдерживаемым MIME-типом, повреждённым Base64 или превышением
лимита 20 МиБ отклонялись ответом `400 invalid_image_payload` — тоже на весь запрос
целиком.

**Стало**

Перед отправкой модели платформа проверяет фактическое содержимое каждой части
`image_url` по сигнатуре байтов, а не по заявленному MIME-типу. Часть, содержимое
которой является веб-ответом (HTML, XML, JSON, ответ HTTP) либо не декодируется,
заменяется на своей позиции текстовой заглушкой `[image unavailable: <причина>]`.
Остальные изображения обрабатываются как обычно, запрос завершается успешно.

Позиции частей сохраняются: длина массива `content` не меняется, поэтому нумерация
кандидатов на стороне клиента остаётся верной.

Отклонённые части видны в ответе — в поле `warnings` появляется запись с кодом
`IMAGE_CONTENT_REJECTED`, а в заголовках `X-Image-Parts-Rejected` с их количеством.
Для потоковых ответов заголовок приходит вместе с началом потока.

Ответом `400` теперь завершаются только структурные ошибки: отсутствующее поле
`url`, строка, не являющаяся ни URL, ни data-URI, неподдерживаемая схема и `http://`
в production.

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

Если ваш код полагался на `400 invalid_image_payload` как на признак того, что
изображение не принято, — читайте вместо этого `warnings` или заголовок
`X-Image-Parts-Rejected`. Запрос теперь завершается успешно, и молчаливой потери
изображения не происходит: факт замены всегда отражён в ответе.

Дополнительно изменилось поведение при отказе модели. Раньше любой не-2xx ответ
провайдера приходил как `502`, теперь статус отражает причину:

- ответ модели `400` или `422` → `400` с кодом `ai_provider_rejected`. Повторять
  такой запрос без изменений бесполезно;
- превышение лимита на стороне модели (`429`) → `429` с заголовком `Retry-After`.
  Повторить нужно, выдержав указанную задержку. В потоковом ответе заголовок
  невозможен, поэтому задержка приходит полем `retryAfter` в кадре ошибки;
- таймаут на стороне модели (`408`) → `503` с кодом `ai_provider_timeout`.

Ответы `401`, `403` и `5xx` по-прежнему приходят как `502`. Изменение затрагивает
[POST /v1/chat/completions](/docs/ai/chat/completions) и
[POST /v1/embeddings](/docs/ai/embeddings).

Ошибки со стороны модели теперь дополнительно несут поле `providerStatusCode` —
исходный HTTP-статус ответа модели. По нему `429` от модели отличается от `429`
собственного лимита платформы (у последнего поля нет): код `rate_limit_exceeded`
у обоих одинаковый, чтобы SDK ретраили единообразно, а различитель — это новое
необязательное поле.

Также в data-URI теперь допускаются параметры между типом и `;base64` —
`data:image/jpeg;name=photo.jpg;base64,...` больше не отклоняется.

## 2026-07-20

### NEW-0720-1: Ответы приложений теперь возвращают статус публикации

Ответы раздела приложений — [список](/docs/apps/list), [данные приложения](/docs/apps/get), [создание](/docs/apps/create), [публикация](/docs/apps/publish) и [снятие с публикации](/docs/apps/unpublish) — теперь несут два новых поля: `catalogStatus` (`PRIVATE` / `PUBLISHED` / `UNPUBLISHED`) и `publishedAt` (дата публикации, ISO 8601, или `null`). Раньше прочитать статус публикации через V1 было нельзя — приходилось угадывать по массиву `placements`, что ненадёжно: снятое с публикации приложение может сохранить ранее привязанные коды, а `PRIVATE` и `UNPUBLISHED` по `placements` неразличимы. Поля добавлены аддитивно — прежние вызовы работают без изменений.

### FIX-0720-2: переименование чата больше не отвечает ложным успехом тому, кто не участник

**Было**

[PATCH /v1/chats/:chatId](/docs/chats/management/rename), вызванный от имени администратора портала, который не состоит в чате, возвращал `{ "success": true, "data": true }`, хотя название чата не менялось: Битрикс24 отвечал на такой вызов ложным успехом. Отличить его от настоящего переименования по ответу было нельзя, и интегратор считал операцию выполненной. Вызывающий при этом не мог даже прочитать этот чат.

**Стало**

Перед переименованием проверяется, что вызывающий состоит в чате. Если нет — ответ `404 CHAT_NOT_FOUND_OR_NO_ACCESS`, тот же, что и для любого другого не-участника, и попытки переименования не происходит. Ложный успех больше не выдаётся. Переименование участником, у которого есть права, работает без изменений.

### NEW-0720-3: стриминг chat-completions обрывает зависший ответ апстрима явной ошибкой

Если при стриминге ([POST /v1/chat/completions](/docs/ai/chat/streaming) с `stream: true`) апстрим-модель отдала заголовки, но затем замолчала в середине ответа и не присылает новых данных дольше окна ожидания, прокси теперь прерывает вызов и присылает в поток терминальный кадр ошибки перед `data: [DONE]`:

`data: {"error":{"code":"stream_idle_timeout","type":"server_error","retryable":true,"retryAfter":<секунды>}}`

Раньше такой вызов висел бесконечно (агент оставался в состоянии «receiving stream response»). Ошибка повторяемая — прочитайте поток до конца (`[DONE]`) и повторите запрос с учётом `retryAfter`. Обычные (не зависшие) стримы и «думающие» модели, которые непрерывно присылают токены рассуждений, не затронуты.

### NEW-0720-4: tasks: новое поле timeSpentInLogs

Поле `timeSpentInLogs` (фактически затраченное время в секундах, сумма записей учёта времени) теперь задекларировано в схеме задач — доступно в `select`, фильтре и сортировке [GET /v1/tasks](/docs/entities/tasks/list) и `POST /v1/tasks/search`, и присутствует в [GET /v1/tasks/fields](/docs/entities/tasks/fields).

Раньше поле возвращалось, только если в `select` были указаны ОБА написания сразу (`timeSpentInLogs` и `TIME_SPENT_IN_LOGS`); теперь достаточно любого одного. Поле только для чтения — фиксируется через эндпоинт учёта времени, не через обновление задачи.

### FIX-0720-5: GET /v1/files/:id?include=folder теперь возвращает папку

**Было**

[GET /v1/files/:id](/docs/entities/files/get) с `?include=folder` отвечал `200`, но без блока `_included`, хотя [GET /v1/files/fields](/docs/entities/files/fields) объявлял `folder` как includable — включение молча не срабатывало.

**Стало**

`?include=folder` прикрепляет папку в `_included.folder`, как и обещает `/fields`.

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

Действий не требуется. Клиенты, читавшие `_included.folder`, теперь получают объект вместо его отсутствия.

### FIX-0720-6: /v1/me: блок infra стал точным по лимиту серверов, идентификатору провайдера и разбивке

**Было**

`GET /v1/me` в блоке `infra` возвращал `limits.max: 3` независимо от реально применяемого лимита серверов на ключ; `providers` мог отдавать внутренний идентификатор провайдера (расходясь с `GET /v1/infra/providers`) и с возможными дублями; `limits.breakdown` относил виртуальные машины управляемых ботов к `direct` вместо `bots`.

**Стало**

`limits.max` отражает реально применяемый лимит серверов на ключ; `providers` отдаёт публичный идентификатор провайдера, согласованный с `GET /v1/infra/providers`, без дублей; `limits.breakdown` учитывает машины ботов в `bots`. Интеграцию менять не нужно — значения просто стали корректными.

### FIX-0720-7: deal-categories: неизвестные поля фильтра отклоняются, сортировка по id учитывает направление

**Было**

`GET /v1/deal-categories` с фильтром по неизвестному полю молча возвращал ВСЮ таблицу воронок с `200` — Bitrix24 игнорирует неизвестные ключи фильтра legacy-метода и отдаёт весь список. А сортировка `?sort=id&order=desc` игнорировала направление и всегда возвращала один и тот же порядок.

**Стало**

Фильтр по неизвестному или неподдерживаемому полю (а также операторные префиксы `>`/`>=`/`!`/… и операторные объекты) отклоняется до вызова Bitrix24 с `400 UNSUPPORTED_FILTER`; в сообщении перечислены фильтруемые поля (`id`, `name`, `sort`). Фильтр точным совпадением и `$in` по этим полям работают как прежде. Сортировка `?sort=id` теперь корректно учитывает `asc`/`desc`.

### FIX-0720-8: openline-configs: нераспознанные поля в теле записи больше не пропадают молча

**Было**

`POST /v1/openline-configs` (и `PATCH`) молча игнорировал нераспознанные поля тела — Bitrix24 отбрасывает неизвестные ключи метода `imopenlines.config.*`. Тело из одних только неизвестных полей при этом создавало конфигурацию со значениями по умолчанию и отвечало `200`.

**Стало**

Если в теле НЕТ ни одного известного поля — запрос отклоняется с `400 VALIDATION_ERROR` до вызова Bitrix24, и в сообщении перечислены нераспознанные поля. Если известное поле есть, но часть полей нераспознана — запись выполняется как прежде, а в ответе возвращается `meta.warnings` с перечнем проигнорированных полей (раньше они исчезали без следа). Поля только для чтения (`id`, `queue`, `dateCreate` и другие) в теле записи теперь отклоняются с `400 READONLY_FIELD` — раньше они проходили как «известные» и могли привести к созданию конфигурации со значениями по умолчанию.

### FIX-0720-9: GET /v1/{entity}/fields сигналит о неполных метаданных при сбое Bitrix24

**Было**

Если запрос динамических полей к Bitrix24 (`*.fields`) падал (rate-limit, `QUERY_LIMIT_EXCEEDED`, таймаут очереди), эндпоинт молча отдавал 200 только со статическими полями схемы — у многих из них нет человекочитаемой метки (`label`). Ответ выглядел полным, клиент не мог отличить его от корректного и строил недетерминированные маппинги полей.

**Стало**

При сбое запроса полей ответ по-прежнему 200 со статическими полями, но несёт `meta.warnings: [{ "code": "fields_partial", "message": "..." }]` — клиент видит, что набор полей неполный, и может повторить запрос. Формат предупреждения — объект `{ code, message }`, тот же канал и та же форма, что у `meta.warnings` в list/search, поэтому один разбор по `warning.code` работает на всех эндпоинтах. Сбой теперь также логируется на стороне Vibe.

## 2026-07-19

### BC-0719-1: Cowork/Code: тарифная линейка переименована — Free / Pro / Max / Ultra, цена Ultra снижена

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

**Было**

Поле `tier` в ответах [GET /v1/cowork/state](/docs/cowork/state) и [GET /v1/cowork/me](/docs/cowork/me) (а также `recommendation.upgrade.nextTier` и каталог `tiers[]`) принимало значения `FREE`, `START`, `PRO`, `MAX`. Тариф `MAX` (×20) стоил 40 000 Ꝟ/мес.

**Стало**

Линейка переименована со сдвигом: `START` → `PRO`, `PRO` → `MAX`, `MAX` → `ULTRA`; набор значений теперь `FREE`, `PRO`, `MAX`, `ULTRA`. Множители тарифов не изменились: `PRO` ×1 (база), `MAX` ×5, `ULTRA` ×20; у `FREE` — 5% от `PRO`. Цена `ULTRA` (бывший `MAX`, ×20 объёма) снижена с 40 000 до 20 000 Ꝟ/мес. Объёмы окон откалиброваны по фактическому использованию: 5-часовое окно выросло вдвое на всех тарифах, месячные объёмы уменьшены; в течение текущего оплаченного периода лимиты подписки не меняются — новые значения применяются со следующего продления. Переименование применяется атомарно в момент релиза: значение `START` больше не возвращается, добавилось значение `ULTRA`. Клиенты, ветвящиеся по строковым значениям `tier` / `nextTier`, должны обновить маппинг с учётом сдвига смысла (`PRO` теперь база, `MAX` — средний тариф); клиенты, отображающие серверные `multiplier` / `feeVibes` как есть, продолжают работать без изменений.

### FIX-0719-2: цены тарифов Cowork/Code на международной версии приведены к долларовой шкале

**Было**

На международной версии платформы каталог тарифов в [GET /v1/cowork/state](/docs/cowork/state) отдавал цены в масштабе российской версии: `feeVibes` 2000 / 10000 / 20000 за Pro / Max / Ultra. При курсе 1 Vibe credit = 1 доллар это читалось как 2000–20000 долларов в месяц.

**Стало**

Каталог тарифов на международной версии отдаёт долларовую сетку: Pro — `feeVibes: 20`, Max — `100`, Ultra — `200` в месяц; квоты трёх окон масштабированы согласованно, поэтому ёмкость тарифа в запросах не изменилась. Российская версия не затронута.

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

Если ваш клиент читает `tiers[].feeVibes` из [GET /v1/cowork/state](/docs/cowork/state) на международной версии — отображаемые значения уменьшились в 100 раз и теперь совпадают с реально списываемой ценой активации. Ничего менять в коде не нужно.

## 2026-07-18

### FIX-0718-1: деплой переиспользует сервер приложения вместо создания дубликата

**Было**

`POST /v1/infra/servers` c `source` всегда создавал новый сервер, даже если у приложения, которому принадлежит ключ, уже был сервер — на счёт заводился второй, простаивающий. А `POST /v1/infra/servers/:id/deploy` отвечал `WRONG_KEY`, если ключ вызова отличался от того, которым сервер был создан (например, у приложения есть личный ключ и ключ авторизации).

**Стало**

Если ключ вызова принадлежит приложению, у которого уже есть живой сервер, `POST /v1/infra/servers` возвращает этот сервер с полем `reused: true` вместо создания нового. Если в том же запросе передан `source`, а переиспользуемый сервер — это приложение галактики (`kind: "GALAXY_APP"`), **у которого ещё нет работающего контейнера** (ни разу не деплоилось или прошлый деплой упал), исходный код сразу разворачивается в его собственный сервер (ответ содержит `reused: true` и `deploying: true`, а статус сервера на время сборки — `provisioning`) — так же, как при обычном create с `source`: опрашивайте `GET /v1/infra/servers/:id` до статуса `running`, второй вызов деплоя не нужен. Если же приложение галактики **уже работает**, ответ содержит `reused: true` и `next: "deploy"` — разверните исходный код отдельным вызовом `POST /v1/infra/servers/:id/deploy` (так живой контейнер не затрагивается на время сборки). Для обычного сервера или запроса без `source` ответ содержит `reused: true` и `next: "deploy"` — разверните исходный код отдельным вызовом `POST /v1/infra/servers/:id/deploy`. `POST /v1/infra/servers/:id/deploy` теперь принимает любой ключ этого же приложения и деплоит в его сервер.

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

Ничего менять не нужно. Дубликаты серверов больше не создаются. Раньше one-shot create с `source` на уже существующий сервер приложения возвращал `next: "deploy"` и терял переданный `source` — теперь исходный код разворачивается сразу. И деплой, и чтение статуса (`GET /v1/infra/servers/:id`) сервера приложения работают под любым ключом этого приложения — независимо от того, каким из них вы вызываете API.

### FIX-0718-2: DELETE /lock снимает зависший лок и на удалённом сервере

**Было**

[`DELETE /v1/infra/servers/:id/lock`](/docs/infra/deploy/lock) возвращал `404 NOT_FOUND`, если сервер был удалён — даже когда лок операции остался в памяти платформы и продолжал держать сервер. Из-за этого сценарий «предыдущий сервер удалён, лок завис, следующий деплой падает с `EXEC_BUSY`» не имел выхода: снять такой лок через API было нельзя.

**Стало**

`DELETE /lock` снимает зависший лок и на удалённом сервере — при условии, что он всё ещё принадлежит вашему API-ключу (владение остаётся единственной проверкой; лок не хранит данных и не держит облачных ресурсов). Успешный вызов возвращает `200` с `data.released: true`. `404 NOT_FOUND` теперь означает только «сервер не существует или принадлежит другому ключу».

### NEW-0718-3: коды ошибок коннектора при установке приложения теперь возможны и на облачных порталах (поэтапная раскатка)

[POST /v1/apps](/docs/apps) на облачном портале теперь тоже может устанавливать приложение через модуль `vibecodeconnector` и, соответственно, возвращать те же коды ошибок коннектора, что раньше были возможны только на коробочных порталах: `403 CONNECTOR_APP_INSTALL_FORBIDDEN` (администратор портала Битрикс24 запретил пользователю установку приложений), `409 CONNECTOR_MODULE_NOT_INSTALLED` (модуль `vibecodeconnector` не установлен на портале) и `502 CONNECTOR_APP_INSTALL_FAILED` (прочие сбои установки). Изменение аддитивное: ответ при успешной установке не изменился, а раскатка идёт поэтапно — на большинстве облачных порталов путь установки пока прежний. Клиентам, которые уже обрабатывают эти коды на коробочных порталах, менять ничего не нужно; клиентам, которые их не обрабатывали, стоит добавить обработку.

## 2026-07-17

### FIX-0717-1: деплой на спящий galaxy-хост отвечает раньше клиентских таймаутов

**Было**

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) на спящем общем хосте удерживал соединение открытым до ~6,5 минут, пока хост просыпался. HTTP-клиенты с типовым таймаутом ожидания заголовков (~300 секунд — дефолт Node fetch) обрывали соединение раньше ответа платформы: деплой выглядел как сетевой сбой `fetch failed` без кода ошибки и рекомендаций. Неудачное пробуждение к тому же возвращало хост в сон, и каждый повтор начинал загрузку хоста заново.

**Стало**

Платформа будит хост в фоне и ждёт подключения не дольше ~4 минут. Хост успел подключиться — деплой выполняется одним вызовом, как раньше. Не успел — сразу возвращается повторяемый `502 GALAXY_HOST_UNREACHABLE` с `hint` (повторить тот же запрос через 1–2 минуты, слот не удалять), а хост продолжает просыпаться в фоне — повторный деплой подхватывает уже идущую загрузку вместо новой. В `deployment.galaxyApp._rules` и `deployment.standalone._rules` (GET /v1/me) добавлена рекомендация держать таймаут HTTP-клиента не ниже 690 секунд — строго выше платформенного окна в 660 секунд.

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

Изменений в запросах не требуется. Если деплой на спящий galaxy-хост раньше завершался у вас сетевой ошибкой без ответа платформы — теперь придёт либо успех, либо 502 с инструкцией повторить.

### NEW-0717-2: поиск сотрудников на сервере подсказывает причину пустого списка

Эндпоинт [GET /v1/infra/servers/:id/b24-users](/docs/infra/access/b24-users) теперь возвращает дополнительное поле `hint`, когда список пуст из-за отсутствия доступа к Битрикс24 — приложение ещё не авторизовано на портале или ключ отозван. Прежние вызовы работают без изменений: поле аддитивное и отсутствует при успешной выдаче.

### NEW-0717-3: справочники полей каталога сообщают о nullable-полях

Справочники [GET /v1/catalog-prices/fields](/docs/entities/catalog-prices/fields), [GET /v1/catalog-sections/fields](/docs/entities/catalog-sections/fields) и [GET /v1/catalog-products/fields](/docs/entities/catalog-products/fields) теперь добавляют ключ `"nullable": true` полям, которые могут вернуть `null`: у цен это `quantityFrom`, `quantityTo` и `extraId`, у разделов — `iblockSectionId`, `xmlId`, `code` и `description`, у товаров — `iblockSectionId`, `code`, `weight`, `purchasingPrice`, `purchasingCurrency`, `quantity` и `quantityReserved`. Тот же признак приходит в `data.entities[].fieldsDetailed` ответа [GET /v1/guide](/docs/keys-auth/guide), который читается ключом OAuth-приложения без сессии, а в машинной спеке [GET /v1/openapi.json](/docs/cli) такие поля описаны union-типом вида `["number", "null"]`. Клиент, который строит типизированную модель по справочнику, теперь получает верную nullability и не падает на первом же `null`. Набор полей, их типы и значения в ответах не изменились.

### NEW-0717-4: EXEC_BUSY подсказывает, через сколько повторить

Ответ `409 EXEC_BUSY` (другая операция держит блокировку сервера) на [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) и `POST /v1/infra/servers/:id/deploy` теперь несёт retry-подсказку: HTTP-заголовок ответа `Retry-After` (в секундах) и два новых поля в теле ошибки — `retryable: true` и `retryAfter` (в секундах). Значение `retryAfter` — короткий интервал опроса (повторяйте с ним, пока не пройдёт), а не полное время до автоматического снятия блокировки; полный верхний предел по-прежнему в `error.hint.autoExpiresInSeconds`. Изменение аддитивное: код, поле `message` и `hint` не меняются, клиенты, читающие `error.code`, продолжают работать без правок.

### FIX-0717-5: деплой galaxy-приложения со слишком большим архивом отдаёт 413, а не «хост недоступен»

**Было**

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) с `source.content`, превышающим лимит загрузки, возвращал `502 GALAXY_HOST_UNREACHABLE` — с текстом про недоступность хоста и советом «повторить, когда хост переподключится». Хост при этом был полностью доступен, а повтор того же архива давал тот же результат: интегратор оказывался в бесконечном цикле бесполезных попыток.

**Стало**

Тот же случай возвращает `413 GALAXY_UPLOAD_TOO_LARGE` со структурированным `error.hint`. Причина детерминирована (архив слишком большой), а не транзиентна, поэтому повтор без изменений не поможет. `hint` подсказывает уменьшить архив — исключить `node_modules`, `.git` и артефакты сборки (зависимости платформа ставит на хосте). У galaxy-приложения источник — только встроенный `source.content` (`source.url` отклоняется с `400 GALAXY_DEPLOY_CONTENT_ONLY`), поэтому уменьшить архив — единственный способ восстановления. Отдельный смежный случай: тело запроса, превышающее жёсткий внешний лимит платформы (500 МБ на base64-тело ≈ ~375 МБ бинарного архива), теперь отклоняется на краю кодированным `413 PAYLOAD_TOO_LARGE` (на Vibe-REST `/v1/`-маршрутах — deploy/upload/create; OpenAI-совместимые AI-роуты отдают ошибку в своём конверте) вместо сырого HTML — раньше клиент получал недекодируемый ответ.

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

Ничего менять не нужно: успешные деплои не затронуты. Клиенты, которые ветвились на коде ошибки для этого сбоя, теперь видят честный `413 GALAXY_UPLOAD_TOO_LARGE` вместо вводящего в заблуждение `502 GALAXY_HOST_UNREACHABLE` — последний остаётся для настоящего обрыва туннеля во время сборки.

### NEW-0717-6: displayName и description в деплое задают карточку в каталоге Битрикс24

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) принял два необязательных поля тела — `displayName` и `description`. [POST /v1/infra/servers](/docs/infra/servers/create) (создание сервера) принял необязательный `description`. Значения становятся именем и описанием карточки приложения в каталоге Битрикс24. Если деплой прошёл без `displayName` и `description`, ответ содержит `warnings: string[]` с подсказкой задать их; одношаговое создание galaxy-приложения с `source` (тело `POST /v1/infra/servers` с полем `source`) тоже возвращает `warnings` в ответе 201, когда поля не заданы. Обратная совместимость сохранена — запросы без новых полей продолжают работать как раньше.

### FIX-0717-7: деплой галактик отдаёт 503 при временной перегрузке базы

**Было**

При кратковременном исчерпании пула соединений с базой во время деплоя приложения в галактику [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) возвращал общий `502 GALAXY_APP_DEPLOY_FAILED` — тот же код, что и настоящий провал сборки. Клиент не мог отличить временную перегрузку от терминальной ошибки и часто читал ответ как окончательный.

**Стало**

Временная перегрузка базы теперь отдаётся как `503 POOL_EXHAUSTED` с заголовком `Retry-After` (число секунд для повторной попытки). Настоящий провал сборки по-прежнему `502 GALAXY_APP_DEPLOY_FAILED`.

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

Ничего менять не нужно. Если ваш клиент повторяет запросы, теперь на `503` он получает явный сигнал бэкоффа через `Retry-After` вместо непрозрачного `502`.

### FIX-0717-8: фильтр и сортировка списка комментариев задачи работают на обеих карточках

**Было**

Запрос [GET /v1/tasks/:taskId/comments](/docs/entities/task-comments/list) с параметром `filter` или с сортировкой не по `ID` на портале с новой карточкой задачи возвращал `200` и пустой список, даже когда комментарии в задаче были. Ошибки не приходило, поэтому отличить «под фильтр ничего не подошло» от «фильтр не сработал» было нельзя.

**Стало**

Такой запрос возвращает подошедшие комментарии. Фильтр и сортировка работают по полям `ID`, `AUTHOR_ID` и `POST_DATE`, перед именем поля в фильтре допустим префикс `!`, `>`, `>=`, `<` или `<=`. Фильтр по `AUTHOR_NAME` и сортировка по `AUTHOR_NAME` или `AUTHOR_EMAIL` на новой карточке отвечают `400` с кодом `UNSUPPORTED_FILTER_FIELD` или `UNSUPPORTED_SORT_FIELD` и указывают на `AUTHOR_ID`, на старой карточке эти поля по-прежнему принимаются. Добавлен параметр `offset` — он учитывается на новой карточке при запросе с `filter` или сортировкой не по `ID`, на остальных путях чтения игнорируется. Код `INVALID_FILTER` теперь приходит ещё и тогда, когда `filter` — скаляр или пустой массив вместо объекта (`0`, `false`, `""`, `[]`), значение поля `ID` или `AUTHOR_ID` не число, значение `POST_DATE` не разбирается как дата, либо значение поля — объект или массив вместо скаляра. В `meta` добавлено поле `truncated` со значением `true` — просмотрено предельное окно истории, и часть комментариев осталась за его границей.

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

Менять ничего не нужно: запрос, который раньше отдавал пустой список, начинает отдавать данные. Учтите три границы. Первая — фильтр по `AUTHOR_NAME` и сортировка по `AUTHOR_NAME` или `AUTHOR_EMAIL` на портале с новой карточкой вместо пустого `200` теперь отвечают `400`, переведите такой запрос на `AUTHOR_ID`, идентификатор сотрудника по имени даёт `GET /v1/users`. Вторая — `meta.total` на запросе с фильтром к новой карточке считает подошедшие комментарии в пределах просмотренного окна, а не во всей истории задачи, и при `meta.truncated: true` это неполное число. Третья — значение `POST_DATE` на новой карточке сравнивается с `createdAt` в UTC, поэтому результат на границе суток может отличаться от выборки на старой карточке. На порталах со старой карточкой поведение не изменилось.

## 2026-07-16

### BC-0716-1: json_object на реасонинг-модели восстанавливает JSON, тело 422 уточнено

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

**Было**

Запрос [POST /v1/chat/completions](/docs/ai/chat/completions) с `response_format` типа `json_object` на модели с рассуждением (например `bitrix/bitrixgpt-5.5-agent`) стабильно возвращал `422 structured_output_truncated`, даже когда модель завершилась сама (`finish_reason: "stop"`) и положила готовый валидный JSON в служебный канал `reasoning_content` — ответ терялся. В теле любой такой ошибки присутствовали поля `error.suggestedMaxTokens` и `error.param`, а текст утверждал «finish_reason=length» независимо от реальной причины остановки.

**Стало**

Если модель на `json_object` завершилась сама и валидный JSON лежит в `reasoning_content`, платформа восстанавливает его и возвращает `200` с этим JSON в `content` (в потоковом режиме — чанком `content` перед терминальным чанком с `finish_reason`). Тело `422` стало правдивым: `error.suggestedMaxTokens` и `error.param` присутствуют **только** при реальной обрезке (`finish_reason: "length"`); при завершении по любой другой причине (`stop` и т.д.) эти поля **опущены**, а текст называет фактический `finish_reason`. Поведение `json_schema` не изменилось — там строгая схема проверяется на стороне модели.

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

Ничего, если вы просто обрабатываете `422` по `error.code`. Если ваш код **безусловно** читает `error.suggestedMaxTokens` или `error.param` на ошибке `structured_output_truncated` — сделайте чтение опциональным: при `finish_reason !== "length"` этих полей теперь нет. Потоковым клиентам с `response_format` — собирать `content` по всем дельтам до `data: [DONE]`.

### FIX-0716-2: env, отправленный файлом в multipart-деплое, больше не игнорируется молча

**Было**

При `multipart/form-data` в [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) поле `env`, отправленное как файл или Blob, молча игнорировалось — деплой завершался успехом, но приложение стартовало без переменных окружения.

**Стало**

Такой запрос возвращает 400 с кодом `VALIDATION_ERROR` и подсказкой отправлять `env` текстовым полем с JSON-строкой.

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

Корректный способ (текстовое поле `env` со значением вида `{"KEY":"value"}`) не затронут. Кто отправлял `env` файлом или Blob — теперь получает явную ошибку вместо ложного успеха.

### FIX-0716-3: деплой крупных архивов через source.content больше не падает с Gateway HTTP 413

**Было**

На части порталов [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) с `source.content` (base64-архив) падал на шаге `download` с `{ "code": "DEPLOY_FAILED", "message": "Gateway HTTP 413", "step": "download" }`, если base64-тело превышало ~1 МБ (примерно 768 КБ исходного `tar.gz`) — вопреки заявленному лимиту 500 МБ. Обходом была загрузка через `source.url`.

**Стало**

Лимит 500 МБ на inline-загрузку (`source.content` и `multipart`) действует на всех порталах. `source.url` продолжает работать как раньше.

### BC-0716-4: POST /v1/infra/servers отклоняет неизвестные поля в теле

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

**Было**

Неизвестное поле в теле запроса молча игнорировалось. Запрос с `deployMode: "STANDALONE"` (несуществующее поле) возвращал `201` и создавал galaxy-приложение вместо ожидаемого выделенного сервера — правильное поле называется `placement: "dedicated"`.

**Стало**

[POST /v1/infra/servers](/docs/infra/servers/create) отклоняет тело с неизвестным полем ошибкой `400 UNKNOWN_PARAM`. В `details.unknownFields` перечислены лишние поля, в `details.suggestions` — подсказка правильного имени (`deployMode` → `placement`), в `details.validParams` — полный список допустимых полей.

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

Убрать из тела поля, которых нет в списке параметров создания, либо исправить опечатку по подсказке `details.suggestions`. Модель размещения задаётся полем `placement` (`auto` по умолчанию, `dedicated` — отдельная виртуальная машина).

### NEW-0716-5: /v1/sites/fields описывает допустимые значения поля type

`GET /v1/sites/fields` теперь возвращает у поля `type` перечень допустимых значений в `type.enum` с подписями: `PAGE` (лендинг), `STORE` (интернет-магазин), `KNOWLEDGE` (база знаний 2.0), а также `VIBE` (сайт из конструктора) и `SMN` (связка с модулем «Управление сайтом»). Значения `VIBE` и `SMN` встречаются только в ответах и доступны только для чтения — создать сайт такого типа через API нельзя.

### NEW-0716-6: Избранное и закрепление задачи без прав на редактирование

Добавлены четыре ручки для «личных» действий над задачей, которые в Битрикс24 разрешены при доступе только на чтение (не на редактирование): `POST /v1/tasks/:taskId/favorite` добавляет задачу в избранное, `DELETE /v1/tasks/:taskId/favorite` убирает из избранного, `POST /v1/tasks/:taskId/pin` закрепляет задачу в списке задач текущего пользователя, `DELETE /v1/tasks/:taskId/pin` открепляет. Раньше единственным способом изменить задачу был `PATCH /v1/tasks/:id`, который требует прав на редактирование и возвращал «Нет доступа к редактированию задачи», из-за чего разрешённые пользователю действия были недоступны.

### NEW-0716-7: предупреждение о вытесняемом тарифе в ответах пробуждения по расписанию

Ответы [POST /v1/infra/servers/:id/wake-schedules](/docs/infra/wake-schedules/create) и [PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId](/docs/infra/wake-schedules/update) теперь дополнительно несут два поля верхнего уровня: `preemptibleAdvisoryCode` и `preemptibleAdvisory`. Если сервер работает на вытесняемом тарифе, `preemptibleAdvisoryCode` равен `"PREEMPTIBLE_BEST_EFFORT"`, а `preemptibleAdvisory` — краткое английское пояснение того же факта: подъём такого сервера к моменту окна не гарантирован, окно может быть пропущено при нехватке свободной ёмкости. Для сервера на невытесняемом тарифе оба поля — `null`. Предупреждение не блокирует создание или обновление окна — это тот же неблокирующий паттерн, что уже используют поля `tzWarning`/`tzWarningCode`. Старые интеграции, не читающие новые поля, продолжают работать без изменений.

## 2026-07-14

### NEW-0714-1: OpenAPI-спека: валидность 3.1, семантика полей и срез по скоупу

**Было**

Машинная спека [GET /v1/openapi.json](/docs/cli) содержала 3.0-ключ `nullable` (невалиден в 3.1), не объявляла path-параметр `{entityTypeId}` на batch/aggregate/fields/products, не несла описаний/допустимых значений/примеров полей, описывала только ключи `vibe_app_` и отдавалась одним монолитом.

**Стало**

Спека валидна по OpenAPI 3.1: nullable-поля используют union-тип `["<тип>","null"]`, все path-параметры объявлены. У свойств появились `title`/`description`, допустимые значения (`x-enumValues` + расшифровка в описании) и примеры. Секьюрити-схемы называют все три вида ключей (`vibe_api_`/`vibe_app_`/`vibe_live_`), у каждой операции есть `x-required-scope`, а в корне — каталог `x-scopes`. Добавлены корневые `tags`/`externalDocs`, блок `webhooks` для бот-событий и `anyOf` для мультиполей. `GET /v1/openapi.json?scope=<scope>` (например `?scope=crm`) отдаёт срез спеки по одному скоупу, чтобы он помещался в контекст агента. Указатели на спеку добавлены в `/v1/me` и `/v1/guide`. Массовый перенос curl-примеров по каждой операции — отдельная последующая работа.

### FIX-0714-2: Общая 5xx-ошибка на AI-эндпоинтах теперь в OpenAI-совместимом конверте

[AI-эндпоинты](/docs/ai) (`/v1/ai/*`, `/v1/models`, `/v1/chat/*`, `/v1/audio/*`) документированы с OpenAI-совместимым форматом ошибок. При исчерпании пула соединений это уже соблюдалось, но общая (непредвиденная) `5xx`-ошибка на этих эндпоинтах отдавала обычный V1-конверт.

**Было**

Общая `5xx`-ошибка на AI-эндпоинте: `{"success": false, "error": {"code": "...", "message": "..."}}` — не тот формат, что SDK-клиенты OpenAI ожидают для этих путей.

**Стало**

Тот же случай теперь отдаёт `{"error": {"message": "...", "type": "...", "code": "..."}}` — единый конверт для всех ошибок AI-эндпоинтов, включая общие `5xx`.

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

Клиенты на OpenAI SDK, уже читающие `error.type`/`error.code` (штатный путь для этих эндпоинтов), не заметят изменения. Код, который на AI-эндпоинтах ожидал `success`/`error.code` в верхнем уровне именно на общих `5xx`, должен переключиться на `error.type`/`error.code`.

### FIX-0714-3: `versions` в списке версий исходников приложения ограничен 500 записями

`GET /v1/apps/:id/sources` возвращал весь список версий без ограничения — для приложений с очень длинной историей это была неограниченная выборка.

**Было**

`data.versions` — весь список версий без ограничения по размеру; `data.totalVersions` всегда равнялся `data.versions.length`.

**Стало**

`data.versions` содержит не более 500 последних версий (по `savedAt`, убывание). `data.totalVersions` и `data.totalSizeBytes` по-прежнему считаются по полной выборке — точность агрегатов не зависит от кэпа.

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

Для приложений с историей до 500 версий поведение не меняется. Для приложений с историей больше 500 версий `data.versions.length` теперь может быть меньше `data.totalVersions` — код, полагавшийся на их равенство, должен ориентироваться на `data.totalVersions`/`data.totalSizeBytes` для агрегатов и не считать `data.versions` полным списком.

### FIX-0714-4: Телефония: `userId`/`duration` теперь по-настоящему валидируются, а не только на truthy

`POST /v1/calls/register`, `/v1/calls/:callId/show`, `/v1/calls/:callId/hide`, `/v1/calls/:callId/finish` принимали `userId` (и `duration` у `finish`) без проверки типа/формы — любое truthy-значение (например, строка `"abc"` или объект) проходило в Битрикс24 и падало там уже непрозрачной ошибкой апстрима.

**Было**

`{"userId": "abc"}` (или любое другое truthy, не являющееся положительным целым) проходил валидацию и уходил в Битрикс24; ошибка `Required: userId (number)` появлялась только на полностью пустом/falsy значении.

**Стало**

`userId` принимается как положительное целое число либо как числовая строка (`"42"`), иначе — чистый `400 MISSING_PARAMS` с уточнённым текстом `Required: userId (positive integer)` (для `register` — плюс `phoneNumber`). `duration` у `finish` аналогично — неотрицательное число либо числовая строка, иначе `400`.

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

Корректные вызовы (`userId` — число или числовая строка) не меняются. Вызовы с ранее «проходившим» некорректным `userId`/`duration` (не число, не числовая строка) теперь получают явный `400` вместо непрозрачной ошибки со стороны Битрикс24.

### FIX-0714-5: Создание комментария к несуществующей задаче — понятная ошибка вместо ложного успеха

[POST /v1/tasks/:taskId/comments](/docs/entities/task-comments/create) и его пакетный вариант [POST /v1/tasks/:taskId/comments/batch](/docs/entities/task-comments/comments-batch) (`action: create`) обращаются к Битрикс24 для создания комментария на старой карточке задачи. Если задача не существует или недоступна ключу, Битрикс24 не создаёт комментарий и не возвращает идентификатор.

**Было**

Оба эндпоинта отвечали успехом с пустым идентификатором — одиночный вызов `201 {"success": true, "data": {"id": null}}`, пакетный — элементом `{"success": true, "id": null}`. Комментарий не создавался, но интегратор не мог отличить это от штатного случая.

**Стало**

Одиночный вызов возвращает `404 TASK_NOT_FOUND`. Пакетный вариант помечает соответствующий элемент как `{"success": false, "error": "TASK_NOT_FOUND"}`, не отменяя обработку остальных элементов пакета. Отдельный, не связанный с этой ошибкой случай сохраняется: на новой карточке задачи комментарий уходит в чат, и если система не смогла восстановить его id отдельным поиском, ответ по-прежнему `success: true` с `id: null` — комментарий в этом случае реально создан.

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

Код, проверяющий `data.id` / `data[i].id` на `null` как признак ошибки, продолжит работать без изменений и получит более точный код ошибки. Код, полагавшийся на молчаливый успех с `id: null` при недоступной задаче, должен обрабатывать `404` / `TASK_NOT_FOUND` явно.

### FIX-0714-6: Лимит запросов `/v1/search`, `/v1/research`, `/v1/batch` теперь считается на портал

**Было**

Лимиты (`/v1/search` 60/мин, `/v1/research` 20/мин, `/v1/batch` 30/мин) фактически считались по IP-адресу, а не по порталу. Портал с несколькими API-ключами (или несколько порталов за одним общим egress-IP) мог получить больше документированного лимита, а само ограничение обходилось ротацией IP.

**Стало**

Лимит считается **на портал**: все API-ключи одного портала делят единый бакет (60 / 20 / 30 запросов в минуту соответственно). Документированный лимит на тенант теперь применяется корректно и не обходится ни числом ключей, ни сменой IP.

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

Если ваш портал распределял нагрузку на `/v1/search` / `/v1/research` / `/v1/batch` между несколькими API-ключами, суммарный предел теперь — единый лимит портала, а не сумма по ключам. При достижении предела возвращается `429` с заголовком `Retry-After` (как и раньше).

### FIX-0714-7: Календарь: рабочий batch-delete секций, чистые ошибки update и без утечки sync-полей

**Было**

- `POST /v1/calendar-sections/batch` с `action: "delete"` не имел канала для `type`/`ownerId`, которые требует `calendar.section.delete` — каждый элемент падал на обеих платформах.
- `PATCH /v1/calendar-sections/{id}` без `type`/`ownerId`/`name` уходил в Битрикс24 и возвращал сырой `422` с именем внутреннего метода.
- `GET /v1/calendar-sections` отдавал недокументированные сырые поля Битрикс24 `GAPI_CALENDAR_ID`, `CAL_DAV_CON`, `SYNC_TOKEN`, `PAGE_TOKEN`, `EXTERNAL_TYPE` (три из них — токены синхронизации).
- `GET /v1/calendar-events` и `GET /v1/calendar-events/{id}` отдавали внутреннее поле `attendeesEntityList` — схема пыталась его вырезать, но не срабатывала из-за несовпадения регистра ключа.

**Стало**

- Batch-delete секций читает `type`/`ownerId` из тела рядом с `ids` и прокидывает их в каждую команду удаления. Отсутствие любого — чистый `400 MISSING_REQUIRED_PARAMS` до вызова Битрикс24.
- Partial-update секций требует якорные `type`/`ownerId`/`name` (у секций нет get-by-id для подстановки) — чистый `400`, без сырого `422` и без утечки имени метода.
- Оба календарных read-пути убирают перечисленные внутренние/sync-поля из ответа.

**Затронутые эндпоинты:**

- [`POST /v1/calendar-sections/batch`](/docs/entities/calendar-sections/create)
- [`PATCH /v1/calendar-sections/{id}`](/docs/entities/calendar-sections/update)
- [`GET /v1/calendar-sections`](/docs/entities/calendar-sections/list)
- [`GET /v1/calendar-events`](/docs/entities/calendar-events/list)

### FIX-0714-8: PATCH catalog-product-properties снова работает (был неустранимый catch-22)

**Было**

Обновить свойство товара было невозможно ни при каком теле: `PATCH` без `iblockId` → `422` («Required fields: iblockId» — B24 требует его на каждом update), а `PATCH` **с** `iblockId` → `400 READONLY_FIELD` (поле только для создания). Итог — весь глагол UPDATE мёртв: ни одно поле нельзя было изменить после создания.

**Стало**

`iblockId` теперь подставляется автоматически из существующей записи (pre-fetch, как у `catalog-sections`), так что `PATCH {name:"…"}` доходит до B24 с нужным `iblockId` и возвращает `200`. `iblockId` в теле по-прежнему не нужен (и по-прежнему отклоняется как read-only, если его прислать) — его сохраняет сам сервис.

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

Если раньше ваш `PATCH` свойства товара всегда падал `422`/`400` — теперь шлите только изменяемые поля (`PATCH {name:"…"}`), `iblockId` передавать не надо.

### FIX-0714-9: Валидация entityTypeId: мусорные формы → 400 вместо тихого усечения

**Было**

Пять поверхностей парсили `entityTypeId` (селектор типа smart-process / динамической сущности) снисходительно — неякорным `parseInt` или коэрсирующим `Number()`, — и мусорная форма молча превращалась в ДРУГОЙ (в рамках портала) тип сущности:

- Путевой `entityTypeId` (`/v1/items/:entityTypeId/...`, `/v1/categories/:entityTypeId/...` — CRUD и `/aggregate`): `GET /v1/items/1058abc` усекался до `1058`, `1e3` → `1`, `1.5` → `1`.
- Глобальный `POST /v1/batch`: `params.entityTypeId` через `Number()` принимал дробные (`1.5`), hex (`'0x10'` → 16), переполнение до `Infinity`, а также массив `[1058]` → 1058.
- `/v1/items/:entityTypeId/userfields/*`: собственный парсер — `2abc` резолвил пользовательские поля типа `2`.
- `POST /v1/smart-processes/batch`: `ids: ['1030abc']` усекался до `1030` — delete/update молча выполнялся против ЧУЖОГО реального типа; дробное число (`1030.5`) тоже проходило.
- `POST /v1/triggers/fire` (`entityType="item"`): `entityTypeId: '1038abc'` → триггер автоматизации выстреливал по типу `1038`.

**Стало**

Все пять поверхностей требуют каноничную форму положительного целого (`/^[1-9]\d*$/` для строк, `Number.isInteger` для чисел): любая иная форма → `400` с прежним кодом ошибки поверхности (`INVALID_DYNAMIC_PARAM` / `INVALID_ENTITY_TYPE_ID` / `BATCH_ITEM_VALIDATION` / `MISSING_PARAMS`) ДО вызова Битрикс24. Aggregate использует общий с CRUD-маршрутами валидатор вместо инлайн-копии.

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

Формы, которые раньше коэрсились в корректное значение и обслуживались — `007` → 7, `%20`-пробелы, `+2` → 2, массив `[1058]` в batch — теперь отклоняются с `400`: значение должно быть каноничным целым без префиксов, хвостов и ведущих нулей. Boolean в batch отклонялся и раньше (коэрсился в reserved-тип); меняется только код ошибки — теперь `INVALID_DYNAMIC_PARAM`. Корректные вызовы не меняются.

### FIX-0714-10: Переоткрытие фидбека очищает поля решения

**Было**

`PATCH /v1/feedback/:id` (и админ-эндпоинт `PATCH /api/platform/feedback/:id`, через который переоткрывает админ-UI) при возврате тикета в активный статус (`NEW`, `REVIEWING`, `AWAITING_USER`, `NEEDS_REVIEW`) из `RESOLVED`/`WITHDRAWN` не сбрасывал `resolvedAt`, `resolvedBy` и `resolution` — они «зависали» от предыдущего закрытия, и переоткрытый тикет выглядел одновременно активным и решённым.

**Стало**

Возврат тикета **из** `RESOLVED`/`WITHDRAWN` в активный статус очищает `resolvedAt`, `resolvedBy` и `resolution`. `RESOLVED`/`WITHDRAWN` по-прежнему проставляют штамп решения; `ARCHIVED` не трогает поля (архивирование сохраняет историю решения). Обычный переход между активными статусами (например `AWAITING_USER → REVIEWING`) поля НЕ трогает — на активных тикетах `resolution` отражает последний комментарий команды. Явно переданный в том же запросе `resolution` имеет приоритет над очисткой.

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

Если вы читали `resolvedAt`/`resolution` переоткрытого тикета и получали значения от прошлого закрытия — теперь они `null` для активного тикета.

### FIX-0714-11: `INVALID_JSON_BODY` больше не цитирует внутренний текст парсера

**Было**

Шесть маршрутных групп (`/api/billing/*`, `/v1/apps*`, `/v1/bots*`, `/v1/keys*`, `/v1/note*`, `/v1/infra/servers/*` deploy/exec/upload) на битый JSON отвечали `400` с сообщением вида `Invalid JSON: Unexpected token } in JSON at position 41` — сырой текст движка V8 (отпечаток рантайма, деталь реализации). Группа deploy/exec/upload при этом не ставила и код ошибки.

**Стало**

Все шесть отвечают единым статическим сообщением `Request body is not valid JSON.` — как `/v1/<entities>` (тот же класс закрывается там отдельным исправлением). На V1-поверхностях код — `INVALID_JSON_BODY` (deploy/exec/upload теперь тоже его ставит); статус `400` не изменился.

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

Если ваш код парсил текст сообщения (например, вытаскивал позицию ошибки) — опирайтесь на код `INVALID_JSON_BODY`; позиция ошибки больше не сообщается.

### FIX-0714-12: Смета: amount, currency и даты в ответе GET снова заполнены (были null)

**Было**

Чтение сметы (`GET`/list/search `/v1/quotes`) отдавало `null` для `amount`, `currency`, `beginDate`, `closeDate` — значения «протекали» только под сырыми ключами Битрикс24 (`opportunity`, `currencyId`, `begindate`, `closedate`). Запись работала правильно, но READ-проекция теряла все поля-алиасы: **сумма сметы и валюта были 100% невидимы через задокументированный API**.

**Стало**

READ-ветка теперь реверс-мапит объявленные алиасы (зеркало write-маппинга): `opportunity → amount`, `currencyId → currency`, `begindate → beginDate`, `closedate → closeDate` — с приведением типов. Сырые ключи Битрикс24 в ответе больше не появляются.

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

Если вы читали `amount`/`currency` смет и получали `null` — теперь они заполнены. Код, читавший обходным путём сырой `opportunity`/`currencyId` из ответа, их там больше не найдёт — переключитесь на документированные `amount`/`currency`.

### FIX-0714-13: Валидация записи: фантомный чек-лист → 404, мусорные типы и `:id` → 400

**Было**

- `POST /v1/tasks/:taskId/checklist` на несуществующую задачу возвращал `201` с правдоподобным `id`, хотя пункт не создавался (его нельзя было получить GET-ом).
- `POST /v1/warehouses` принимал нестроковые `title`/`address` (число, объект) и пересылал их в Битрикс24 с непредсказуемым результатом; `POST /v1/doc-templates` так же пропускал нестроковые `name`/`region` и нечисловой `numeratorId`.
- Нечисловой `:id` в путях сущностей с типизированным числовым id (`GET/PATCH/DELETE /v1/quotes/abc`, `/v1/deals/1.5`, `/v1/leads/1e3`) уходил в Битрикс24 как есть — в ответ прилетала непрозрачная ошибка B24 вместо внятного кода. У smart-processes `12abc` усекался `parseInt`-ом до `12` и попадал в ЧУЖОЙ тип.

**Стало**

- Чек-лист: родительская задача проверяется до создания пункта; несуществующая (или недоступная ключу) задача → `404 TASK_NOT_FOUND`.
- Склады и шаблоны документов: значение не того типа → `400 INVALID_PARAMS` без вызова Битрикс24 (числовая строка в `numeratorId` по-прежнему принимается).
- Сущности с явно типизированным числовым id: не-канонично-целый `:id` → `400 INVALID_PARAMS` до вызова Битрикс24 (id `0` — основная воронка сделок `categories` — остаётся валидным). Smart-processes сохраняют `INVALID_ENTITY_TYPE_ID` и теперь отклоняют `12abc` на GET/PATCH/DELETE, а не усекают до `12`. Сущности, у которых тип id не задекларирован в схеме, сохраняют прежнее сквозное поведение.

Затронутые эндпоинты: [POST /v1/tasks/:taskId/checklist](/docs/entities/tasks/checklist), [POST /v1/warehouses](/docs/entities/warehouses/create), [POST /v1/doc-templates](/docs/entities/doc-templates/create) + GET/PATCH/DELETE по сущностям с типизированным числовым id.

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

Если ваш код опирался на фантомный `201` от чек-листа или отправлял мусорные значения «на авось» — теперь придёт явный `4xx` с кодом. Корректные вызовы не меняются.

### FIX-0714-14: Пять тихих false-success/hint-дефектов: честный ответ вместо мнимого успеха

**Было**

- `PATCH /v1/userfields/{entity}/{id}` c `label` — тихий no-op на обновлении: ответ `200`, но подпись поля не менялась (Битрикс24 `crm.*.userfield.update` игнорирует `LABEL`).
- `POST /v1/humanresources/nodes/{id}` c `parentId` — ложный «перенос удался»: `name` применялся, `parentId` молча игнорировался, а в ответе эхом возвращался старый родитель.
- `POST /v1/chats/messages/bulk` — псевдоним `dialogId: "me"` не разрешался в bulk-цикле (одиночные роуты его разрешают) → сообщение уходило не туда.
- `POST /v1/bots/{botId}/chats/{dialogId}/users` — предохранитель `USERS_NOT_ADDED` был мёртвым кодом (не совпадал с формой ответа метода v2) → неудачное добавление проходило как успех.
- Ошибки `crm.item.list` получали нерелевантную подсказку «Maximum 50 records…» даже когда причина была иной (например «entity type does not exist»).

**Стало**

- `label` на обновлении разворачивается в реальные параметры `EDIT_FORM_LABEL`/`LIST_COLUMN_LABEL`/`LIST_FILTER_LABEL` — подпись действительно меняется.
- `parentId` и `type` теперь только для создания: на `PATCH` они отклоняются с `400` (перенос — через `POST /v1/humanresources/nodes/{id}/move`), а не имитируют успех.
- Bulk-чтение сообщений разрешает `dialogId: "me"` для каждого элемента, как и одиночные роуты.
- Предохранитель добавления в бот-чат снова срабатывает: непринятые пользователи возвращаются в `warning.USERS_NOT_ADDED`.
- Подсказки об известных ограничениях привязаны к релевантности сообщения ошибки, а не только к имени метода.

**Затронутые эндпоинты:**

- [`PATCH /v1/userfields/{entity}/{id}`](/docs/userfields/crm/update)
- [`PATCH /v1/humanresources/nodes/{id}`](/docs/humanresources/nodes/update)
- [`POST /v1/chats/messages/bulk`](/docs/chats)
- [`POST /v1/bots/{botId}/chats/{dialogId}/users`](/docs/bots)

### FIX-0714-15: GET /v1/task-time теперь честно возвращает больше 50 записей при limit>50

**Было**

[GET /v1/task-time](/docs/entities/tasks/time) с `limit` больше 50 возвращал только 50 записей, хотя `meta.limit` повторял запрошенное значение, а `meta.hasMore` мог вводить в заблуждение. Клиент, обходивший данные постранично с шагом больше 50, молча терял часть записей.

**Стало**

Запрос отдаёт до `limit` записей (максимум 500), собирая их постранично на стороне бэкенда; `meta.total` и `meta.hasMore` соответствуют фактически возвращённому окну. При `limit` больше 50 значение `offset` теперь указывает на правильную позицию, а не сдвигается на первые страницы.

### NEW-0714-16: GET /v1/companies/fields и системные поля catalog-prices получили label и description

В ответе [GET /v1/companies/fields](/docs/entities/companies/fields) теперь у всех полей компании есть человекочитаемые `label` и `description`. В [GET /v1/catalog-prices/fields](/docs/entities/catalog-prices/fields) те же метки добавлены системным полям `extraId`, `priceScale` и `timestampX`. Метки приходят по-русски. Семантику поля можно получить программно из ответа, без сверки со статической документацией.

### FIX-0714-17: /stop и /reboot различают отсутствующий сервер и неподходящий статус

**Было**

[POST /v1/infra/servers/:id/stop](/docs/infra/lifecycle/stop) и [POST /v1/infra/servers/:id/reboot](/docs/infra/lifecycle/reboot) на сервере не в статусе `running` (например, спящем) отвечали плоским `404 NOT_FOUND` с текстом «Running server not found» — по нему нельзя было понять, что сервер существует, и агент решал, что он удалён.

**Стало**

Оба маршрута ведут себя как [/start](/docs/infra/lifecycle/start) и [/wake](/docs/infra/lifecycle/wake): `404 SERVER_NOT_FOUND` — только когда сервера с таким `id` действительно нет; `422 SERVER_WRONG_STATE` — когда сервер есть, но не в статусе `running`. В `error.currentState` возвращается текущее состояние, в `error.availableActions` — доступные действия (`wake`/`start`/`repair`/`delete`).

### FIX-0714-18: понятная ошибка на /fields у комментариев и учёта времени задач

**Было**

Запрос [GET /v1/tasks/:taskId/comments/fields](/docs/entities/task-comments) отвечал сбивающим с толку `400 INVALID_PARAMS` с текстом «taskId and id must be positive integers» (спрашивали про поля — ответ про идентификатор), а [GET /v1/tasks/:taskId/time/fields](/docs/entities/tasks/time) утекал сырой ошибкой Bitrix24 с внутренним PHP-методом и HTML-тегом.

**Стало**

Обе сущности распознают сегмент `fields` и возвращают понятный `400 WRONG_PATH`: у них нет метода `/fields` (состав полей описан в справочнике `/v1/guide`), в тексте перечислены доступные маршруты. Нечисловой или дробный идентификатор в маршрутах по id теперь отклоняется как `400 INVALID_PARAMS` до обращения к Bitrix24 — без утечки внутренней ошибки.

### FIX-0714-19: Лимиты `/v1/ai/credentials*` считаются на портал; `CREDENTIAL_NOT_FOUND` подсказывает решение

**Было**

- Лимиты BYOK-маршрутов (`POST /v1/ai/credentials`, `/:id/test`, `/:id/fetch-models` — 10/мин; `/:id/models` добавление/удаление — 30/мин) фактически считались по IP-адресу: перебор чужих ключей обходился ротацией IP, а арендаторы за одним общим egress-IP делили один бакет. Маршрут `PATCH /:id` (при передаче `credentials` он проверяет ключ у апстрима — тот же оракул, что `/:id/test`) не имел лимита вовсе.
- `404 CREDENTIAL_NOT_FOUND` от `/v1/search` и `/v1/research` содержал только слаг провайдера — без указания, как настроить ключ.

**Стало**

- Лимит считается **на портал**: все API-ключи одного портала делят единый бакет; ротация IP и число ключей на предел не влияют. При превышении — `429` с `Retry-After`. Дополнительно `PATCH /:id` (проверяет ключ у апстрима, как `/:id/test`) раньше вовсе не имел лимита — теперь тоже 10/мин на портал.
- В ответ `CREDENTIAL_NOT_FOUND` добавлено поле `hint` с точным рецептом: `POST /v1/search/credentials {provider, apiKey}`, список провайдеров — `GET /v1/search/providers`.

### FIX-0714-20: PATCH bizproc-шаблонов, роботов и активностей через /v1 применяет изменения

**Было**

`PATCH /v1/bizproc-templates/:id`, `/v1/bizproc-robots/:code` и `/v1/bizproc-activities/:code` с полями метаданных (`name`, `description`, `autoExecute`) возвращали `422 BITRIX_ERROR "No fields to update."` — обновить сущность было нельзя (то же и в пакетных запросах). Поле `autoExecute`, переданное числом, дополнительно отвергалось как `Incorrect field AUTO_EXECUTE!`.

**Стало**

Поля корректно применяются (в том числе `autoExecute`, переданное числом); эндпоинт подтверждает успех и возвращает `id` обновлённой сущности. Работают и одиночный `PATCH`, и пакетные запросы. Создание (`POST`) не изменилось.

### FIX-0714-21: Транскрипция: проверка кошелька до вызова распознавания

**Было**

`POST /v1/audio/transcriptions` (и `/v1/ai/audio/transcriptions`) не проверял состояние кошелька перед вызовом: PREPAY-счёт, ушедший за овердрафт (но ещё не замороженный фоновой проверкой), всё равно запускал распознавание и уводил баланс глубже в минус. Чат и эмбеддинги отклоняли такой вызов заранее; полностью замороженный счёт и раньше блокировался глобально (`ACCOUNT_FROZEN`).

**Стало**

Как у чата и эмбеддингов: денежная проверка выполняется **до** обращения к Whisper. Превышенный овердрафт → `402 insufficient_balance`, распознавание не запускается. BYOK-ключи (USER-scope) бесплатны — для них проверки нет и поведение не меняется.

### FIX-0714-22: удаление приложения больше не блокируется галактическим хостом на его ключе

**Было**

`DELETE /v1/apps/:id` возвращал `409 APP_HAS_ACTIVE_SERVERS`, если на ключе приложения оказывался галактический хост (общая инфраструктура портала). Снять хост через удаление приложения было нельзя, а объяснения в ответе не было.

**Стало**

Галактический хост исключён из проверки блокирующих серверов: он управляется на уровне портала, а не ключа, поэтому не должен мешать удалению приложения. Отдельные серверы приложения (в том числе контейнеры приложений) по-прежнему блокируют удаление с `409 APP_HAS_ACTIVE_SERVERS` — их сначала нужно перепривязать к другому ключу.

### FIX-0714-23: OpenAPI: тело per-entity батч-эндпоинтов теперь описано верно (action + items/ids/calls)

**Было**

Спека (`GET /v1/openapi.json`) описывала тело каждого пер-сущностного батча как `{create:[], update:[], delete:[]}`. Рантайм же (общий обработчик батчей) требует `{action, items|ids|calls}` и отвечает `400 INVALID_BATCH_ACTION` на документированную форму. Клиент, сгенерированный из спеки (codegen / AI-агент), получал **100% отказ batch-write** на всех ~45 пер-сущностных батч-эндпоинтах. Сама фича работает — врала только спека (глобальный `POST /v1/batch`, `/v1/tasks/{taskId}/comments/batch` и `/v1/guide` уже описывали правильную форму).

**Стало**

Генератор спеки выдаёт правильную форму: один `action` (`create`/`update`/`delete`/`list`/`get`/`fields`); `create`/`update` шлют `items`, `delete` — `ids`, чтения — `calls`. Совпадает с рантаймом и с глобальным `/v1/batch`.

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

Если вы генерировали клиент из `openapi.json` и batch-write падал `INVALID_BATCH_ACTION` — перегенерируйте: тело теперь `{action:"create", items:[…]}` вместо `{create:[…]}`. Handwritten-клиенты, уже славшие `{action,…}`, не затронуты.

### FIX-0714-24: Валюты: fullName, формат и число знаков теперь сохраняются при плоской записи

**Было**

`POST`/`PATCH /v1/currencies` с плоскими `fullName`, `formatString`, `decimals`, `decPoint`, `thousandsSep` возвращал успех, но значения **молча терялись** — Битрикс24 хранит их в структуре локализации по языкам (`LANG`), а API слал их плоско. Задокументированный обходной путь «передавайте сырой `LANG`» тоже не работал: `LANG` — поле только для чтения, запрос отклонялся.

**Стало**

API упаковывает плоские локализуемые поля в локализацию **вашего** языка (языка API-ключа) перед вызовом Битрикс24, поэтому плоская запись сохраняется и читается обратно (`POST {fullName:"…",decimals:3}` → `GET` вернёт их). Работает на всех путях записи: одиночном, `POST /v1/currencies/batch` и глобальном `POST /v1/batch`. Сырой `LANG` в теле по-прежнему отклоняется как read-only. Разные значения сразу для нескольких языков через API пока не задаются. Язык записи определяется локалью API-ключа (ru или en) и может не совпадать с языком отображения валюты на портале — на порталах с иной локалью (de/pl/ua…) правка ляжет под en.

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

Если вы обходили баг сырым `LANG` (и упирались в `400 READONLY_FIELD`) — уберите его и шлите плоские поля. Уже работавшие плоские запросы теперь ещё и сохраняют значения.

### FIX-0714-25: Aggregate уважает обязательные фильтры; infra-валидация не течёт сырым Zod

**Было**

- `POST /v1/<entity>/aggregate` с сущностью, требующей фильтр (напр. `catalog-products` требует `iblockId`), пропускал пустой запрос в Битрикс24 и возвращал сырой `422`, тогда как GET-list/search на том же условии отдают чистый `400`.
- `POST /v1/infra/servers/:id/{deploy,exec,upload,logs}` при ошибке валидации тела клал в `error.message` многострочный JSON-сериализованный массив Zod-issues (сырой отпечаток валидатора).

**Стало**

- Aggregate проверяет обязательные фильтры/параметры до вызова Битрикс24: нет обязательного фильтра — `400 MISSING_REQUIRED_FILTER` (напр. `catalog-products`→`iblockId`); нет обязательного list-параметра — `400 MISSING_REQUIRED_PARAMS` (напр. `calendar-events`→`type`,`ownerId`; `humanresources-nodes`→`type`). Как у list/search.
- Infra-валидация форматирует ошибку компактно (`поле: сообщение; …`), как соседний `infra.ts`. Код (`VALIDATION_ERROR`) и статус `400` не изменились.

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

Если вы ловили сырой `422` от aggregate без фильтра — теперь придёт `400 MISSING_REQUIRED_FILTER`. Если парсили infra-`error.message` как JSON — теперь это плоская строка `поле: сообщение`.

### FIX-0714-26: Batch: работает per-entity batch для items и создание папок через batch

**Было**

- `POST /v1/items/{entityTypeId}/batch` возвращал `404` — per-entity batch-роут для сущностей с динамическим параметром (`items`, `categories`) монтировался по адресу без сегмента параметра (`/v1/items/batch`), поэтому документированный путь не находился, а `entityTypeId` не доходил до команды Битрикс24. Обходной путь — глобальный `POST /v1/batch` — работал.
- `POST /v1/folders/batch` с `action: "create"` падал на каждом элементе с `ERROR_ARGUMENT`: batch-создание слало `fields[...]`, тогда как `disk.folder.addsubfolder` ожидает родительскую папку верхним параметром `id`, а остальные поля — под `data[...]`.

**Стало**

- Per-entity batch-роут для `items`/`categories` монтируется с сегментом `:{entityTypeId}` и прокидывает валидированный `entityTypeId` (положительное целое; id выделенных API вроде `deals=2` отклоняются с подсказкой — как в одиночных роутах) в каждую команду — для **всех** действий: `create`/`update`/`delete` и read-действий `list`/`get`/`fields`.
- Batch-создание папок повторяет форму одиночного роута: `id=<родитель>&data[...]`. Пропущенный `parentId` — чистый per-item `400` до вызова Битрикс24.

**Затронутые эндпоинты:**

- [`POST /v1/items/{entityTypeId}/batch`](/docs/entities/items/create)
- [`POST /v1/folders/batch`](/docs/entities/folders/create)

### NEW-0714-27: восстановление зависшего exec-канала сервера

Новый эндпоинт [POST /v1/infra/servers/:id/unstick](/docs/infra/deploy/exec) принудительно освобождает залипший канал команд Black Hole-сервера, когда деплой или exec продолжает отвечать `EXEC_BUSY` («Another command is running») даже после `DELETE /v1/infra/servers/:id/lock`. Эндпоинт снимает блокировку на стороне платформы и разрывает туннель агента — при переподключении агент завершает зависшую команду и освобождает свой мьютекс. Перезагрузка сервера не выполняется.

Если в момент вызова на сервере ещё выполняется легитимная операция (деплой, exec, харден), эндпоинт по умолчанию отвечает `409 OPERATION_IN_PROGRESS` и НЕ трогает её — расклинивать нужно только зависший канал. Повторите с `?force=true`, если уверены, что канал команд действительно завис.

Ответ: `{ success: true, data: { backendLockReleased, agentBounced, reconnected } }`. Коды ошибок: `404 SERVER_NOT_FOUND`, `409 CONFLICT` (восстановление уже идёт), `409 OPERATION_IN_PROGRESS` (на сервере выполняется операция — повторите с `?force=true`), `409 GALAXY_UNSTICK_UNSUPPORTED` (для galaxy-хостов и galaxy-приложений не поддерживается), `502 GATEWAY_ERROR`. Ошибка `/exec` (`EXEC_BUSY`) и провал `/deploy` (код `DEPLOY_FAILED`, сообщение «Another command is running») теперь несут подсказку `hint` с этим эндпоинтом.

### NEW-0714-28: описание сервера в `PATCH /v1/infra/servers/:id`

**Было**

`PATCH /v1/infra/servers/:id` принимал только `displayName`. Поле `description` в контракте отсутствовало, а `GET`-ответы его не отдавали.

**Стало**

`PATCH /v1/infra/servers/:id` принимает опциональное поле `description` (строка, до 500 символов; пустая строка или `null` очищает описание; отсутствие поля оставляет текущее значение без изменений). Значение синхронизируется с карточкой приложения в каталоге. Поле `description` теперь возвращается в `GET /v1/infra/servers`, `GET /v1/infra/servers/:id` и в ответе `PATCH`. Старые запросы без `description` продолжают работать без изменений.

### FIX-0714-29: деплой galaxy-приложения при обрыве связи во время сборки возвращает честную ошибку, а не ложный успех

**Было**

Если соединение с хостом обрывалось прямо во время сборки galaxy-приложения (частое явление под нагрузкой тяжёлой сборки), `POST /v1/infra/servers/:id/deploy` мог вернуть `200` со статусом `running` и пометкой `[recovered]` в `buildLog`, хотя новая версия так и не собралась и не поднялась — на хосте продолжал работать прежний контейнер. Повторный деплой упирался в тот же обрыв и снова рапортовал ложный успех.

**Стало**

Деплой считается восстановленным, только если он полностью завершился: под именем приложения запущен контейнер именно этой попытки, и деплой прошёл до конца. Если связь оборвалась во время сборки — или в любой момент до завершения деплоя — и новая версия не поднялась полностью, эндпоинт возвращает retryable-ошибку `502` с кодом `GALAXY_HOST_UNREACHABLE` и подсказкой «повторите тот же деплой, не удаляйте сервер» — вместо ложного `200`. Только если деплой завершился полностью и потерялся лишь финальный ответ, восстановление в `200` работает как прежде.

### BC-0714-30: переименование приложения в каталоге доходит до Битрикс24, publish потерял menuTitle

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

**Было**

[PATCH /v1/apps/:id](/docs/apps/update) с полем `title` у приложения, добавленного в каталог, возвращал `200`, но не менял ничего из того, что видит пользователь: карточка каталога и привязки мест встраивания на портале (пункт левого меню, вкладки CRM) оставались со старым именем. Отказа не было никогда — вызов всегда успешен.

У [POST /v1/apps/:id/publish](/docs/apps/publish) тело не проверялось, а заголовок места встраивания задавался отдельным полем `menuTitle`.

**Стало**

Для приложения в каталоге `title` — это одна операция над отображаемым именем: имя синхронизируется в карточку каталога (вместе с `title` пишется `catalogTitle`) и перепривязывается в места встраивания на портале. Поэтому вызов, который раньше всегда отдавал `200`, теперь может честно отказать:

- `400 NO_USER_TOKEN` — приложение не авторизовано на портале, перепривязать места встраивания нечем;
- `400 TITLE_TOO_LONG_FOR_CATALOG` — имя приложения в каталоге ограничено 100 символами, тогда как `title` допускает 255;
- `502 BITRIX_PARTIAL_REBIND` — Битрикс24 отклонил привязку. Имя в этом случае не записывается, повтор того же запроса чинит состояние.

Тело `POST /v1/apps/:id/publish` теперь валидируется, а параметр `menuTitle` удалён: заголовок места встраивания всегда равен отображаемому имени приложения. Пустое тело по-прежнему работает — публикация берёт значения из записи приложения.

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

- Убрать `menuTitle` из тела публикации: поле игнорируется, имя пункта меню задаётся через `title` и `catalogTitle`.
- Держать имя приложения в каталоге в пределах 100 символов.
- Обработать отказы при переименовании: на `NO_USER_TOKEN` авторизовать приложение на портале, на `BITRIX_PARTIAL_REBIND` повторить запрос.
- Учесть побочный эффект: переименование через `title` теперь пишет и `catalogTitle` — у приложения в каталоге эти поля держатся синхронными.

### NEW-0714-31: GET /v1/tasks/:taskId/comments/fields — схема полей комментариев задачи

У комментариев к задаче появился метод `/fields`, как у остальных сущностей: `GET /v1/tasks/:taskId/comments/fields` возвращает статическую схему из 5 полей (`id`, `taskId`, `authorId`, `message`, `createdAt`) с типом, признаком «только для чтения», названием и описанием по-русски. Метод не обращается к Битрикс24. Раньше этот путь возвращал `400 WRONG_PATH` — состав полей был доступен только из статических доков.

### FIX-0714-32: пер-сущностный batch с action list теперь применяет filter

**Было**

`POST /v1/{entity}/batch` с `action: "list"` игнорировал `filter`: имена полей не приводились к именам Битрикс24 (например `statusId` у лидов не превращался в `stageId`), а операторы `$gt` / `$contains` / `$in` и другие не работали. Вызов возвращал `200` со всей таблицей — тихий отказ с неверными данными. Глобальный `POST /v1/batch` и `POST /v1/{entity}/search` при этом фильтровали корректно.

**Стало**

Пер-сущностный batch пропускает `filter` через тот же транслятор, что `search` и глобальный `/v1/batch`. Псевдонимы полей и операторы (`$gt`, `$gte`, `$lt`, `$lte`, `$ne`, `$contains`, `$in`, `$nin`, префиксные `>=`, `<=`, `!` и т.д.) применяются. Некорректный `filter` (неизвестное поле у сущности с полной схемой, неподдерживаемый оператор, префиксы `@` / `!@`, логические токены `$or` / `$and`) теперь возвращает `400` с указанием индекса вызова, а не молча всю выборку — так же, как на одиночных эндпоинтах.

### FIX-0714-33: авто-пагинация больше не дублирует записи на границах страниц

**Было**

Для методов Битрикс24 без поддержки сортировки (например список объектов хранилища) запись на границе страниц могла сместиться между запросами соседних страниц и попасть в обе — при `limit > 50` в выдаче появлялся дубль, занимавший слот, и клиент обрабатывал одну и ту же запись дважды.

**Стало**

После склейки всех страниц выдача дедуплицируется по `id` (сохраняется первое вхождение). Для методов со стабильной сортировкой ничего не меняется (дублей нет — операция вхолостую).

### FIX-0714-34: список полей шаблона реквизитов больше не пустеет; создание не возвращает чужую запись

**Было**

`GET /v1/requisite-presets/:presetId/fields` мог вернуть `200` с пустым `data: []`, хотя в шаблоне есть поля: метод Битрикс24 отдаёт `result` то массивом `[{…}]`, то объектом-картой `{"0":{…},"1":{…}}`, а обработчик принимал только массив. При создании поля (`POST …/fields`) ответ мог содержать ЧУЖУЮ существующую запись — эхо созданной строки читалось по id из ответа `add`, а на некоторых порталах чтение по этому id возвращало другое поле.

**Стало**

Список нормализует обе формы ответа Битрикс24 (массив и объект-карту) — поля больше не теряются. Эхо созданной записи возвращается только если её `fieldName` совпадает с тем, что создавали; при любом расхождении ответ содержит `{ id }` (сам объект не подставляется), чтобы клиент не получил постороннюю строку.

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

Идентификатор поля шаблона (`id`) — это позиционный идентификатор Битрикс24: он может меняться после операций записи в шаблоне и на части порталов не является устойчивым ключом. Не кэшируйте `id` между изменениями шаблона — перечитывайте список полей перед `get`/`update`/`delete` по конкретному полю.

### FIX-0714-35: привязка плейсмента для ранее созданных приложений больше не отклоняется по обработчику

**Было**

[POST /v1/placements/bind](/docs/keys-auth) мог вернуть `400` с кодом `PLATFORM_HANDLER_UNRESOLVABLE` для приложения, созданного до перехода на единый платформенный обработчик (у такого приложения в качестве обработчика сохранился его собственный технический адрес). Привязка отклонялась даже тогда, когда платформенный обработчик `/v1/bitrix-handler` был доступен, — приложение нельзя было опубликовать заново через API.

**Стало**

Привязка проходит: обработчик плейсмента регистрируется на платформенный `/v1/bitrix-handler`, а в ответе появляются `handlerRewritten: true` и `requestedHandler` с исходным значением. Код `PLATFORM_HANDLER_UNRESOLVABLE` теперь возвращается только когда платформенный обработчик действительно недоступен. [POST /v1/placements/unbind](/docs/keys-auth) снимает такой плейсмент по тому же адресу.

## 2026-07-13

### FIX-0713-1: Платформенные scope ключа OAuth-приложения синхронизируются при создании и правке

**Было**

Ключ, выписанный вместе с OAuth-приложением через [POST /v1/apps](/docs/apps), не получал платформенные scope (`vibe:infra`, `vibe:ai`, `vibe:search`, `vibe:storage`) — в отличие от ключа, созданного в кабинете. Из-за этого [POST /v1/infra/servers](/docs/infra/servers) под таким ключом отвечал 403 `INFRA_SCOPE_REQUIRED`. Добавление `vibe:infra` в scope приложения через [PATCH /v1/apps/:id](/docs/apps) меняло только приложение, но не парный ключ — эффекта на доступ не было.

**Стало**

Парный ключ при создании через `POST /v1/apps` получает те же платформенные scope по умолчанию, что и ключ из кабинета. Изменение `vibe:*`-scope через `PATCH /v1/apps/:id` (добавление И удаление) теперь синхронизируется в активные ключи приложения. Ключ в режиме «только чтение» (READONLY) при этом отклоняется с 403 `WRITE_BLOCKED_READONLY_KEY` на трёх write-операциях: создание сервера (`POST /v1/infra/servers`), изменение scope приложения (`PATCH /v1/apps/:id`) и выписка READWRITE-ключа (`POST /v1/apps` с `mode: "READWRITE"`).

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

Приложения, созданные через API, теперь могут управлять инфраструктурой без пересоздания. Ключ, у которого приложение уже объявляет `vibe:infra`, но сам ключ его лишён (старое расхождение), чинится одной правкой: снять `vibe:infra` из scope приложения и вернуть обратно через `PATCH /v1/apps/:id` — вторая правка синхронизирует ключ; либо пересоздать приложение. READONLY-ключ по-прежнему может создавать READONLY-приложение (`mode: "READONLY"`).

### FIX-0713-2: product-sections: неподдерживаемые фильтры возвращают 400, а не всю таблицу

**Было**

Операторы (`>`, `<`, `!`, `%`, `$ne`, `$contains`, `$nin`) и поля вне точного равенства (`sort`, неизвестные) в фильтре [GET /v1/product-sections](/docs/entities/product-sections/list) и [POST /v1/product-sections/search](/docs/entities/product-sections/search) молча игнорировались — возвращался код 200 и весь список без фильтра.

**Стало**

Такие фильтры отклоняются с `400 UNSUPPORTED_FILTER`. Фильтруйте точным равенством или `$in` по `id`, `name`, `xmlId`, `code`, `catalogId`, `sectionId`. Сортировка (`order`/`sort`) не изменилась.

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

Вызовы с операторами или `filter[sort]`, ранее возвращавшие 200 с неотфильтрованными данными, теперь возвращают `400` — переключитесь на точное равенство или `$in`.

### FIX-0713-3: причина неудачного первого провижининга сервера теперь видна

**Было**

Если сервер не поднимался при первом создании (вытесняемый тариф вытеснен, нет свободной ёмкости), он молча оказывался в статусе `sleeping` без объяснения причины. Клиент, опрашивающий [GET /v1/infra/servers/:id](/docs/infra/servers/get), видел `sleeping` и не понимал, почему деплой заблокирован.

**Стало**

Такой сервер теперь переходит в `status: "error"` с заполненным `provisionError` (человекочитаемая причина) и новым полем `provisionErrorCode` — машиночитаемой категорией сбоя (`PREEMPTIBLE_EVICTION` / `PROVISION_TIMEOUT` / `NO_CAPACITY` / `GENERIC`). Поле `provisionErrorCode` добавлено в ответы [GET /v1/infra/servers/:id](/docs/infra/servers/get) и [GET /v1/infra/servers](/docs/infra/servers/list) рядом с `provisionError` (аддитивно, `null`, если ошибок не было).

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

Ничего менять не нужно: `error` — уже существующий статус. Восстановление такого сервера — через `POST /v1/infra/servers/:id/start` или `/repair` (не `/wake`: на статусе `error` он вернёт `422`; поле `availableActions` в ответе подсказывает доступное действие).

### NEW-0713-4: POST и PATCH /v1/tasks/:taskId/time принимают createdDate

Необязательное поле `createdDate` теперь передаётся в [POST /v1/tasks/:taskId/time](/docs/entities/tasks/time/create) и [PATCH /v1/tasks/:taskId/time/:itemId](/docs/entities/tasks/time/update) — запись учёта времени ложится на указанную дату, а не на текущий момент (сценарий понедельничного добивания трека за прошлую неделю). Принимаются ISO 8601 со смещением, ISO без смещения и `YYYY-MM-DD` — значение уходит в `CREATED_DATE` без изменений. Если поле не передано, поведение прежнее — дата равна моменту создания.

### FIX-0713-5: meta.hasMore перестаёт зависать в true при filter + offset

**Было**

При пагинации списка с фильтром (например [GET /v1/deals](/docs/entities/deals/list) с `filter` и `offset`) `meta.hasMore` оставался `true` на любом offset — даже далеко за пределами `meta.total`. Цикл `while (meta.hasMore) { offset += limit }` уходил в бесконечность.

**Стало**

Когда Bitrix24 игнорирует смещение за пределами отфильтрованного набора и отдаёт его целиком, `meta.hasMore` считается по окну запроса (`offset + limit < meta.total`), а не выставляется в `true` безусловно. При `offset=0` ответ прежний; после того как окно перекрыло `total`, `hasMore` становится `false`. Затрагивает список и `POST /search` для всех сущностей.

### FIX-0713-6: GET /v1/lists и /v1/lists/:iblockId/elements учитывают offset

**Было**

[GET /v1/lists/:iblockId/elements](/docs/lists/elements) и [GET /v1/lists](/docs/lists/lists) молча игнорировали `offset` — при `?limit=50&offset=50` возвращалась та же первая страница, и клиент, листающий через `offset`, никогда не доходил до строк 51 и дальше.

**Стало**

`offset` мапится в нативный для этих методов параметр `start`, так что пагинация через `offset` работает. Явный `start` сохраняет приоритет, если переданы оба.

### NEW-0713-7: Флаг preserveEnv в деплое сохраняет .env при cleanDeploy

Тело `POST /v1/infra/servers/:id/deploy` приняло необязательный булев `preserveEnv` (по умолчанию `false`). Когда `cleanDeploy: true` (очищает каталог приложения вместе с файлом `.env`), а `preserveEnv: true` — существующий `.env` считывается до очистки и восстанавливается после неё, если в этом же запросе не передан `env` (переданный `env` имеет приоритет). Без флага повторный деплой с `cleanDeploy` мог поднять приложение без переменных окружения — на порту по умолчанию.

### NEW-0713-8: у неудачной сборки galaxy-приложения появилась подсказка `buildHint`

**Было**

Провал сборки galaxy-приложения возвращал только `GALAXY_APP_BUILD_FAILED`
(и `GALAXY_APP_START_FAILED`) c «хвостом» лога в `buildLog` — разобрать причину
можно было лишь вручную. `GET /v1/infra/servers/:id` по ERROR-приложению отдавал
короткий `provisionError`, но без готовой рекомендации.

**Стало**

Ответ теперь несёт разобранную причину. В теле ошибки `POST /v1/infra/servers/:id/deploy`
(502) при классифицируемом провале дополнительно приходят `error.category`
(машинная категория, например `MODULE_NOT_FOUND`, `INSTALL_AUTH`, `RESOURCE`) и
`error.buildHint` — локализованная строка-рекомендация с конкретным следующим шагом.
`GET /v1/infra/servers/:id` по ERROR-приложению добавляет то же поле `data.buildHint`.
Поля аддитивные: если причина не распознана, они отсутствуют (`buildHint` — `null`),
а `provisionError`/`buildLog` продолжают приходить как раньше — старые запросы
не меняются.

### FIX-0713-9: одновременные одинаковые сохранения исходников больше не создают дубль-версию

**Было**

Два одновременных сохранения одинаковых байтов на один сервер (`POST /v1/infra/servers/:id/sources`, а также автосохранение при деплое) при редком стечении таймингов создавали **две** байт-идентичные версии вместо одной — дедупликация по содержимому была best-effort.

**Стало**

Дедупликация детерминированная: одинаковые байты, отправленные одновременно, всегда сходятся на одну версию. На `POST /v1/infra/servers/:id/sources` оба ответа возвращают один и тот же `versionId`, проигравший запрос получает `deduplicated: true`; автосохранение при деплое сходится на ту же единственную версию (формат ответа деплоя не менялся).

## 2026-07-12

### NEW-0712-1: Ответ при исчерпании AI-квоты подсказывает путь к пополнению

Ошибка `402 ai_quota_exhausted` с `reason: wallet_empty` теперь дополнительно возвращает поля `hint` и `topupUrl`. `hint` — короткая подсказка на английском: месячная AI-квота и баланс портала исчерпаны, и администратор портала может пополнить баланс, чтобы возобновить работу. `topupUrl` — ссылка, которую администратор открывает для пополнения. Поля аддитивны: прежние поля ответа (`reason`, `resetAt`) не изменились, а ветки `reason: breaker` и `reason: wallet_off` их не несут. Так агент-клиент может передать человеку путь к пополнению. Ошибку возвращают вызовы моделей, см. [POST /v1/chat/completions](/docs/ai/chat/completions).

### FIX-0712-2: `sleep-now` на сервере с давно прошедшим расчётным пробуждением теперь честно усыпляет

**Было**

Для сервера с включённым расписанием пробуждения вызов `POST /v1/infra/servers/:id/sleep-now` мог навсегда возвращать `{ data: { slept: false, reason: "WAKE_IMMINENT" } }`, если денормализованное «следующее пробуждение» осталось в далёком прошлом (сервер разбудили вне планировщика, и штамп не пересчитался). Сервер никогда не засыпал и держал тариф включённым круглосуточно.

**Стало**

Штамп старше динамического окна («поле для манёвра» = margin + типовой lead) считается протухшим, а не «неминуемым»: `sleep-now` честно усыпляет сервер и отвечает `{ success: true }`, после чего планировщик тихо перекатывает якорь к будущему окну без пробуждения. `WAKE_IMMINENT` остаётся штатным ответом только для действительно близкого пробуждения.

## 2026-07-11

### FIX-0711-1: галактики: ошибка GALAXY_HOST_UNREACHABLE стала действенной — подсказка hint и точная причина в provisionError

**Было**

Когда хост галактики был временно недоступен, [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) отвечал голым 502 с кодом `GALAXY_HOST_UNREACHABLE`, а слот, созданный одним вызовом [POST /v1/infra/servers](/docs/infra/servers/create) с `source`, переходил в статус ошибки с текстом «Deploy failed unexpectedly — please retry; details are in the server logs». Ни причина, ни путь восстановления не сообщались — клиенты удаляли слот и создавали новый, что не помогает: новый слот попадает на тот же хост.

**Стало**

Ответ 502 `GALAXY_HOST_UNREACHABLE` — при деплое, exec-команде и удалении ([DELETE /v1/infra/servers/:id](/docs/infra/servers/delete)) — несёт структурную подсказку `error.hint`: состояние временное, слот и его данные целы, нужно повторить тот же запрос через 1–2 минуты, удалять слот не нужно. На пути «создание с `source`» поле `provisionError` теперь содержит настоящую причину («Galaxy host … became unreachable during build …») и тот же совет повторить деплой в существующий слот. Дополнительно: когда несколько серверов работают под одним OAuth-приложением, сохранённая версия исходников больше не теряется из-за конфликта нумерации версий — ни при автосохранении на деплое, ни при явном сохранении через [POST /v1/infra/servers/:id/sources](/docs/source-storage).

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

Изменение аддитивное: коды и статусы ответов не менялись, добавилось поле `error.hint` и уточнился текст `provisionError`. Обновлять клиентов не нужно. AI-агентам стоит читать `hint.recovery` — там прямо сказано, что делать.

### NEW-0711-2: отдельный код ошибки, когда на портале не установлен модуль Vibecode Connector

Выписка ключа приложения через модуль-коннектор ([POST /v1/apps](/docs/apps)) теперь при отсутствии на портале модуля `vibecodeconnector` возвращает `409` с кодом `CONNECTOR_MODULE_NOT_INSTALLED` и понятным сообщением «установите модуль», вместо прежнего непрозрачного `502 CONNECTOR_APP_INSTALL_FAILED`. Это законное, постоянное состояние (особенно для коробки), а не временный сбой — повторять запрос бессмысленно, нужно установить модуль на портале. Остальные коды выписки не изменились.

### FIX-0711-3: pacing в GET /v1/ai/quota теперь может заполняться платформой по умолчанию (поэтапная раскатка)

Платформа теперь умеет включать пейсинг (равномерное расходование AI-квоты) по умолчанию для портала — без действий администратора. Раскатка поэтапная (пилотные порталы → все порталы): пока портал не попал под платформенный default-on, поле `data.pacing` в ответе [GET /v1/ai/quota](/docs/ai/consumption/quota) остаётся `null`, как и раньше. Когда портал под default-on: `mode: "ignore"` — информационный режим, `active: false` — лимит окна не отклоняет запросы (отказ `429 ai_pacing_limited` невозможен). Форма ответа не изменилась; интеграторам, уже обрабатывающим `data.pacing` как опциональное поле, ничего менять не нужно.

## 2026-07-10

### NEW-0710-1: управление окнами пробуждения по расписанию (wake-schedules)

Новый CRUD-контракт на серверах Black Hole: `GET`/`POST /v1/infra/servers/:id/wake-schedules`, `PATCH`/`DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId`. Позволяет объявить одно или несколько повторяющихся окон пробуждения (`cronExpr` + обязательная IANA-таймзона `timezone`, необязательные `label`/`lead`/`enabled`) — платформа будит спящий сервер к нужному моменту, дальше запуск задачи делает собственный cron внутри VM.

Раскатывается постепенно и пока не работает на всех порталах — до включения на конкретном портале запрос отвечает `403` с кодом `WAKE_SCHEDULE_DISABLED`. Доступно только для серверов в режиме BLACKHOLE (иначе `400 BLACKHOLE_ONLY`) и пока не поддержано для галактик (`400 GALAXY_NOT_SUPPORTED` на хосте и вложенном приложении — появится позже). Минимальный интервал между срабатываниями и лимит окон на сервер (50) заданы платформой; нарушение отвечает `400 CADENCE_TOO_LOW` и `403 WAKE_SCHEDULE_LIMIT` соответственно. Ответ создания/обновления дополнительно несёт поле `tzWarning` — предупреждение о том, что таймзона в VM могла разойтись с таймзоной окна, если сервер не переразвёртывался. **`PATCH` заменяет окно целиком (PUT-семантика): не переданные необязательные поля сбрасываются к значениям по умолчанию — `enabled`→`true`, `label`/`lead`→пусто.** Передавайте полный объект окна при обновлении.

Затронутые эндпоинты: `GET|POST /v1/infra/servers/:id/wake-schedules`, `PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId`.

### NEW-0710-2: машиночитаемый код tz-предупреждения в ответах wake-schedules (`tzWarningCode`)

Ответ создания/обновления окна пробуждения (`POST`/`PATCH /v1/infra/servers/:id/wake-schedules[/:scheduleId]`) теперь дополнительно несёт поле `tzWarningCode` рядом с существующим текстовым `tzWarning` — `"SINGLE_ZONE"` / `"MULTI_ZONE"` / `null` (когда после мутации у сервера не осталось включённых окон). Значение — машиночитаемый эквивалент того же предупреждения, чтобы клиент мог локализовать текст сам вместо отображения английской строки `tzWarning` как есть. Поле аддитивное, `tzWarning` не меняется и остаётся для обратной совместимости.

Затронутые эндпоинты: `POST|PATCH /v1/infra/servers/:id/wake-schedules[/:scheduleId]`.

### FIX-0710-3: wake-schedules: расписание запрещено на серверах «Всегда онлайн»

`POST`/`PATCH /v1/infra/servers/:id/wake-schedules` теперь отклоняют создание или обновление окна пробуждения на сервере в режиме «Всегда онлайн» (24/7) — ответ `400` с кодом `ALWAYS_ON_CONFLICT`. Такой сервер работает на невытесняемом тарифе и не уходит в авто-сон, поэтому расписание пробуждения тихо нарушило бы оплаченную гарантию постоянной доступности. Обычные засыпающие серверы Black Hole и вытесняемые агенты/боты не затронуты. Отключите «Всегда онлайн», чтобы объявлять окна пробуждения.

Затронутые эндпоинты: `POST /v1/infra/servers/:id/wake-schedules`, `PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId`.

### FIX-0710-4: wake-schedules теперь можно объявлять на вложенных приложениях galaxy

**Было**

`POST`/`PATCH /v1/infra/servers/:id/wake-schedules` отвечал `400 GALAXY_NOT_SUPPORTED` для любого сервера семейства galaxy — как для самого хоста, так и для вложенных приложений (`kind=GALAXY_APP`).

**Стало**

Вложенные приложения galaxy (`kind=GALAXY_APP`) теперь принимаются — окно пробуждения можно объявить на конкретном приложении, и платформа разбудит его (и при необходимости — хост) к нужному моменту. Сам хост galaxy по-прежнему отвечает `400 GALAXY_NOT_SUPPORTED`: расписание объявляется на приложениях, а не на хосте.

Затронутые эндпоинты: `POST|GET /v1/infra/servers/:id/wake-schedules`, `PATCH|DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId`.

### FIX-0710-5: sleep-now откладывает засыпание, когда пробуждение по расписанию близко

**Было**

[POST /v1/infra/servers/:id/sleep-now](/docs/infra/lifecycle/sleep-now) всегда усыплял сервер немедленно, даже если ближайшее пробуждение по расписанию наступало через минуту — сервер сразу же просыпался обратно.

**Стало**

Если у сервера есть включённое расписание пробуждения и ближайшее пробуждение наступит в пределах защитного окна, вызов не усыпляет сервер и отвечает `200` с телом `{ "success": true, "data": { "slept": false, "reason": "WAKE_IMMINENT" } }`. В остальных случаях сервер усыпляется, а планировщик разбудит его в следующее окно.

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

Блок `data` с полем `slept: false` приходит только при отказе усыпить — проверяйте его наличие, если полагаетесь на то, что после вызова сервер обязательно засыпает. При успешном усыплении ответ состоит из одного `success: true`. Старые вызовы серверов без расписания работают без изменений.

### FIX-0710-6: не-стриминговые chat/completions и embeddings больше не обрываются на 360 секундах

**Было**

Не-стриминговый (`stream:false`) запрос к `POST /v1/chat/completions` (и `POST /v1/embeddings`), генерация которого длилась дольше ~2 минут, стабильно обрывался на ~360 секундах с `{"code":"ai_provider_unavailable","message":"This operation was aborted"}` — независимо от таймаута клиента. При этом на обречённую генерацию тратилось тройное количество вычислений.

**Стало**

Такой запрос обрабатывается в рамках единого бюджета ~850 секунд (одна попытка на весь бюджет, без утроения нагрузки). Если генерация всё же не укладывается в бюджет, возвращается осмысленный `503` с кодом `ai_provider_timeout`, локализованным `userMessage` и подсказкой (уменьшить объём запроса либо использовать потоковый режим `stream:true`) — без заголовка `Retry-After` (таймаут не транзиентный). Обрыв соединения клиентом теперь немедленно отменяет генерацию на стороне провайдера. Стриминговый режим (`stream:true`) этим лимитом не затрагивался.

### FIX-0710-7: requisite-presets/:presetId/fields и requisite-links: сортировка, фильтрация и валидация параметров списка

**Было**

[GET /v1/requisite-presets/:presetId/fields](/docs/entities/requisite-presets) игнорировал `sort`/`order` и любые `filter[...]` — всегда возвращал полный список в порядке Битрикс24. [GET /v1/requisite-links](/docs/entities/requisite-links) и [POST /v1/requisite-links/search](/docs/entities/requisite-links) принимали только простое равенство, а операторы (`$gte`, `>` и т.п.), `sort`/`order` и неизвестные поля молча пропускались — в результате приходила вся таблица.

**Стало**

Оба списка честно применяют `sort`/`order` (в том числе форму `order[поле]=asc|desc`) и `filter`. У requisite-links работают операторы сравнения (`$gt`/`$gte`/`$lt`/`$lte`, `$in`/`$nin`, префиксы `>=`/`>`). Неизвестное поле фильтра или сортировки теперь возвращает `400` (`UNKNOWN_FILTER_FIELD` / `UNKNOWN_SORT_FIELD`), логический оператор верхнего уровня (`$or`/`$and`) — `400 INVALID_FILTER_OPERATOR`, а фильтр по `entityId` без `entityTypeId` — `400 MISSING_ENTITY_TYPE_ID` вместо сырого «Access denied».

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

Запросы по документированным полям продолжают работать и теперь действительно сортируются/фильтруются. Если раньше вы полагались на молчаливое игнорирование неизвестного параметра, теперь он вернёт `400` — уберите опечатку или используйте поле из ответа.

### FIX-0710-8: Сортировка банковских реквизитов по id учитывает направление

**Было**

Запрос списка банковских реквизитов ([GET /v1/bank-details](/docs/entities/bank-details/list)) с сортировкой по `id` по убыванию (`?sort=-id`) возвращал записи по возрастанию — направление сортировки молча игнорировалось.

**Стало**

`?sort=-id` (и `?sort=id`) сортирует по идентификатору в запрошенном направлении.

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

Изменений в коде не требуется — запросы, полагавшиеся на сортировку по `id`, теперь возвращают ожидаемый порядок.

### NEW-0710-9: GET /v1/quotes/fields — объявлены ~26 полей предложения с человеческими названиями

Схема сущности «предложения» (quotes) пополнена ~26 полями, которые Битрикс24 возвращал, но которые не были объявлены: `quoteNumber`, `updatedBy`, `lastActivityBy`, `lastActivityTime`, `content`, `terms`, `leadId`, `storageTypeId`, `storageElementIds`, `personTypeId`, `webformId`, `lastCommunicationTime`, `contactIds`, `contacts`, `locationId`, `taxValue`, `actualDate`, `mycompanyId`, `utmSource`/`utmMedium`/`utmCampaign`/`utmContent`/`utmTerm`, `lastCommunicationCallTime`/`lastCommunicationEmailTime`/`lastCommunicationImolTime`/`lastCommunicationWebformTime`. Теперь они видны в [GET /v1/quotes/fields](/docs/entities/quotes/fields) с читаемыми названиями (вместо служебных `STORAGE_TYPE_ID`/`UTM_SOURCE`), а их значения приводятся к типам (числа, даты в ISO) в ответах list/get. Названиями снабжены также ранее необъявленные `stageId`, `opened`, `closed`.

### FIX-0710-10: PATCH без единого записываемого поля отклоняется явной ошибкой

**Было**

`PATCH /v1/{entity}/:id` с пустым телом — или с телом, в котором нет ни одного распознанного записываемого поля (например из-за опечатки в имени поля) — доходил до Битрикс24, который молча игнорировал такой запрос и отвечал успехом. Обёртка возвращала `200` с неизменённым объектом, и клиент считал, что правка применилась, хотя на деле ничего не менялось — риск тихой рассинхронизации, особенно у AI-агентов. Это касалось всех трёх поверхностей обновления: одиночного `PATCH /v1/{entity}/:id`, пакетного `POST /v1/{entity}/batch` и общего `POST /v1/batch`.

**Стало**

Обновление с пустым телом возвращает `400 EMPTY_UPDATE_BODY` (в пакетных вызовах — ошибку элемента) на всех трёх поверхностях, до обращения к Битрикс24. Для `/v1/catalog-products/:id` добавлена строгая проверка: `PATCH`, в котором нет ни одного распознанного записываемого поля, возвращает `400 NO_RECOGNIZED_UPDATE_FIELDS`. Осмысленное обновление всегда несёт хотя бы одно поле — передайте распознаваемое поле (пользовательские свойства `propertyNNN` и поля `UF_*` тоже принимаются). Сущности, у которых уже была проверка полей, ведут себя как прежде.

Дополнительно у товаров каталога в `GET /v1/catalog-products/fields` появились и стали доступны для явного `select`, фильтра и сортировки поля из контракта обновления товара — `code`, `xmlId`, `sort`, `vatId`, `height`, `length`, `width`, `previewText`, `detailText` и другие (раньше фильтр по ним отвечал `400 UNKNOWN_FILTER_FIELD`). Надёжно фильтровать и сортировать стоит по индексируемым полям (`code`, `xmlId`, `sort`, `vatId`, размеры); по полнотекстовым (`previewText`, `detailText`) Битрикс24 фильтр может игнорировать. Ответы списков без явного `select` не изменились.

### FIX-0710-11: leads, companies, quotes — поля дат в /fields теперь createdTime и updatedTime

**Было**

`GET /v1/leads/fields`, `GET /v1/companies/fields` и `GET /v1/quotes/fields` объявляли поля `createdAt` и `updatedAt`, хотя в ответах list/get/search Bitrix24 всегда возвращал `createdTime` и `updatedTime` — прочитать значение по имени `createdAt`/`updatedAt` было нельзя. Фильтр и сортировка при этом принимали имена `createdAt`/`updatedAt`.

**Стало**

`/fields` этих сущностей объявляют реальные ключи `createdTime` и `updatedTime` — как у contacts, invoices и items. Ключи в теле ответов те же (`createdTime`/`updatedTime` приходили всегда), но теперь значение нормализуется к ISO-8601 в UTC (`2026-04-15T07:00:00.000Z`) — раньше приходило в исходном формате Bitrix24 со смещением портала (`2026-04-15T10:00:00+03:00`). Момент времени тот же, меняется только представление.

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

Читайте даты из `createdTime` и `updatedTime` (ключи не менялись). Если вы сравниваете строку даты побайтово или кэшируете по ней — учтите переход `+03:00` → `Z` (тот же момент времени). В фильтре и сортировке используйте `createdTime`/`updatedTime`; прежние `createdAt`/`updatedAt` теперь возвращают `400 UNKNOWN_FILTER_FIELD` (фильтр) и `400 UNKNOWN_SORT_FIELD` (сортировка). На запись `createdAt`/`updatedAt` больше не отклоняются как readonly — они игнорируются как неизвестные поля (как у contacts/invoices/items); задать дату создания/изменения по-прежнему нельзя.

### FIX-0710-12: сон-настройки: флип в «Всегда онлайн» теперь запрещён при активных окнах пробуждения

`PATCH /v1/infra/servers/:id/sleep` теперь отклоняет установку `sleepAfterMinutes: null` («Всегда онлайн», 24/7) на сервере, у которого есть включённые окна расписания пробуждения — ответ `400` с кодом `ALWAYS_ON_CONFLICT`. Это обратное направление уже существующего гейта: раньше запрещалось создавать окно пробуждения на сервере «Всегда онлайн», теперь симметрично запрещён и обратный переход — иначе сервер продолжал бы засыпать по расписанию, тихо нарушая оплаченную гарантию постоянной доступности. Удалите или отключите окна пробуждения, либо оставьте таймаут сна вместо «Никогда», чтобы включить «Всегда онлайн».

Затронутые эндпоинты: `PATCH /v1/infra/servers/:id/sleep`.

### NEW-0710-13: placement.bind на коробочном Битрикс24 отдаёт понятный SESSION_REQUIRES_ADMIN для не-администратора

На коробочном (self-hosted) Битрикс24 привязка плейсмента через путь developer-key требует, чтобы пользователь ключа был администратором аккаунта. Раньше запрос не-администратора возвращал глухой `502 BITRIX_UNAVAILABLE`.

Теперь [POST /v1/placements/bind](/docs/keys-auth) распознаёт отказ доступа со стороны Битрикс24 и возвращает `403 SESSION_REQUIRES_ADMIN` с подсказкой: выполните привязку под учётной записью администратора аккаунта либо попросите администратора выдать эти права. Требование заранее видно в [GET /v1/me](/docs/keys-auth) — блок `placements.bindPrerequisite` для коробочных аккаунтов теперь включает код `SESSION_REQUIRES_ADMIN`.

### FIX-0710-14: capabilities в GET /v1/me отражают режим только для чтения (READONLY)

**Было**

Для ключа в режиме READONLY [GET /v1/me](/docs/keys-auth) отдавал `capabilities.managedBots.create`, `agents.create`, `servers.create` и `apps.*` со значением `available: true`, хотя любая операция записи блокируется с `403 WRITE_BLOCKED_READONLY_KEY`. Агент видел «можно создать» и упирался в отказ.

**Стало**

Для READONLY-ключа эти write-способности возвращаются как `available: false` с `reason: "WRITE_BLOCKED_READONLY_KEY"` и подсказкой переключить ключ в режим чтение+запись. Способности только для чтения и AI Router не меняются. Для ключей в режиме READWRITE ответ прежний.

### FIX-0710-15: автор обращения без скоупа vibe:feedback снова может отвечать на своё обращение

**Было**

[POST /v1/feedback/:id/comments](/docs/keys-auth) отклонял автора обращения с `403 FEEDBACK_SCOPE_REQUIRED`, если у ключа не было скоупа `vibe:feedback` — хотя ветка автора была задокументирована. Автор не мог ответить на своё же обращение в статусе `AWAITING_USER`, и обращение зависало.

**Стало**

Проверка на автора выполняется до скоуп-гейта: автор, отвечающий тем же ключом, которым создал обращение, попадает в авторскую ветку (`authorType=USER`, правило мяча `AWAITING_USER → NEEDS_REVIEW`) даже без скоупа `vibe:feedback`. Ключ, не являющийся автором и не имеющий скоупа, по-прежнему получает `403 FEEDBACK_SCOPE_REQUIRED`.

### NEW-0710-16: Пейсинг AI-квоты: поле pacing в ответе и код ошибки 429 ai_pacing_limited

Ответ [GET /v1/ai/quota](/docs/ai/consumption/quota) дополнен полем `pacing` — состоянием равномерного расходования месячной AI-квоты (сглаживание пиков через суточный и недельный лимит поверх общего месячного лимита; включается администратором портала в кабинете `/ai`). Значение `null`, если пейсинг выключен на платформе или не настроен для портала; иначе объект `{ mode, active, day, week }`: `mode` — режим реакции на превышение (`wallet`/`block`/`ignore`), `active` — сработает ли превышение прямо сейчас в отказ (`false` в режиме наблюдения и всегда `false` при режиме `ignore` — окна считаются и только информируют, `429` не отдаётся), `day` и `week` — по `{ pctUsed, resetAt }` в процентах от собственного лимита окна. Ответ по-прежнему кэшируется на 30 секунд, поэтому состояние пейсинга может отставать на этот срок.

При срабатывании суточного или недельного лимита запросы [POST /v1/chat/completions](/docs/ai/chat/completions), [POST /v1/embeddings](/docs/ai/embeddings) и [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) могут вернуть `429` с телом `{ success: false, error: { code: "ai_pacing_limited", type: "rate_limit_exceeded", message, reason, overageDenied, resetAt, retryAfter } }` и заголовком `Retry-After`. `reason` — какое окно пробито (`day_window` или `week_window`); `overageDenied` — причина отказа в платном превышении лимита (`wallet_empty`, `breaker`, `wallet_off`), либо `null` в режиме жёсткой блокировки. Повторять вызов раньше `Retry-After`/`resetAt` не имеет смысла — квота не станет доступнее за это время. По умолчанию пейсинг выключен — включается платформой.

### FIX-0710-17: GET /v1/pages, /v1/sites и POST /v1/{pages,sites}/search теперь учитывают offset

**Было**

Запрос списка страниц или сайтов со смещением (`GET /v1/pages?offset=50`, `POST /v1/pages/search` с `offset`) возвращал ошибку `422 BITRIX_ERROR "Unknown parameter: start"`. Первая страница (без `offset`) работала.

**Стало**

`offset` для `pages` и `sites` обрабатывается корректно: возвращается запрошенное окно `[offset, offset+limit)`, а `meta.total` и `meta.hasMore` считаются по числу строк. Менять клиентский код не нужно — вызовы без `offset` работают как раньше.

### NEW-0710-18: /fields сущностей документов, каталогов, цен, телефонных линий, позиций корзины и лидов дополнены метаданными

`GET /:entity/fields` нескольких сущностей теперь несёт более полную метаданную схемы. У документов ([GET /v1/documents/fields](/docs/entities/documents/fields)), каталогов ([GET /v1/catalogs/fields](/docs/entities/catalogs/fields)), цен каталога ([GET /v1/catalog-prices/fields](/docs/entities/catalog-prices/fields)) и телефонных линий по каждому полю добавлены человекочитаемые `label` и `description` по-русски.

У позиций корзины ([GET /v1/basket-items/fields](/docs/entities/basket-items/fields)) поля `weight`, `vatRate`, `measureCode`, `measureName`, `dimensions`, `productXmlId`, `catalogXmlId` помечены флагом `nullable` — они могут прийти пустыми. У лидов ([GET /v1/leads/fields](/docs/entities/leads/fields)) тем же флагом помечены `secondName`, `sourceDescription`, `comments`, а также объявлены ранее неописанные поля `originatorId`, `dateClosed`, `lastCommunicationTime` и метки `utmSource`/`utmMedium`/`utmCampaign`/`utmContent`/`utmTerm` — теперь по ним работают фильтр и сортировка, а `dateClosed` нормализуется к ISO-8601.

У сайтов лендингов объявлены измерения для группировки, поэтому [POST /v1/sites/aggregate](/docs/entities/sites/aggregate) с `groupBy` (`type`, `active`, `deleted`, `lang`, `tplId`, `domainId`, `createdById`, `modifiedById`) больше не отвечает `Available: .`. У документов агрегация отключена (все числовые поля — идентификаторы): [POST /v1/documents/aggregate](/docs/entities/documents/list) возвращает `404`.

Прежние вызовы работают без изменений — это дополнительные метаданные полей.

### NEW-0710-19: Идемпотентное создание сервера — заголовок Idempotency-Key

[POST /v1/infra/servers](/docs/infra/servers/create) теперь принимает необязательный заголовок `Idempotency-Key` для создания отдельного сервера (standalone). Повторный запрос с тем же ключом — например, после потерянного ответа или разрыва сети — не создаёт второй сервер: он возвращает тот же самый сервер, что и первый запрос, со статусом 201 и заголовком ответа `Idempotent-Replayed: true`. Ключ — строка 1–255 символов из набора `[A-Za-z0-9_.:-]`; область действия — ваш API-ключ.

При повторе одноразовые SSH-учётные данные (`ssh.privateKey` / `ssh.password`) НЕ выдаются повторно — в теле ответа они `null` и добавлено поле `note` с пояснением. Сохраните учётные данные из ответа первого создания.

Новые коды ошибок: `400 INVALID_IDEMPOTENCY_KEY` (ключ не проходит валидацию), `400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION` (ключ вместе с `graduateFrom` для выделенного сервера не поддерживается), `409 IDEMPOTENCY_KEY_ALREADY_USED` (ключ уже использован для сервера, который затем был удалён), `409 IDEMPOTENCY_CONCURRENT_RETRY` (параллельный запрос с тем же ключом ещё выполняется — повторите чуть позже).

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

Дополнительно: ответ создания теперь возвращает каноническое имя сервера (с суффиксом при разрешении конфликта имён), а не имя из запроса — при совпадении имён это ранее расходилось.

### NEW-0710-20: локализованное сообщение при отказе переключения в режим OPEN

Ответы [PATCH /v1/infra/servers/:id/mode](/docs/infra/access/mode) с кодами `OPEN_MODE_DISABLED` (режим OPEN выключен на уровне платформы) и `OPEN_MODE_NOT_ALLOWED` (режим OPEN запрещён политикой портала) теперь дополнительно несут поле `error.userMessage` — локализованную человекочитаемую формулировку с подсказкой использовать [Deploy API](/docs/infra/deploy) как штатную замену прямого SSH. Поле аддитивное: `error.message` (английская техническая строка), `error.code` и HTTP-статус не меняются. Совпадает по форме с уже существующим `error.userMessage` у отказа `OPEN_MODE_REQUIRES_COMMERCIAL`. Текст `userMessage` зависит от локали владельца ключа.

### BC-0710-21: Поля конфигураций открытых линий приведены к camelCase и описаны в /fields

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

**Было**

[GET /v1/openline-configs](/docs/openlines/config/list), [GET /v1/openline-configs/{id}](/docs/openlines/config/get) и [POST /v1/openline-configs/search](/docs/openlines/config/search) возвращали большинство полей конфигурации в «родном» для Bitrix24 виде — в верхнем регистре через подчёркивание (`CRM_CREATE`, `WELCOME_MESSAGE`, `QUEUE_TIME` и другие). Справка [GET /v1/openline-configs/fields](/docs/openlines/config/fields) описывала только 6 полей, поэтому остальные не были видны для программного обнаружения.

**Стало**

Все поля конфигурации приведены к единому camelCase (`crmCreate`, `welcomeMessage`, `queueTime` и так далее), а `/fields` описывает полный набор полей с человеческими названиями (`label`) и описаниями (`description`). Фильтрация и сортировка по новым camelCase-именам работают. При записи (`create`/`update`) по-прежнему принимаются оба регистра — прежние вызовы с верхним регистром в теле не ломаются.

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

Читать поля ответа по camelCase-именам: `config.crmCreate` вместо `config.CRM_CREATE`. Соответствие имён — прямая транслитерация из верхнего регистра в camelCase (`WELCOME_BOT_ID` → `welcomeBotId`, `WORKTIME_TO` → `workTimeTo`, `LINE_NAME` → `name`). Полный перечень новых имён — в справке `/fields`.

### NEW-0710-22: Добавлен GET /v1/warehouses/fields — схема полей склада

Появился эндпоинт [GET /v1/warehouses/fields](/docs/entities/warehouses/list), возвращающий схему 19 полей склада: для каждого поля — тип (`type`), признак «только для чтения» (`readonly`), человеческое название (`label`) и описание (`description`). Склады — кастомный роут (без сущностной схемы), поэтому раньше у них не было справки полей, которая есть у автогенерируемых сущностей. Ответ — `{ success: true, data: { fields: { … } } }`. Требуется скоуп `catalog`.

### FIX-0710-23: публикация приложения восстанавливается при рассинхроне плейсмента

**Было**

При публикации ([POST /v1/apps/:id/publish](/docs/apps)) или обновлении плейсментов ([PATCH /v1/apps/:id](/docs/apps)), если плейсмент был зарегистрирован на стороне Bitrix24, но отсутствовал в приложении (дрейф после снятия с публикации), привязка падала с ошибкой «Handler already binded» и плейсмент оставался несинхронизированным.

**Стало**

При такой ошибке платформа один раз снимает устаревшую привязку и повторяет её — плейсмент синхронизируется автоматически. Восстановление срабатывает только на подтверждённом конфликте, поэтому «живой» плейсмент никогда не снимается по ошибке; плейсменты, которым нужны непереносимые OPTIONS (чат-виджеты, фоновый обработчик), из авто-восстановления исключены и по-прежнему сообщают предупреждение.

### FIX-0710-24: storage: sha256 объекта заполняется при прямой загрузке

**Было**

Поле `sha256` в ответе на загрузку объекта хранилища всегда было `null` для пользовательских объектов, хотя схема описывала его как «вычисляется при загрузке».

**Стало**

При прямой загрузке ([POST /v1/storage/objects/upload](/docs/storage), файлы до 10 МБ) `sha256` теперь содержит SHA-256-хэш содержимого объекта. По нему можно проверять целостность и находить дубликаты (одинаковое содержимое — одинаковый хэш). Для presigned- и multipart-загрузок байты идут напрямую в хранилище мимо платформы, поэтому там `sha256` пока остаётся `null`.

### FIX-0710-25: поле pacing.active в GET /v1/ai/quota больше не сообщает об активном лимите при нулевой квоте

**Было**

При включённом равномерном расходовании на портале с нулевой месячной квотой (жёсткая блокировка через переопределение `monthlyVibes = 0` или ещё не инициализированный расход) поле `data.pacing.active` возвращало `true` — хотя отказ `429 ai_pacing_limited` в этом состоянии структурно невозможен: запросы отклоняются месячным лимитом, а не оконным.

**Стало**

`data.pacing.active` возвращает `true` только когда превышение дневного или недельного окна действительно может привести к `429 ai_pacing_limited`. При нулевой квоте поле честно отдаёт `false`. Форма ответа не изменилась; клиентам, строившим backoff-логику по `active`, действий не требуется — сигнал стал точнее.

## 2026-07-09

### FIX-0709-1: транзиентная перегрузка БД теперь отдаёт 503 с Retry-After вместо 500

**Было**

В редкой форме кратковременной перегрузки БД (исчерпание коннектов) часть запросов (включая `POST /v1/infra/servers`) возвращала `500`.

**Стало**

Такие запросы возвращают `503` с кодом `POOL_EXHAUSTED` и заголовком `Retry-After`. Ошибка транзиентная — повтори запрос с задержкой (backoff).

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

Клиенты, повторяющие запросы при 5xx, теперь должны обрабатывать `503` и уважать `Retry-After`. `POST /v1/infra/servers` неидемпотентен — повтор может создать дубль сервера, поэтому повторяйте с backoff, а не немедленно.

### FIX-0709-2: placements/bind честнее сообщает о необходимости подписки Маркетплейса

**Было**

При привязке плейсмента через ключ приложения на портале без активной подписки «BitrixGPT + Маркетплейс» Bitrix24 отвечал отказом доступа, а `POST /v1/placements/bind` возвращал непрозрачный `502 BITRIX_UNAVAILABLE` без указания причины. Подписка требуется на пути через ключ разработчика независимо от коммерческого тарифа, но `GET /v1/me` не сообщал об этом предусловии заранее.

**Стало**

Отказ по подписке теперь классифицируется: `POST /v1/placements/bind` возвращает `403` с кодом `B24_MARKET_SUBSCRIPTION_REQUIRED` (или `B24_MARKET_TRIAL_USED`, если демо уже использован), понятным `userMessage` и ссылкой на оформление в `details.upgradeUrl`. У ключа приложения в `GET /v1/me` добавлен блок `placements.bindPrerequisite` — он заранее описывает предусловие Bitrix24 (путь через ключ разработчика требует активной подписки Маркетплейса, устаревший OAuth-путь — коммерческого тарифа) и перечисляет коды ошибок. Прочие отказы привязки (неизвестный clientId, устаревший embedding) по-прежнему возвращают `502 BITRIX_UNAVAILABLE`.

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

Менять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал `502 BITRIX_UNAVAILABLE` при привязке, стоит дополнительно ловить `403 B24_MARKET_SUBSCRIPTION_REQUIRED` / `B24_MARKET_TRIAL_USED` и подсказывать пользователю оформить подписку на портале.

### FIX-0709-3: bindPrerequisite в GET /v1/me учитывает регион портала

**Было**

Блок `placements.bindPrerequisite` у ключа приложения описывал предусловие привязки одинаково для всех порталов: `subscriptionRequired: true`, рецепт «попросите администратора активировать подписку Маркетплейса» и список кодов с `B24_MARKET_SUBSCRIPTION_REQUIRED` / `B24_MARKET_TRIAL_USED`. На порталах с тарифной моделью доступа отказ привязки приходит с кодом `INT_TARIFF_REQUIRED`, а подписки там нет — предложенный рецепт был невыполним.

**Стало**

Блок зависит от региона портала. На порталах с подписочной моделью доступа он прежний. На порталах с тарифной моделью `subscriptionRequired` равен `false`, текст `note` описывает требование коммерческого тарифа Битрикс24, а `errorCodes` содержит `INT_TARIFF_REQUIRED` и `BITRIX_UNAVAILABLE` — только те коды, которые портал действительно может получить.

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

Успешные вызовы не затронуты. Если вы читали `errorCodes` из `bindPrerequisite` как полный перечень, учтите, что теперь он сужен до достижимых на конкретном портале кодов. Сами коды и поведение `POST /v1/placements/bind` не менялись.

### BC-0709-4: Поля CRM, задач и лендингов приведены к реальному контракту Битрикс24

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

**Было**

`GET /v1/deal-categories` возвращал `isLocked` строкой `"Y"`/`"N"`, а `GET /v1/payments` — `paySystemIsCash` строкой `"Y"`/`"N"`. `GET /v1/leads` в каждом ответе отдавал служебное поле `searchContent` (внутренний полнотекстовый индекс Битрикс24). Поля `paySystemXmlId`, `dateMarked`, `dateResponsibleId` у платежей приходили как есть, без нормализации даты.

**Стало**

`isLocked` (воронки) и `paySystemIsCash` (платежи) теперь имеют тип boolean (`true`/`false`). `searchContent` в ответах `GET /v1/leads` больше не отдаётся. У платежей добавлены задекларированные поля `paySystemXmlId` (строка), `dateMarked` и `dateResponsibleId` (даты нормализованы в единый формат ISO с суффиксом `Z`). У задач добавлены `changedBy`/`closedBy`/`statusChangedBy` (только для чтения — попытка записать возвращает `400 READONLY_FIELD`). `GET /v1/currencies/fields` отдаёт человекочитаемые `label` для полей и поле `lang`; `GET /v1/deal-categories/fields` — `label` для полей; `GET /v1/sites/fields` — флаг `nullable` у полей, которые могут прийти пустыми.

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

Читать `isLocked` и `paySystemIsCash` как boolean, а не сравнивать со строкой `"Y"`. Если код опирался на поле `searchContent` у лидов — перестать: оно было служебным и не задокументированным.

### FIX-0709-5: создание сущности с пустым телом отклоняется явной ошибкой

**Было**

Создание сущности с пустым телом (или вовсе без тела, но с заголовком `Content-Type: application/json`) доходило до Битрикс24 и молча создавало сущность со значениями по умолчанию — включая сделки, лиды, контакты, компании, счета и смарт-процессы. Повторный вызов или клиент без тела так плодил мусорные записи в CRM. Это касалось всех трёх поверхностей создания: одиночного `POST /v1/{entity}`, пакетного `POST /v1/{entity}/batch` и общего `POST /v1/batch`.

**Стало**

Такой запрос возвращает `400 EMPTY_CREATE_BODY` (в пакетных вызовах — ошибку элемента) до обращения к Битрикс24, на всех трёх поверхностях. Осмысленное создание всегда несёт хотя бы одно поле — передайте нужные поля в теле запроса. Сущности, у которых уже есть проверка обязательных полей, ведут себя как прежде.

### FIX-0709-6: снятие блокировки сервера принимает пустое тело JSON

**Было**

[DELETE /v1/infra/servers/:id/lock](/docs/infra) с заголовком `Content-Type: application/json` и пустым телом возвращал `400` (пустое JSON-тело). Чтобы снять зависшую блокировку, приходилось слать явное `{}` — не зная этого, клиент упирался в тупик.

**Стало**

Пустое тело при этом заголовке принимается как `{}`; запрос без тела отрабатывает штатно и снимает блокировку. Явное `{}` по-прежнему работает.

### FIX-0709-7: события портала теперь будят спящее galaxy-приложение

**Было**

Событие Битрикса, отправленное на подписку спящего galaxy-приложения, не будило его — доставка
уходила в повторные попытки и после их исчерпания терялась.

**Стало**

Платформа будит спящее galaxy-приложение при доставке события и доставляет его после подъёма —
как для обычного сервера.

### NEW-0709-8: camelCase-ключи внутри communications при создании дела

[POST /v1/activities](/docs/entities/activities/create) теперь принимает вложенные ключи элементов `communications` в camelCase (`type`, `value`, `entityTypeId`, `entityId`) — единообразно с остальным API. Раньше вложенные ключи принимались только в ВЕРХНЕМ регистре Bitrix24 (`TYPE`, `VALUE`, `ENTITY_TYPE_ID`, `ENTITY_ID`), а camelCase-форма молча отбрасывалась — `communications: [{ "type": …, "value": … }]` возвращал `422` «COMMUNICATIONS is not defined or invalid», тогда как `[{ "TYPE": …, "VALUE": … }]` создавал дело. ВЕРХНИЙ регистр по-прежнему работает; если в одном объекте заданы обе формы одного ключа, приоритет у ВЕРХНЕГО регистра.

### FIX-0709-9: снятие с публикации убирает вкладку по всем обработчикам приложения

**Было**

[POST /v1/apps/:id/unpublish](/docs/apps/unpublish) снимал placement только по обработчику, совпадающему с ожидаемым платформенным адресом. Если вкладка была привязана к техническому адресу самого приложения, Битрикс24 не находил её и не удалял — вкладка оставалась висеть в карточке CRM, и через API её уже нельзя было убрать.

**Стало**

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

### NEW-0709-10: Самоописание ключей в GET /v1/guide

Ответ `GET /v1/guide` дополнен блоком `data.keysAuth`. Он описывает два эндпоинта самоописания — `GET /v1/me` и `GET /v1/guide` — и содержит ссылки на документацию: контракт ответа каждого из них, режим доступа ключа и общее описание типов ключей.

Оба эндпоинта работают по одному заголовку `X-Api-Key`, токен сессии для них не нужен.

Поле аддитивное: существующие клиенты не затронуты.

**Затронутые эндпоинты:** [GET /v1/guide](/docs/keys-auth/guide)

### FIX-0709-11: GET /v1/{entity}/:id теперь учитывает ?select=

Проекция полей `?select=` на чтении одной записи по id раньше игнорировалась: ответ всегда приходил со всеми полями, хотя `/v1/me` заявляет, что `?select=` работает «на списке, чтении по id и POST /search». Теперь чтение по id проецирует ответ так же, как список и поиск, — приводя поведение в соответствие с задокументированным.

**Было**

`GET /v1/leads/42?select=id,title` возвращал полную запись (все поля).

**Стало**

`GET /v1/leads/42?select=id,title` возвращает только `id` и `title`. Поддерживаются формы через запятую (`?select=id,title`), массивом (`?select[]=id&select[]=title`) и с индексами (`?select[0]=id&select[1]=title`); `id` включается в ответ всегда. Неизвестное имя поля молча пропускается — это не ошибка. При одновременном `?select=` и `?include=` связанная сущность в ответе сохраняется. Индексная форма (`?select[0]=…`) раньше возвращала 500 и на списке `GET /v1/{entity}` — теперь тоже проецирует корректно.

### FIX-0709-12: /fields сущностей заказов, позиций корзины, пресетов реквизитов и шаблонов документов приведены к реальному контракту B24

**Было**

`GET /:entity/fields` (и генерируемая по нему OpenAPI-схема) объявлял поля, которые Bitrix24 не возвращает: `provider` у шаблонов документов; `reserved`, `sumPaid`, `dateBill`, `datePayBefore`, `datePaid`, `empPaidId`, `userEmail`, `userName` на верхнем уровне заказа; `module`, `fUserId`, `lid`, `dateRefresh`, `subscribe`, `reserved`, `reserveQuantity` у позиций корзины; `originatorId` у пресетов реквизитов. Фильтрация и сортировка по этим полям молча не срабатывали. При этом реально приходящие поля не были объявлены: `requisiteLink` у заказа, `type`/`properties`/`reservations` у позиции корзины. Пресеты реквизитов принимали `countryId`/`entityTypeId` на обновление, где Bitrix24 их молча игнорирует.

**Стало**

Несуществующие поля убраны из `/fields` и OpenAPI. Реально приходящие поля объявлены: у заказа — `requisiteLink` (объект `requisiteId`/`bankDetailId`/`mcRequisiteId`/`mcBankDetailId`, только чтение); у позиции корзины — `type`, `properties`, `reservations` (только чтение). У пресетов реквизитов `countryId` и `entityTypeId` помечены как задаваемые только при создании: обновление возвращает `400 READONLY_FIELD` вместо тихого игнорирования.

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

Ответы list/get не меняются — убранные поля и так никогда не приходили. Если запрос фильтровал или сортировал по убранному полю, теперь он вернёт `400` — используйте реальные поля из `/fields` (например, даты и суммы оплаты у заказа лежат внутри массива `payments`, а не на верхнем уровне). Обновление `countryId`/`entityTypeId` у пресета реквизитов теперь явно отклоняется — задавайте эти поля только при создании.

**Затронутые эндпоинты:** [GET /v1/orders/fields](/docs/entities/orders/fields), [GET /v1/basket-items/fields](/docs/entities/basket-items/fields), [GET /v1/requisite-presets/fields](/docs/entities/requisite-presets/fields), [GET /v1/doc-templates/fields](/docs/entities/doc-templates/fields)

### NEW-0709-13: GET /:entity/fields отдаёт человекочитаемые label и описания для сделок, лидов, счетов, дел, справочников и комментариев таймлайна

Ответ `GET /v1/deals/fields`, `/v1/leads/fields`, `/v1/invoices/fields`, `/v1/activities/fields`, `/v1/statuses/fields` и `/v1/timelines/fields` теперь несёт по каждому полю человекочитаемые `label` и `description` по-русски. У полей со служебными кодами добавлены словари `enum`: у сделок — `stageSemanticId` (P — в работе, S — успех, F — провал); у дел — `typeId`, `direction`, `priority`, `status`, `notifyType` и `descriptionType`. Прежние вызовы работают без изменений — это дополнительные поля метаданных, форма ответа не меняется.

### FIX-0709-14: POST /v1/batch — единый формат ошибок под-вызовов и totals только для list/search

**Было**

Ошибка Bitrix24 внутри успешного 200-ответа [POST /v1/batch](/docs/batch) (например, `get` несуществующего элемента) попадала в `data.errors[<id>]` в исходной форме Bitrix24 `{ error, error_description }` — не в общем для V1 конверте `{ code, message }`, который используют ошибки валидации и любой другой ответ API. Поле `data.totals[<id>]` при этом заполнялось для любого действия, включая `get`/`create`/`update`/`delete`, где одиночное число рядом с единственной записью не имеет смысла.

**Стало**

Ошибка под-вызова приводится к `{ code, message }` (`error` → `code`, `error_description` → `message`), как у остальных ошибок. `data.totals[<id>]` заполняется только для действий `list` и `search` — там, где счётчик совпадений реально имеет смысл.

### FIX-0709-15: GET /v1/openline-configs — нормализация пустых значений в ответе

**Было**

Ответы [GET /v1/openline-configs](/docs/openlines/config/list) и [GET /v1/openline-configs/:id](/docs/openlines/config/get) отдавали служебные поля в неудобных для клиента формах: `KPI_FIRST_ANSWER_LIST`, `KPI_FURTHER_ANSWER_LIST`, `DEFAULT_OPERATOR_DATA` приходили как `null` (на них падал `.map`/`.length`); `AUTO_CLOSE_TEXT` для незаданного значения приходил как `""` в карточке и как `null` в списке; `WORKTIME_HOLIDAYS`/`WORKTIME_DAYOFF` для пустого набора приходили как `[""]` (массив с одной пустой строкой).

**Стало**

Списки нормализованы: `null` → `[]`. `AUTO_CLOSE_TEXT` приведён к единому `null` для пустого значения и в списке, и в карточке. `WORKTIME_HOLIDAYS`/`WORKTIME_DAYOFF` для пустого набора приходят как `[]`. Список и карточка теперь отдают одинаковую форму этих полей.

### FIX-0709-16: GET /v1/users/fields отдаёт возможные значения (items) для UF-полей-перечислений

**Было**

Пользовательское поле-перечисление (`UF_USR_*` типа «список») приходило в `GET /v1/users/fields` как `{ "type": "string", "label": "…" }` — без списка возможных значений. Причина: метод `user.fields` возвращает у UF-полей только подпись, без типа и вариантов, поэтому перечисление было неотличимо от строки.

**Стало**

Такое поле приходит с настоящим типом и списком вариантов: `{ "type": "enumeration", "label": "…", "items": [ { "ID": "…", "VALUE": "…", "DEF": "…", "XML_ID": "…" }, … ] }`. Значения дочитываются из `user.userfield.list` — для этого у ключа должен быть скоуп `user.userfield`; если он не выдан, поле по-прежнему отдаётся с подписью, но без `items` (мягкая деградация). Заодно у остальных UF-полей (`money`, `date` и т. п.) в ответе появляется их настоящий тип вместо `string`.

### FIX-0709-17: подсказка hint при пустой очереди событий появляется по факту устойчивой пустоты

**Было**

[GET /v1/bots/:botId/events](/docs/bots/events/polling) увеличивал счётчик пустых ответов ровно на каждый запрос, и поле `hint` появлялось строго после пятого подряд пустого ответа. Число в тексте подсказки совпадало с количеством сделанных запросов.

**Стало**

Счётчик пустых ответов обновляется периодически, а не на каждый запрос, поэтому `hint` появляется после устойчивой пустоты очереди — при рекомендованном интервале опроса 2–5 секунд спустя примерно пару минут непрерывно пустого опроса. Число N в тексте отражает количество зафиксированных периодов пустоты, а не точное количество сделанных запросов. Правило «доставлено событие → счётчик и подсказка сбрасываются» не изменилось.

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

Менять код не нужно. Если вы опирались на появление `hint` строго на пятом запросе или трактовали N как точное число запросов — используйте `persisted` и наличие событий как основной сигнал, а `hint` как диагностическую подсказку.

## 2026-07-08

### FIX-0708-1: GET /v1/doc-templates и POST /search честят order и offset

**Было**

Параметры `order` и `offset` на `GET /v1/doc-templates` и `POST /v1/doc-templates/search` молча игнорировались: список всегда возвращался в порядке возрастания по `id`, а `offset` не смещал окно выборки. Причина — метод Bitrix24 отдаёт шаблоны объектом с ключами-`id`, и заданный порядок терялся при разворачивании ответа.

**Стало**

Сортировка (`order[поле]=asc|desc`, в том числе по нескольким полям) и постраничная выборка (`offset`/`limit`) применяются на стороне Vibecode: набор шаблонов вытягивается полностью, сортируется и режется по запрошенному окну. `total` и `hasMore` считаются от фактически собранного набора.

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

Тем, кто полагался на неявный порядок «по возрастанию `id`» при `offset=0` без сортировки, менять ничего не нужно — это остаётся поведением по умолчанию. `POST /v1/doc-templates/batch` (batch-list) не затронут. Строковый порядок (`name`, `region`) — побайтовый, без учёта локали.

### NEW-0708-2: GET /v1/apps/:id/sources — новое поле linkedServerSources

[GET /v1/apps/:id/sources](/docs/source-storage) теперь дополнительно возвращает поле `linkedServerSources` — версии исходников, сохранённые под сервером (через `POST /v1/infra/servers/:id/sources` или авто-сохранение при деплое), сгруппированные по серверу, каждая со своим `serverContext`. Такие версии раньше не попадали в этот ответ, если сохранялись под личным ключом, — теперь они видны.

Поле аддитивное: `versions`, `totalVersions`, `currentVersionId` и `totalSizeBytes` не изменились и по-прежнему перечисляют только версии, привязанные к приложению. Рядом приходят `linkedServerSourcesTruncated` (признак усечения при очень большом числе версий) и `linkedServerHint` с указателем на `GET /v1/infra/servers/:serverId/sources` — авторитетный полный список и скачивание этих версий. Секция заполняется для автора приложения (личный ключ) и администратора портала; при вызове ключом OAuth-приложения она пуста, а при `?sha256=`-пробе не вычисляется.

### NEW-0708-3: поле preemptible в ответе списка тарифов серверов

Ответ [GET /v1/infra/providers/:id/plans](/docs/infra/providers/plans) теперь формально описывает поле `preemptible` у каждого тарифа. Вытесняемый тариф дешевле, но облако принудительно перезапускает такую машину примерно раз в сутки — он не подходит для непрерывных 24/7-нагрузок. Для сервера, агента или бота, которые должны работать без перерывов, выбирайте невытесняемый тариф (`preemptible` равно `false` или отсутствует).

Поле уже отдавалось в ответе рантаймом — эта запись фиксирует его в OpenAPI и документации; менять существующие интеграции не требуется.

### NEW-0708-4: GET /v1/models/:id теперь показывает цену преемника у снятых моделей и поле replaced_by

Для модели, снятой с публикации, запрос детали по идентификатору теперь возвращает поле `replaced_by` с идентификатором модели-преемника, на которую фактически уходят вызовы, а поле `pricing` показывает цену этого преемника — ту, по которой запрос и тарифицируется. Раньше `pricing` показывал собственную нулевую цену снятой строки, из-за чего модель выглядела бесплатной, хотя вызовы обслуживал платный преемник. Обычные модели и прежние вызовы не меняются.

### FIX-0708-5: автопагинация списков сохраняет порядок строк при limit выше 550

**Было**

Списочные запросы с автопагинацией — `GET /v1/{entity}?limit=…` и `POST /v1/{entity}/search` — при `limit` выше ~550 возвращали строки с нарушенным порядком: внутренние страницы выдачи склеивались не в том порядке, в котором их отдал Битрикс24, поэтому параметр `order` на итоговом массиве не соблюдался. Если записей было больше, чем `limit`, обрезка окна могла выбросить строки из середины отсортированной выборки, оставив более поздние.

**Стало**

Страницы склеиваются строго в порядке выдачи Битрикс24: строки приходят в заказанной сортировке при любом `limit`, а обрезка по `limit` больше не выбрасывает строки из середины выборки из-за неверного порядка склейки.

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

Менять ничего не нужно. Если вы пересортировывали большие выборки на своей стороне как обходной путь — это больше не требуется.

### FIX-0708-6: автопагинация больше не теряет молча страницу при сбое пакетного подзапроса

**Было**

При `limit > 50` список собирается пакетными подзапросами по 50 записей. Если Битрикс24 отклонял один подзапрос (чаще всего по лимиту запросов — `QUERY_LIMIT_EXCEEDED`), его страница молча выпадала из середины выборки: ответ оставался `200`, в данных образовывалась необнаружимая дыра в 50 записей (например, записи 1–200 и 251–600 без 201–250), а `meta.total` и `meta.hasMore` выглядели непротиворечиво.

**Стало**

Для списков, обычного поиска, пакетных подвызовов и агрегаций результат — всегда непрерывный префикс выборки: записи после сбойной страницы отбрасываются, `meta.hasMore` остаётся `true`, и в ответе появляется `meta.pageErrorSample { code, message }` с причиной сбоя — по образцу `meta.windowErrorSample` оконного поиска. Поле добавлено в ответы списков (например [GET /v1/deals](/docs/entities/deals/list)), в `POST /v1/{entity}/search` (например [сделки](/docs/entities/deals/search)), в `meta` подвызовов [POST /v1/batch](/docs/batch) и в `data.meta` агрегаций `POST /v1/{entity}/aggregate` (там оно объясняет, почему `recordsProcessed` меньше `totalRecords`). В оконном поиске (широкий диапазон дат) набор собирается из окон, поэтому при потере страницы внутри одного окна ответ может недосчитаться хвоста этого окна — признак неполноты там именно `meta.pageErrorSample`, а не `meta.hasMore`. Во всех случаях поле появляется, только если итоговая страница действительно короче `limit`: полный ответ ложным сигналом не помечается. Неполный ответ не кэшируется: повторный запрос сразу идёт в Битрикс24.

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

Менять клиентский код не нужно: выборки, которые раньше могли содержать незаметную дыру, теперь корректны, а причина недобора видна в `meta.pageErrorSample`. Дочитать остаток можно повторным запросом с `offset`, равным сумме исходного `offset` и числа полученных записей, — кроме оконного поиска по широкому диапазону дат (там `offset` не поддерживается: сузьте диапазон или повторите запрос позже).

### BC-0708-7: структурированный вывод: обрезанный или пустой ответ теперь возвращает 422, а не пустой 200

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

**Было**

[POST /v1/chat/completions](/docs/ai/chat/completions) со `response_format` (`json_object` или `json_schema`) при обрыве генерации мог вернуть `200` с `content: null` (или обрезанной, непарсимой строкой JSON) и предупреждением, которое клиенты не замечали. Чаще всего это случалось на моделях рассуждения: фаза рассуждения расходовала весь бюджет `max_tokens` до того, как модель писала JSON. Ответ выглядел успешным, но разобрать его было нельзя.

**Стало**

Такой запрос возвращает `422` с `code: "structured_output_truncated"`, полями `finishReason`, `suggestedMaxTokens` (рекомендованный увеличенный `max_tokens` для повтора) и `param: "max_tokens"`. В потоковом режиме перед `data: [DONE]` приходит служебный кадр `{"error":{"code":"structured_output_truncated"}}` — читайте поток до `[DONE]`. Дополнительно: для бесплатных моделей рассуждения при слишком маленьком `max_tokens` платформа поднимает бюджет до безопасного минимума и помечает успешный ответ предупреждением `MAX_TOKENS_RAISED`. Обрезанная попытка по-прежнему расходует и тарифицирует токены.

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

Обрабатывайте `422 structured_output_truncated` в ветке ошибок и повторяйте запрос с бóльшим `max_tokens` (можно взять значение из `suggestedMaxTokens`). Для строго-детерминированного JSON задавайте `max_tokens` с запасом или используйте обычную (не «thinking») модель.

## 2026-07-07

### FIX-0707-1: smart-processes: linkedUserFields принимает Y/N и булевы значения

**Было**

[POST /v1/smart-processes](/docs/entities/smart-processes/create) и [PATCH /v1/smart-processes/:entityTypeId](/docs/entities/smart-processes/update) с `linkedUserFields` работали только когда значение флага было строго `"true"`/`"false"`. Значение в конвенции `"Y"`/`"N"` (как у всех остальных полей смарт-процесса) или булево `true`/`false` молча игнорировалось: запрос возвращал `success: true`, но отображение в пользовательском поле не включалось.

**Стало**

Значения `linkedUserFields` нормализуются так же, как вложенный флаг `relations[].isChildrenListEnabled`: `true`/`"Y"`/`"yes"`/`1` → включено, `false`/`"N"`/`"no"`/`0` → выключено. Прежние вызовы с `"true"`/`"false"` продолжают работать без изменений.

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

Ничего менять не нужно — вызовы, которые раньше «молча не срабатывали» с `"Y"`, теперь применяются корректно.

### BC-0707-2: Нормализация вложенных полей карточки заказа

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

**Было**

`GET /v1/orders/{id}` отдавал вложенные массивы `clients`, `payments`, `basketItems` в сыром виде Bitrix24: булевы поля строками `"Y"` и `"N"` (`payments[].paid`, `clients[].isPrimary`, `basketItems[].vatIncluded` и другие), даты внутри `payments` и `basketItems` со смещением `+03:00`, а поле `companyId` со значением `0`, когда компания не задана. Поле `accountNumber` при создании и обновлении молча игнорировалось.

**Стало**

Вложенные Y/N-поля приходят как boolean (`true` или `false`); вложенные даты нормализованы к UTC (оканчиваются на `Z`); `companyId` при отсутствии компании приходит `null` вместо `0`; `accountNumber` стал полем только для чтения — попытка задать его при создании или обновлении возвращает `400` с кодом `READONLY_FIELD`.

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

Читать вложенные Y/N-поля как boolean, а не сравнивать со строкой `"Y"`; трактовать `null` вместо `0` как признак «компания не задана»; не передавать `accountNumber` в теле создания и обновления — номер присваивается автоматически.

### FIX-0707-3: Спека /v1/openapi.json приведена к фактическому рантайму

**Было**

Машинная OpenAPI-спека генерировалась из статической метаданной сущностей и расходилась с реальными ответами: ни одно поле не помечено nullable, вложенные массивы карточки заказа типизировались как строка, у списочных методов не описаны `filter` и `select`, у операций объявлены только успешные коды и `403`.

**Стало**

Спека теперь отражает контракт. Nullable-поля выводятся в форме `type: ["<тип>", "null"]`. Объектные и массив-объектов поля типизируются честно, включая вложенные `clients`, `payments`, `basketItems`, `propertyValues` у `GET /v1/orders/{id}`. На списочных методах описаны query-параметры `filter` и `select`. Операции несут стандартные коды ошибок `400`, `401`, `404`, `422` в едином конверте `{ success:false, error:{ code, message } }`. Схемы `*Input` объявляют обязательные при создании поля. Дополнительно `GET /v1/orders/fields` отдаёт `clients` как массив вместо object. SDK, сгенерированный из спеки, получает корректную типизацию.

### BC-0707-4: /search: авто-оконный поиск при полном отказе отдаёт настоящую ошибку Bitrix24

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

**Было**

Любой неуспешный авто-оконный `POST /v1/{entity}/search` возвращал `502 { "error": { "code": "WINDOWED_SEARCH_FAILED" } }` с общим советом «добавьте autoWindow:false».

**Стало**

Ответ совпадает с тем же запросом на узком диапазоне — реальный код и сообщение: отклонённое поле фильтра/сортировки → `400 UNKNOWN_FILTER_FIELD` / `400 INVALID_PARAMS`; нет прав → `403`; лимит запросов / перегрузка очереди → `429` + `Retry-After`; таймаут → `503`; недоступность Bitrix24 → `502 BITRIX_UNAVAILABLE`. Частичный отказ окон (статус `200`) теперь несёт `meta.windowErrorSample` `{ code, message }`.

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

Если вы ветвились на `error.code === "WINDOWED_SEARCH_FAILED"` (например, чтобы повторить с `autoWindow:false`) — ветвитесь на реальные коды. Обходной путь `autoWindow:false` остался; он полезен там, где действительно помогает (подсказка `429 QUEUE_TIMEOUT` называет его). Ответ полного отказа больше не несёт блок `meta` (`autoWindowed`/`windowCount`/`windowErrors`) — сигнал теперь в самом коде/сообщении ошибки; `meta.windowErrorSample` остаётся на частичном отказе (статус `200`).

### FIX-0707-5: списки на портале без модуля стабильно отдают 409, а не 429

**Было**

На портале, где модуль «Универсальные списки» не включён, вызовы [/v1/lists](/docs/lists) отдавали понятный `409 LISTS_MODULE_NOT_ENABLED` только для первых нескольких запросов. После этого встроенная защита от циклов ошибок срабатывала и все последующие вызовы возвращали `429 ERROR_LOOP_DETECTED` — реальная причина (модуль не подключён) переставала быть видна.

**Стало**

Сигнал «метод недоступен на портале» больше не учитывается защитой от циклов, поэтому вызовы `lists.*` на портале без модуля стабильно возвращают `409 LISTS_MODULE_NOT_ENABLED` при любом числе повторов. Ответ остаётся действенным: подключите модуль на портале и повторите запрос.

### NEW-0707-6: Подсказка error.hint на 400 при создании сервера без source и без provider/plan/region

[POST /v1/infra/servers](/docs/infra/servers/create) при `400 INVALID_REQUEST` из-за отсутствующих `provider`/`plan`/`region` (и отсутствующего `source`) на портале с galaxy-размещением теперь дополнительно возвращает объект `error.hint` с полями `reason` (почему запрос отклонён на этом портале), `recovery` (рекомендованный one-shot путь и рабочая двухшаговая альтернатива) и `example` (готовый скелет тела one-shot запроса). Поля `error.code` и `error.message` не изменились — подсказка строго аддитивна; на порталах без galaxy-размещения ответ прежний, без `hint`.

Подсказка также возвращается в ветке `400 RUNTIME_PARAM_REMOVED` (создание с `runtime`, но без `source`, на портале с galaxy-размещением), а тело с `placement: "dedicated"` получает отдельный вариант подсказки — под выделенный сервер, с сохранением намерения и добавлением недостающего tuple `provider`/`plan`/`region`, без увода в galaxy-контейнер.

### FIX-0707-7: Galaxy-чек-лист деплоя в /v1/me приведён к фактическому контракту

**Было**: шаг 2 чек-листа `deployment.galaxyApp.checklist` предписывал [POST /v1/infra/servers](/docs/infra/servers/create) `{ name }` без `source` и без `provider`/`plan`/`region` — такой вызов всегда завершался `400 INVALID_REQUEST`. Правило CREATE не объясняло, что для двухшагового пути обязателен полный набор `provider`/`plan`/`region`, а `newAppPlacement.note` обещала выделенный standalone-VM там, где создание возвращает galaxy-слот с `next: "deploy"`. Favicon-гайд направлял в этот же неработающий порядок; окно уборки недеплоенного слота указывалось как «~12-20 мин» при фактических ~20-25.

**Стало**: рекомендованный путь — один вызов `POST /v1/infra/servers { name, source, runtime, start }` (`provider`/`plan`/`region` опускаются). Двухшаговый путь описан правдиво: создание без `source` требует полный `provider`/`plan`/`region` (значения для galaxy информационны — приложение наследует хост), на galaxy-размещении возвращает слот с `next: "deploy"`; недеплоенный слот убирается в ERROR после ~20 мин (проверка каждые 5 мин). Favicon: основной путь — собственный `/icon.svg` в архиве (id не нужен); платформенный URL — альтернатива через two-step или re-deploy. Шаг опроса статуса получил ветку `status=error` → `provisionError`/`buildLog` → re-deploy.

**Влияние на интеграторов**: агенты, следующие чек-листу, деплоят с первого вызова. Поведение эндпоинтов не менялось — обновлены только тексты `/v1/me` и описание в `/v1/openapi.json`; существующие интеграции продолжают работать без изменений.

### NEW-0707-8: AI-квота компании доступна через API

Новый эндпоинт [GET /v1/ai/quota](/docs/ai/consumption/quota) возвращает состояние месячной AI-квоты портала: процент израсходованного лимита (`pctUsed`, честное значение — при перерасходе больше 100), признак исчерпания (`exhausted`), дату сброса (`resetAt`, скользящее 30-дневное окно) и разбивку по моделям — количество запросов, токены и долю месячного лимита на каждую модель (`byModel[].pctOfLimit`). Абсолютные значения лимита в Вайбах не раскрываются — только проценты, как в кабинете. Требуется скоуп `vibe:ai`.

## 2026-07-06

### NEW-0706-1: Поля pricing.perCall и pricing.perMinute в каталоге моделей

Ответы [GET /v1/models](/docs/ai/models/list) и [GET /v1/models/{model}](/docs/ai/models/get) дополнены необязательными полями в объекте `pricing`: `perCall` — стоимость одного вызова в Вайбах, `perMinute` — стоимость одной минуты аудио в Вайбах (для моделей распознавания речи). Поля появляются только у моделей, для которых соответствующая базовая цена больше нуля; у остальных моделей объект `pricing` не меняется — существующие запросы работают без изменений.

### NEW-0706-2: Новый код ошибки 402 ai_quota_exhausted на AI-эндпоинтах

При включённом контроле месячной AI-квоты портала запросы [POST /v1/chat/completions](/docs/ai/chat/completions), [POST /v1/embeddings](/docs/ai/embeddings) и [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) могут вернуть 402 с телом `{ success: false, error: { code: "ai_quota_exhausted", type: "insufficient_quota", reason, resetAt? } }`. Поле `reason` различает три случая: `breaker` — сработал часовой предохранитель расходов сверх квоты, `wallet_empty` — квота исчерпана и на балансе портала нет средств, `wallet_off` — расход сверх квоты для портала недоступен. `resetAt` — момент, когда запросы снова начнут проходить (для `wallet_off` при полном отключении может отсутствовать). Пока квота портала не исчерпана, поведение эндпоинтов не меняется.

### FIX-0706-3: Расход сверх AI-квоты списывается по базовой цене модели из каталога

**Было**

При активном контроле месячной AI-квоты портала запросы сверх квоты на [POST /v1/chat/completions](/docs/ai/chat/completions), [POST /v1/embeddings](/docs/ai/embeddings) и [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) списывались с баланса портала по внутренним ставкам программы квот — со скидками непикового времени; итоговую цену нельзя было увидеть в каталоге моделей.

**Стало**

Расход сверх квоты списывается по базовой цене модели из публичного каталога — той же, что возвращается в поле `pricing` ответа [GET /v1/models](/docs/ai/models/list), включая новые `perCall` и `perMinute` для не-токенных моделей. Скидки непикового времени применяются только к списанию квоты, а не к денежному балансу. Расход в рамках квоты по-прежнему не списывается с баланса портала.

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

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

### NEW-0706-4: Модель эмбеддингов bitrix/embeddings доступна в API

Эндпоинт [POST /v1/embeddings](/docs/ai/embeddings) теперь обслуживается моделью `bitrix/embeddings` — преобразование текста в векторные представления для семантического поиска, кластеризации и retrieval (RAG). Модель бесплатная, тарификация только по входным токенам. Список моделей с поддержкой эмбеддингов — [GET /v1/models](/docs/ai/models/list).

### FIX-0706-5: Деплой честно сообщает об ошибке, если новый билд не занял порт

Развёртывание через [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) теперь проверяет, что порт держит именно новый сервис. Если предыдущий процесс продолжает слушать порт, а новый билд крэш-луп'ит с EADDRINUSE, деплой честно завершается ошибкой вместо ложного успеха; порт, занятый оставшимся процессом того же приложения, при возможности освобождается автоматически.

**Было**

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

**Стало**

Шаг `healthcheck` возвращает ошибку с указанием EADDRINUSE и порта, а шаг `stop_existing` освобождает порт от оставшегося процесса приложения (или предупреждает и продолжает, если освободить нельзя).

### NEW-0706-6: фильтр: оператор $nin (NOT IN) для исключения набора значений

**Было**

Отобрать записи, у которых поле НЕ входит в набор значений, было нельзя: оператор `$in` (IN) поддерживался, а обратного не было. Родные префиксы Битрикс24 `@` (IN) и `!@` (NOT IN) в имени поля (`{ "!@categoryId": [1, 3] }`) не транслировались — такой фильтр по сделкам возвращал `400 UNKNOWN_FILTER_FIELD`.

**Стало**

Добавлен оператор `$nin`: `{ "filter": { "categoryId": { "$nin": [1, 3] } } }` вернёт записи со всеми значениями, кроме перечисленных (NOT IN). Симметричен `$in`. Родные префиксы Битрикс24 `@` / `!@` в имени поля по-прежнему не поддерживаются, но теперь дают понятный `400 INVALID_FILTER_FIELD` с подсказкой перейти на `$in` / `$nin` — вместо невнятной ошибки или молча проигнорированного (и потому возвращавшего весь набор) фильтра.

### BC-0706-7: отдельный код ошибки для слишком длинной команды exec

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

**Было**

Команда длиннее 10 000 символов у [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) отклонялась общим кодом `VALIDATION_ERROR` без указания причины и выхода.

**Стало**

Такой запрос возвращает 400 с отдельным кодом `COMMAND_TOO_LONG` и структурированным `hint`: большие данные и скрипты передаются через [POST /v1/infra/servers/:id/upload](/docs/infra/deploy/upload), затем выполняются `bash /путь/скрипт.sh`. Остальные нарушения схемы по-прежнему возвращают `VALIDATION_ERROR`.

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

Если ваш клиент обрабатывает `VALIDATION_ERROR` этого эндпоинта как общий случай ошибки валидации — добавьте обработку кода `COMMAND_TOO_LONG` (или обрабатывайте любой 400 единообразно).

### NEW-0706-8: подсказка в ошибке таймаута exec

Ошибка `EXEC_TIMEOUT` у [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) теперь несёт структурированное поле `hint` (`reason` / `recovery` / `recoveryAction`): почему процесс был остановлен (по истечении `timeout` процесс-группа завершается принудительно, без grace-паузы) и что делать — запустить длинную операцию фоновой задачей и следить за ней через [GET /v1/infra/servers/:id/logs](/docs/infra/deploy/logs), поднять `timeout` до 600 секунд или использовать режим `?stream=true`. Поле аддитивное: прежний формат `code` / `message` не изменился, подсказка приходит и в JSON-режиме, и в SSE-событии `error`.

## 2026-07-05

### FIX-0705-1: Отправка сообщения в чат — понятная ошибка при пустом тексте

Текст сообщения передаётся в поле `message`. Раньше вызов [POST /v1/chats/{dialogId}/messages](/docs/chats/messages/send) с текстом под неизвестным именем поля (например `{"text": "hi"}`) молча отбрасывал это поле, и Битрикс24 возвращал `422 BITRIX_ERROR` о пустом сообщении — хотя контент был передан.

**Было**

`{"text": "hi"}` → `422 BITRIX_ERROR` о пустом сообщении, без указания причины.

**Стало**

Тот же вызов сразу возвращает `400 MESSAGE_REQUIRED` и перечисляет нераспознанные поля, подсказывая поле `message`. Пустой текст по-прежнему допустим вместе с блоком `attach` (сообщение только с вложением).

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

Корректные вызовы с полем `message` не меняются. Ошибка при неверном имени поля стала точной.

### FIX-0705-2: Ключ в заголовке Authorization: Bearer — понятная ошибка вместо INVALID_SESSION

API-ключ передаётся в заголовке `X-Api-Key`. Раньше, если ключ по ошибке клали в `Authorization: Bearer` (это место — для сессионного токена OAuth-приложения), сервер возвращал `401 INVALID_SESSION`, и интегратор искал проблему в OAuth-сессии, хотя причина была в неверном заголовке.

**Было**

Ключ `vibe_app_*` в `Authorization: Bearer` → `401 INVALID_SESSION`.

**Стало**

Тот же запрос возвращает `401 WRONG_AUTH_SCHEME` с подсказкой: ключ OAuth-приложения (`vibe_app_*`) передаётся в `X-Api-Key`, а `Authorization: Bearer` несёт сессионный токен (`vibe_session_*`) из [POST /v1/oauth/token](/docs/keys-auth); клиенту, который умеет только Bearer, подойдёт личный ключ (`vibe_api_*`). Сессионные токены и личные ключи в Bearer не затронуты.

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

Корректные вызовы с ключом в `X-Api-Key` и сессией в `Authorization: Bearer` не меняются.

### FIX-0705-3: multipart/create отклоняет XSS-опасные типы содержимого для PUBLIC-объектов

**Было**

Для `PUBLIC`-объектов типы содержимого `text/html`, `application/javascript`, `application/x-javascript` и `image/svg+xml` отклонялись при прямой и presigned-загрузке, но не при инициализации многочастевой (multipart) загрузки. Вызов [POST /v1/storage/objects/multipart/create](/docs/storage) с `visibility` = `PUBLIC` и таким типом создавал сессию, и после завершения объект отдавался встроенно в браузере.

**Стало**

[POST /v1/storage/objects/multipart/create](/docs/storage) с `visibility` = `PUBLIC` и XSS-опасным типом содержимого возвращает `415 STORAGE_FORBIDDEN_CONTENT_TYPE` — так же, как прямая и presigned-загрузка. Многочастевая сессия при этом не открывается. `PRIVATE`-объекты по-прежнему допускают любой тип содержимого.

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

Поведение приведено к задокументированному в разделе «Хранилище»: XSS-опасные типы содержимого недопустимы для `PUBLIC`-объектов на всех путях загрузки. Чтобы загрузить такой файл многочастевой загрузкой, используйте `visibility` = `PRIVATE` либо безопасный тип содержимого.

### FIX-0705-4: привязка плейсмента на технический адрес сервера теперь ведёт через платформенный обработчик

**Было**

POST /v1/placements/bind принимал `handler`, указывающий на технический Black Hole-адрес приложения (app-*.vibecode…), и регистрировал его в Битрикс24 как есть. Битрикс24 отправлял iframe плейсмента напрямую на этот адрес, минуя платформу: сессия не выпускалась, и на сервере с доступом «только для пользователей Битрикс24» открытие плейсмента зацикливало авторизацию (на публичном сервере приложение отдавало собственную ошибку 404).

**Стало**

Такой `handler` автоматически переписывается на платформенный обработчик приложения (/v1/bitrix-handler) — плейсмент открывается и авторизуется штатно. В ответе появляются `handlerRewritten: true` и `requestedHandler` с исходным значением. Внешние (не Black Hole) обработчики не изменяются. Если платформенный обработчик приложения не удаётся определить, привязка отклоняется с кодом `PLATFORM_HANDLER_UNRESOLVABLE` вместо регистрации нерабочего адреса. Дополнительно GET /v1/placements помечает уже неправильно привязанный обработчик: `data.handlers[].misbound: true` плюс текстовый `warnings[]`.

Кроме того, если плейсмент уже зарегистрирован в Битрикс24, но отсутствует в списке приложения (рассинхрон — например, после снятия с публикации без отвязки в Битрикс24), привязка больше не завершается ошибкой «Handler already binded»: платформа снимает устаревшую привязку и повторяет запрос один раз, восстанавливая рассинхрон. Если же привязка не проходит по другой причине (например, требуется коммерческий тариф Битрикс24), рабочий плейсмент не снимается.

### NEW-0705-5: Новый код ошибки 402 ai_quota_exhausted на AI-эндпоинтах

При включённом контроле месячной AI-квоты портала запросы [POST /v1/chat/completions](/docs/ai/chat/completions), [POST /v1/embeddings](/docs/ai/embeddings) и [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) могут вернуть 402 с телом `{ success: false, error: { code: "ai_quota_exhausted", type: "insufficient_quota", reason, resetAt? } }`. Поле `reason` различает три случая: `breaker` — сработал часовой предохранитель расходов сверх квоты, `wallet_empty` — квота исчерпана и на балансе портала нет средств, `wallet_off` — расход сверх квоты для портала недоступен. `resetAt` — момент, когда запросы снова начнут проходить (для `wallet_off` при полном отключении может отсутствовать). Пока квота портала не исчерпана, поведение эндпоинтов не меняется.

## 2026-07-04

### BC-0704-1: Перегрузочные отказы: 429/503 вместо 504

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

Перегрузочные отказы сменили HTTP-статусы (коды в теле ответа НЕ изменились — меняется только статус). Правило: **429** — запрос НЕ был обработан, безопасно повторить через `Retry-After` (заголовок теперь ставится всегда); **503** — платформе или апстриму плохо, повторите позже, для write-операций сначала проверьте, применилось ли изменение. Прикладной 504 из API исключён.

**Что изменилось:** `QUEUE_OVERFLOW` 503→429; `QUEUE_TIMEOUT` 504→429; `BITRIX_TIMEOUT` (Bitrix24 не ответил за 15 секунд — для этого кода write мог примениться, перечитайте сущность перед повтором) →503; `ai_provider_timeout` 504→503; `UPSTREAM_TIMEOUT` (веб-поиск `/v1/search` — апстрим-провайдер не ответил вовремя) 504→503; `RUNTIME_TIMEOUT` / `GATEWAY_TIMEOUT` / `WAKE_TIMEOUT` 504→503.

### NEW-0704-2: Новый код перегрузки AI: 429 ai_congested

AI-запросы к платформенному кластеру теперь проходят через admission-гейт: при перегрузке пула ответ — `429` с кодом `ai_congested` в теле и заголовком `Retry-After`. Повтор безопасен (запрос не выполнялся, списания нет). BYOK-ключи и внешние провайдеры гейтом не затрагиваются. По умолчанию гейт выключен — включается платформой.

### BC-0704-3: ключи в режиме «только чтение» (READONLY) больше не выполняют запись на рукописных эндпоинтах

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

**Было**

APP-ключ с режимом доступа «только чтение» (`accessMode: READONLY`) доходил до записи в Битрикс24 на части рукописных эндпоинтов (реквизиты и пресеты, пользовательские поля, timeline pin/note/bind, телефония, почта, диск, бизнес-процессы, приглашение и деактивация пользователя и другие) — гард проверял только скоуп, но не режим доступа ключа.

**Стало**

Любая попытка записи ключом в режиме «только чтение» возвращает `403` с кодом `WRITE_BLOCKED_READONLY_KEY`. Эндпоинты чтения не затронуты.

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

Если интеграция выполняла запись ключом «только чтение», переключите ключ в режим чтения и записи на странице управления ключами.

### FIX-0704-4: деплой galaxy-приложения корректно распаковывает архивы с обёрткой и понятно сообщает о пустом source.content

**Было**

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) для galaxy-приложения (`kind=GALAXY_APP`), где файлы проекта в `source.content` лежали внутри обёрточной папки (типичный zip, собранный в macOS), собирал приложение с пустым контекстом сборки и падал в рантайме с `npm error enoent Could not read package.json`. Если же `source.content` вообще не распаковывался в файлы (передан versionId, путь или пустой архив) — деплой давал ту же непонятную ошибку сборки.

**Стало**

Такие архивы деплоятся корректно: файлы приложения оказываются в корне контекста сборки. А если `source.content` распаковался в пустой контекст, деплой сразу возвращает `GALAXY_APP_BUILD_FAILED` с понятным сообщением о пустом контексте сборки и подсказкой, что `source.content` должен быть base64-архивом (tar.gz или zip) файлов вашего проекта, а не versionId и не путём.

## 2026-07-03

### FIX-0703-1: привязка размещения по ключу разработчика больше не возвращает 500 после цикла снятия и повторной публикации

**Было**

`POST /v1/placements/bind` для приложения, управляемого ключом разработчика (тип `local.*`), мог стабильно возвращать `500` (`BITRIX_UNAVAILABLE`, `INTERNAL_SERVER_ERROR`) при привязке размещения (например `CRM_DEAL_DETAIL_TAB` или `CRM_CONTACT_DETAIL_TAB`) после того, как приложение снимали с публикации и публиковали заново. Прежняя регистрация размещения на стороне Битрикс24 сохранялась, и попытка зарегистрировать поверх неё новую завершалась внутренней ошибкой. Повторные вызовы давали ту же ошибку.

**Стало**

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

### FIX-0703-2: /v1/sites — фильтр по типу «база знаний» (KNOWLEDGE) и «группа» (GROUP) больше не игнорируется

**Было**

`POST /v1/sites/search` (а также `GET /v1/sites` и `POST /v1/sites/aggregate`) с `filter[type]=KNOWLEDGE` или `filter[type]=GROUP` молча возвращал обычные сайты-лендинги (`PAGE` / `STORE` / `VIBE`) вместо баз знаний или страниц групп. Причина на стороне Битрикс24: метод `landing.site.getList` привязывает фильтр по `TYPE` к внутренней области (`scope`), и без параметра `scope` типы `KNOWLEDGE` / `GROUP` не входят в область по умолчанию — фильтр по типу тихо отбрасывался. Обойти можно было только вручную, добавив `scope` (см. [Список сайтов](/docs/entities/sites/list)).

**Стало**

Если в фильтре указан один такой тип и `scope` не передан явно, Вайбкод сам подставляет соответствующую область (`type=KNOWLEDGE` → `scope=KNOWLEDGE`, `type=GROUP` → `scope=GROUP`) — и запрос возвращает именно базы знаний / страницы групп. Явно переданный `scope` всегда в приоритете и не переопределяется. Если тип задан списком или оператором (например `{"type":{"$in":["KNOWLEDGE","PAGE"]}}`), где одну область выбрать нельзя, в `meta.warnings` приходит подсказка с кодом `TYPE_REQUIRES_SCOPE`.

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

Действий не требуется. Запросы с `filter[type]=PAGE` / `STORE` / `VIBE` и запросы без фильтра по типу работают как раньше. `MAINPAGE` — это область, а не тип сайта (её сайты имеют тип `VIBE`), поэтому из фильтра по типу область `MAINPAGE` не выводится. Для `/v1/pages` поведение не изменилось.

### FIX-0703-3: агрегация страниц и сайтов снова возвращает count

**Было**

[POST /v1/pages/aggregate](/docs/entities/pages/aggregate) и [POST /v1/sites/aggregate](/docs/entities/sites/aggregate) возвращали `count: 0` и `meta.totalRecords: 0` даже при наличии страниц и сайтов — во всех формах: без фильтра, с фильтром, с выражением `count` и как верхний `count` при `groupBy`. Счётчики отдельных групп при `groupBy` при этом были корректными.

**Стало**

`count` и `meta.totalRecords` отражают фактическое число записей; верхний `count` при `groupBy` равен сумме счётчиков групп.

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

Действий не требуется — ответ стал корректным.

### NEW-0703-4: Параметры качества и таймстампов в расшифровке аудио

Расшифровка аудио [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) принимает пять новых необязательных полей. Качество распознавания: `prompt` — контекстная подсказка (тема разговора, стиль, правильное написание терминов, до 2000 символов), `hotwords` — список спец-слов через запятую (редкие термины, бренды, имена, до 500 символов), `vad_filter` — фильтр тишины перед распознаванием (меньше галлюцинаций на записях с паузами). Управление результатом: `temperature` — температура декодера от 0 до 1, `timestamp_granularities[]` — детализация таймстампов `word`/`segment` (только с `response_format=verbose_json`; со значением `word` каждый сегмент дополняется массивом `words` с таймингом и вероятностью каждого слова). Поля передаются в `multipart/form-data` рядом с `file` и совместимы с OpenAI-контрактом. Невалидные значения отклоняются кодами `invalid_prompt`, `invalid_hotwords`, `invalid_temperature`, `invalid_vad_filter`, `invalid_timestamp_granularities`.

### FIX-0703-5: Приложения, созданные через API, корректно открываются как плейсменты

**Было**

Часть приложений, созданных через [POST /v1/apps](/docs/apps/create), не открывалась при вызове плейсмента в Битрикс24 — вместо интерфейса приложения пользователь видел ошибку распознавания приложения.

**Стало**

Созданные приложения корректно резолвятся и открываются как плейсмент-виджеты в Битрикс24. Ответ создания не изменился — приложение сразу пригодно для публикации и привязки плейсментов.

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

Ничего менять не нужно. Ранее не открывавшиеся приложения нужно пересоздать (удалить и создать заново) — новое приложение открывается корректно.

### NEW-0703-6: удаление ключа блокируется при привязанном агенте или боте

[DELETE /v1/keys/:id](/docs/keys-auth) теперь возвращает `409` с кодом `KEY_HAS_LINKED_AGENT`, если ключ является управляющим ключом живого AI-агента или управляемого бота.

**Было**

Удаление такого ключа осиротляло агента и каскадно удаляло бота вместе с его токеном — идентичность бота в Bitrix24 терялась безвозвратно, без предупреждения.

**Стало**

Тело ответа: `{ success: false, error: { code: "KEY_HAS_LINKED_AGENT", message, details: { linkedAgentCount, linkedBotCount, agents: [{ id, name, status }] } } }`. Перед удалением перепривяжите ресурсы к другому ключу либо удалите сам агент/бот; для восстановления доступа осиротевшему агенту используйте кабинетное действие «Восстановить доступ». Проверка идёт до синхронизации с Bitrix24 — при `409` учётные данные на стороне Bitrix24 не затрагиваются. Сиблинг существующего `KEY_HAS_ACTIVE_SERVERS`.

### NEW-0703-7: История изменений задачи и стадии канбана

Добавлены два метода только для чтения (скоуп `task`). `GET /v1/tasks/:taskId/history` возвращает историю изменений задачи целиком за один вызов: смены стадий канбана, перемещения в спринт и бэклог, статусы и другие события. Фильтр по типу события — `?field=STAGE` (несколько типов через запятую, например `?field=STAGE,MOVE_TO_SPRINT`); сортировка — `?order=asc` или `?order=desc` (по умолчанию по возрастанию даты создания). Каждая запись содержит `id`, `createdDate`, `field`, объект `value` с прежним и новым значением и `user` с идентификатором автора. `GET /v1/tasks/stages/:entityId` возвращает текущие колонки канбана рабочей группы (`N`) или личного плана (`0`).

### FIX-0703-8: bizproc-activities: понятная ошибка вместо «Wrong handler URL» при отсутствии handler

**Было**

POST /v1/bizproc-activities без поля `handler` (или с URL обработчика, ошибочно переданным в поле `handlerUrl`) возвращал непрозрачную ядровую ошибку `422 BITRIX_ERROR: Wrong handler URL`.

**Стало**

Поля `code`, `name`, `handler` проверяются до вызова Битрикс24: при отсутствии `handler` метод возвращает `400 MISSING_REQUIRED_FIELDS` с сообщением `Body field "handler" is required to create bizprocActivity`, подсказывая правильное имя поля. Успешные вызовы с корректным полем `handler` не затронуты.

### FIX-0703-9: POST /v1/batch — ответы update и delete теперь нормализованы, как create и get

**Было**

В глобальном пакетном вызове [POST /v1/batch](/docs/batch) подвызов update возвращал ответ в сырой обёртке (вложенный объект вместо плоской записи), а подвызов delete возвращал пустой массив без признака успеха. Это расходилось с create и get в том же эндпоинте и с одиночным PATCH /v1/{entity}/:id, которые отдают плоскую нормализованную запись.

**Стало**

Подвызов update возвращает плоскую нормализованную запись (camelCase-поля) — как create, get и одиночный PATCH. Подвызов delete возвращает признак успеха вида { id, deleted: true }.

### FIX-0703-10: POST /v1/{entity}/batch — create, update и delete снова работают для сущностей CRM

**Было**

По-сущностный пакетный вызов [POST /v1/{entity}/batch](/docs/batch) с действием create, update или delete для сделок, контактов, компаний, лидов, предложений и счетов возвращал по каждому элементу ошибку «Could not find value for parameter {entityTypeId}», и запись не создавалась, не менялась и не удалялась. Одиночные вызовы (POST /v1/{entity}) и глобальный POST /v1/batch на тех же сущностях при этом работали.

**Стало**

По-сущностный пакетный create, update и delete для этих сущностей выполняется корректно — так же, как одиночные вызовы и глобальный пакетный эндпоинт.

### FIX-0703-11: Пропуск поля model в чате снова подставляет модель по умолчанию

**Было**

[POST /v1/chat/completions](/docs/ai/chat/completions) без поля `model` возвращал `400 no_default_model` на порталах, где модель по умолчанию не была явно назначена, — даже когда на портале была доступная бесплатная модель. При этом `GET /v1/me` мог показывать в `defaultModel` модель, которую нельзя вызвать.

**Стало**

Если поле `model` не передано, запрос автоматически берёт первую доступную для вызова модель портала — как и описано в документации. `GET /v1/me` в поле `defaultModel` теперь всегда показывает вызываемую модель, ту же самую, которую подставит чат.

### FIX-0703-12: include по связанным сущностям снова возвращает сами сущности, а не null

**Было**

`GET /v1/deals/:id?include=contact,company` возвращал `_included.company` = `null`, а `_included.contacts` — только метаданные связи (`sort`/`isPrimary`/`roleId`) без полей самой сущности, хотя сделка ссылалась на существующие компанию и контакты.

**Стало**

`_included.company` содержит полный объект компании, а `_included.contacts` — полные объекты контактов (`id`, `name`, …) вместе с метаданными связи. Исправление затрагивает `include` по всем CRM-сущностям (`deals`, `leads`, `quotes` и другим).

### FIX-0703-13: поиск сделок отклоняет неизвестное поле фильтра вместо тихой отдачи всех строк

**Было**

[GET /v1/deals](/docs/entities/deals/list) и [POST /v1/deals/search](/docs/entities/deals/search) с неизвестным полем фильтра (опечатка в имени, поле не из схемы) молча пропускали его в Битрикс24, который игнорирует незнакомые ключи фильтра и возвращает весь набор сделок с ответом `200`. Клиент, отправивший фильтр с ошибкой в имени поля, получал не пустой результат и не `400`, а полную таблицу — как будто фильтр применился.

**Стало**

Неизвестное поле фильтра теперь отклоняется до вызова Битрикс24 ответом `400` с кодом `UNKNOWN_FILTER_FIELD` и списком доступных полей в сообщении — как уже делают `contacts`, `companies`, `leads`, `quotes`, `invoices` и `items`. Объявленные поля (включая псевдонимы вроде `amount`), пользовательские поля (`UF_CRM_*` и `ufCrm*`) и `id` работают как прежде.

### FIX-0703-14: Каталог: список и поиск отдают чистый 400 при отсутствии iblockId

**Было**

Список и поиск [GET /v1/catalog-products](/docs/entities/catalog-products/list), [POST /v1/catalog-products/search](/docs/entities/catalog-products/search), [GET /v1/catalog-sections](/docs/entities/catalog-sections/list) и [POST /v1/catalog-sections/search](/docs/entities/catalog-sections/search) без `iblockId` в фильтре доходили до Битрикс24 и возвращали мутный `422 BITRIX_ERROR` («Field iblockId is not specified in the filter»).

**Стало**

Каталожные список и поиск требуют `iblockId` в фильтре — при отсутствии сразу возвращается `400 MISSING_REQUIRED_FILTER` с примером, запрос до Битрикс24 не доходит.

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

Менять ничего не нужно — корректные запросы (с `filter[iblockId]`) работают как прежде. Изменились только код и ясность ошибки для запросов, которые и так не выполнялись.

### FIX-0703-15: Контакты — реальные поля дат createdTime/updatedTime вместо фантомных createdAt/updatedAt

**Было**

[GET /v1/contacts/fields](/docs/entities/contacts/fields) объявлял поля `createdAt` и `updatedAt`, но в ответах контактов они никогда не появлялись — Битрикс24 отдаёт даты под ключами `createdTime`/`updatedTime`, и именно они были в теле контакта. При этом фильтр и `select` по `createdTime` (имя, которое клиент реально видит в ответе) отклонялись как неизвестное поле, а по фантомному `createdAt` — «работали», хотя само поле в ответе не читалось.

**Стало**

Схема объявляет реальные ключи `createdTime` и `updatedTime` (тип datetime, только чтение): они присутствуют в `/fields`, фильтр и `select` по ним работают, а значение нормализуется к ISO-8601 в UTC. Фантомные `createdAt`/`updatedAt` больше не объявлены — фильтр или `select` по ним возвращает `400 UNKNOWN_FILTER_FIELD`.

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

Чтение не меняется — ключи `createdTime`/`updatedTime` и раньше были в теле ответа, теперь ещё и нормализованы. Если вы фильтровали или проецировали контакты по `createdAt`/`updatedAt`, замените имена на `createdTime`/`updatedTime`.

### FIX-0703-16: Список открытых линий теперь учитывает параметр limit

**Было**

[GET /v1/openline-configs](/docs/openlines/config/list) и одноимённый поиск игнорировали `limit`: нижележащий метод Битрикс24 возвращает весь набор конфигураций, а обёртка отдавала все строки. Поле `hasMore` при этом было неверным — `false`, даже когда за пределами запрошенного `limit` оставались ещё записи.

**Стало**

Ответ обрезается до `limit` на стороне обёртки. `hasMore: true`, когда Битрикс24 вернул больше записей, чем запрошенный `limit` (есть следующая страница), иначе `false`. `total` — число записей в текущем окне.

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

Ответ на запрос с `limit` теперь содержит не больше `limit` записей. Клиенты, полагавшиеся на возврат всего набора без учёта `limit`, увидят усечённый список — используйте `offset` для следующей страницы.

## 2026-07-02

### NEW-0702-1: фильтр и сортировка задач по реальному статусу (realStatus)

`GET /v1/tasks`, `POST /v1/tasks/search` и `POST /v1/tasks/aggregate` теперь принимают поле `realStatus` в `filter` (а список и поиск — ещё и в `sort`) — фильтрация по фактически сохранённому статусу задачи: 1 — новая, 2 — ждёт выполнения, 3 — выполняется, 4 — ожидает контроля, 5 — завершена, 6 — отложена, 7 — отклонена. Раньше `filter[realStatus]` молча игнорировался и запрос возвращал весь набор.

В отличие от `filter[status]`, который на стороне Bitrix24 работает как виртуальный (мета-)фильтр (значения −1 просрочена, −2 не просмотрена, −3 почти просрочена) и не совпадает со значением поля `status` в ответе, `realStatus` фильтрует именно по хранимому статусу. Поле доступно только для чтения (статус меняется через `status`) и участвует только в `filter`/`sort` — в ответе реальный статус задачи уже отдаётся в поле `status`.

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

**Было**

Повторный [POST /v1/infra/servers](/docs/infra/servers/create) с тем же `name` после неудачного деплоя создавал новый слот приложения. Упавшие слоты накапливались и удалялись автоочисткой только через 7 дней.

**Стало**

Если у владельца ключа на портале уже есть слот с тем же `name` в статусе `error` (или созданный, но так и не получивший ни одного деплоя), повторный вызов возвращает этот же слот: его `id` сохраняется, ошибка и лог сборки сбрасываются, статус возвращается в `provisioning` — деплойте в него. Слоты, в которые ни разу не отправляли код, теперь удаляются автоочисткой через 24 часа вместо 7 дней (слоты с упавшей сборкой по-прежнему хранятся 7 дней вместе с логом сборки).

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

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

### FIX-0702-3: ключи «только чтение» больше не пишут через /v1/bots

**Было**

Ключ API в режиме «только чтение» (`accessMode: READONLY`) мог выполнять операции записи через эндпоинты бота ([POST /v1/bots](/docs/bots/management/create), отправка и удаление сообщений, добавление участников в чат, регистрация и удаление бота и другие) — вызов возвращал 200 вместо 403. Остальные проксирующие поверхности Битрикс24 такие записи уже блокировали.

**Стало**

Запись через `/v1/bots/*` ключом «только чтение» возвращает 403 с кодом `WRITE_BLOCKED_READONLY_KEY`. Операции чтения не затронуты, включая получение контекста сообщения ([GET /v1/bots/:botId/messages/:messageId/context](/docs/bots/messages/context)) и скачивание файла ([GET /v1/bots/:botId/files/:fileId](/docs/bots/files/download)).

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

Если бот-интеграции нужна запись — переключите ключ в режим «чтение и запись» в разделе /keys.

### FIX-0702-4: include=storage у папок теперь резолвится

**Было**

`GET /v1/folders/:id?include=storage` (и список `GET /v1/folders?parentId=...&include=storage`) не добавляли `_included` в ответ, хотя `GET /v1/folders/fields` объявляет для связи `storage` признак `includable: true`.

**Стало**

Связанное хранилище резолвится: в ответе появляется `_included.storage` с карточкой хранилища, найденной по `storageId`. Связь описана в [GET /v1/folders/fields](/docs/entities/folders/fields).

### FIX-0702-5: PAGE_BACKGROUND_WORKER: привязка больше не падает с 500

**Было**

`POST /v1/placements/bind` для placement `PAGE_BACKGROUND_WORKER` подставлял обязательный для Битрикс24 параметр `options.errorHandlerUrl` только когда вызов шёл через OAuth-сессию. Если приложение привязывалось по ключу разработчика или на коробочном портале, параметр не добавлялся и Битрикс24 отвечал 500 (`BITRIX_UNAVAILABLE`, «Field errorHandlerUrl is empty»), хотя остальные placement привязывались нормально.

**Стало**

Для `PAGE_BACKGROUND_WORKER` значение `options.errorHandlerUrl` по умолчанию подставляется равным `handler` независимо от способа привязки. Явно переданный `options.errorHandlerUrl` по-прежнему имеет приоритет. В ответе поле `options` теперь отражает применённое значение (с подставленным `errorHandlerUrl`).

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

Действий не требуется — вызов, который раньше возвращал 500, теперь проходит.

### FIX-0702-6: POST /search сообщает об отсутствии обязательных параметров чистой ошибкой

**Было**

`POST /v1/{entity}/search` для сущностей, чей метод списка Битрикс24 требует обязательные параметры, при их отсутствии не проверял этого и передавал запрос в Битрикс24 как есть. Наружу утекала сырая ошибка Битрикс24 (`BITRIX_ERROR`, например «Invalid value of parameter [ `$id` ]» или «Не задан обязательный параметр `type`»), тогда как у эквивалентного `GET`-списка та же ситуация давала понятный `400 MISSING_REQUIRED_PARAMS`. Затрагивало [POST /v1/calendar-events/search](/docs/entities/calendar-events/search) (нужен `type`), [POST /v1/files/search](/docs/entities/files) (нужен `folderId`) и [POST /v1/folders/search](/docs/entities/folders) (нужен `parentId`).

**Стало**

`POST /v1/{entity}/search` проверяет обязательные параметры до вызова Битрикс24 — так же, как это давно делает `GET`-список. При отсутствии параметра приходит `400` с кодом `MISSING_REQUIRED_PARAMS` и перечнем недостающих полей, без обращения к Битрикс24. Обязательный параметр можно передать в `filter`, а параметр-родитель (`folderId` для файлов, `parentId` для папок) — также на верхнем уровне тела запроса.

### NEW-0702-7: Цена research в самоописании ключа и сумма к пополнению в ответе 402

[GET /v1/me](/docs/keys-auth) теперь отдаёт `cost` у каждого провайдера в блоке `webResearch.providers[]` — по образцу блока `webSearch`. Поле несёт цену режима research в Ꝟ (`cost.research`) и валюту (`cost.currency`), так что агент видит стоимость глубокого поиска прямо в самоописании ключа, без отдельного вызова.

Ответ `402` при недостатке средств (`INSUFFICIENT_BALANCE`, а также `BILLING_FROZEN`) на [POST /v1/search](/docs/search/run) и [POST /v1/research](/docs/search/research) теперь содержит поле `required` — сумму в Ꝟ, необходимую для запроса. Прежние поля `userMessage` и `hint` не изменились.

Клиентам со строгой валидацией схемы по `additionalProperties` нужно учесть новые поля ответа.

### NEW-0702-8: POST /v1/triggers/fire поддерживает счета (SmartInvoice)

Эндпоинт [POST /v1/triggers/fire](/docs/automation/triggers/fire) принимает новое значение `entityType` — `invoice`. Передайте `entityType: "invoice"` и `entityId` счёта (его выдаёт [GET /v1/invoices](/docs/entities/invoices)), чтобы запустить триггер автоматизации по смарт-счёту. Прежние значения (`deal`, `lead`, `contact`, `company`, `quote`, `item`) работают как раньше.

Раньше запустить триггер по счёту было нельзя, а попытка через `entityType="item"` с `entityTypeId=31` отклонялась сообщением, которое уводило в тупик. Теперь `item` с зарезервированным `entityTypeId` (в том числе 31) подсказывает перейти на соответствующий `entityType` — для счёта это `invoice`.

### NEW-0702-9: GET /v1/ai/usage отдаёт длительность аудио по моделям транскрибации

[GET /v1/ai/usage](/docs/ai/consumption/usage) в блоке `byModel[]` теперь возвращает поле `audioSeconds` — суммарное количество секунд аудио, переданных на транскрибацию по каждой модели за выбранный период. Поле заполняется для вызовов speech-to-text (Whisper) и равно `0` для текстовых моделей, где длительность аудио неприменима.

Поле аддитивное, существующие интеграции продолжают работать без изменений.

### BC-0702-10: categories: code и isDefault помечены read-only (были phantom-writable)

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

**Было**

`GET /v1/categories/:entityTypeId/fields` объявлял `code` и `isDefault` записываемыми (`readonly: false`), но запись этих полей в `crm.category.add`/`update` молча игнорировалась (значения не сохранялись, ответ `200`).

**Стало**

Оба поля помечены `readonly: true`. `/fields` теперь честно показывает их как read-only, а попытка записать `code` или `isDefault` возвращает `400 READONLY_FIELD` вместо тихой потери данных.

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

Раньше передача `code`/`isDefault` в теле `POST`/`PATCH` `/v1/categories/:entityTypeId` принималась (`200`, значения молча игнорировались). Теперь такой запрос возвращает `400 READONLY_FIELD`. Уберите `code` и `isDefault` из тела запросов create/update категорий — на запись эти поля больше не принимаются.

### BC-0702-11: telephony-lines: поле crmAutoCreate нормализовано в boolean и появилось в /fields

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

**Было**

`GET /v1/telephony-lines/fields` отдавал только `number`, `serverName`, `name`. Поле автосоздания CRM протекало в ответах списка сырым UPPER-именем `CRM_AUTO_CREATE` строкой `"Y"`/`"N"` — единственное UPPER-поле среди camelCase, и его не было в `/fields`. На запись camelCase `crmAutoCreate` молча отбрасывался.

**Стало**

Поле объявлено как `crmAutoCreate` (boolean). Теперь оно присутствует в `/fields`, в ответах `list` приходит нормализованным (`true`/`false`) вместо сырого `"Y"`/`"N"`, а на `create`/`update` принимается camelCase boolean (сырое UPPER-имя ещё принимается на запись для совместимости). Клиенты, читавшие `data[].CRM_AUTO_CREATE`, должны перейти на `data[].crmAutoCreate` (boolean).

### NEW-0702-12: workgroups: раскрыта операция aggregate и groupBy по полям

**Было**

`POST /v1/workgroups/aggregate` работал, но нигде не был заявлен: операции не было в машинном индексе `/v1/guide`, а `groupBy` возвращал `400` на любом поле (`Available: .`), потому что список агрегируемых полей был пуст.

**Стало**

Объявлен список агрегируемых полей: `membersCount` (числовые sum/avg/min/max) плюс категориальные `active`, `isProject`, `ownerId` для группировок. Теперь операция видна в `/v1/guide` и `/fields`, а `groupBy` по этим полям работает.

### FIX-0702-13: /fields: полнота метаданных у doc-templates и bookings

**Было**

`GET /v1/doc-templates/fields` не содержал полей `isDefault` и `productsTableVariant`, хотя они приходят в ответах списка. У `GET /v1/bookings/fields` обязательные `resourceIds` и `datePeriod` не были помечены `required`, поэтому их обязательность не была видна в схеме.

**Стало**

`doc-templates`: объявлены `isDefault` и `productsTableVariant` (только чтение) — теперь состав `/fields` совпадает с ответами. `bookings`: `resourceIds` и `datePeriod` помечены `required: true`, обязательность видна в `/fields`.

### FIX-0702-14: orders: /fields синхронизирован с ответом, убран псевдо-ключ order, companyId фильтруется

**Было**

`GET /v1/orders/fields` содержал лишний псевдо-ключ `order` (артефакт разбора `sale.order.getFields`) и не содержал полей, реально приходящих в ответах: `companyId`, `clients`, `dateMarked`, `personTypeXmlId`, `statusXmlId`, `version`. Из-за отсутствия `companyId` в схеме фильтр по нему падал с `UNKNOWN_FILTER_FIELD`.

**Стало**

Состав `/fields` теперь собирается из схемы: псевдо-ключ `order` убран, объявлены шесть недостающих полей (`companyId` — записываемое число; `clients` — объект, только чтение, приходит в `get`; `dateMarked`/`personTypeXmlId`/`statusXmlId`/`version` — только чтение). Фильтр и сортировка по `companyId` теперь работают.

### FIX-0702-15: users: limit > 50 теперь работает, meta.hasMore честный

**Было**

`GET /v1/users?limit=500` возвращал только 50 записей, хотя `meta.total` показывал больше. `meta.hasMore` всегда был `false` — документированная пагинация по `hasMore` молча теряла данные за первой страницей.

**Стало**

Сущность опирается на легаси-метод `user.get` (без суффикса `.list`), поэтому авто-паджинатор не включался. Добавлен флаг `paginateViaStart` (как у отделов): при `limit > 50` идёт многостраничная загрузка через `start`, а `meta.hasMore` отражает реальное наличие следующих записей.

### FIX-0702-16: POST /v1/batch отклоняет отключённые операции записи и прокидывает обязательные параметры списка

**Было**

Глобальный [POST /v1/batch](/docs/batch) с действием create, update или delete для сущности, у которой эта операция отключена (например openline-configs — запись вынесена в отдельные роуты), выполнял вызов напрямую в Bitrix24 в обход нормализации и мог тихо создать или изменить запись. Отдельно: действие list или search для сущности с обязательными параметрами метода (например calendar-events — type и ownerId) возвращало AUTO_PAGINATION_FAILED «missing required parameter», хотя прямой запрос списка с теми же параметрами работал. Кроме того, batch-list для folders и files отправлял родительскую папку под именем parentId или folderId, которое метод disk.folder.getchildren игнорирует, поэтому список тихо возвращался не по той папке; а batch-list для calendar-events с лишним ключом filter молча прокидывал его в Bitrix24, и метод возвращал весь календарь без ошибки.

**Стало**

Отключённая операция записи в под-вызове отклоняется с кодом ACTION_NOT_SUPPORTED до обращения к Bitrix24 — так же, как в POST /v1/{entity}/batch. Обязательные параметры списка и параметры верхнего уровня метода прокидываются в Bitrix24 с исходными именами, поэтому batch-list работает так же, как прямой список, а при их отсутствии возвращается понятный MISSING_REQUIRED_PARAMS вместо сырой ошибки Bitrix24. Для folders и files родительская папка приводится к имени id, которого ждёт метод, поэтому batch-list возвращается по нужной папке. Для сущностей, у метода которых нет конверта filter (calendar-events), лишний ключ filter теперь отклоняется с кодом UNSUPPORTED_FILTER до обращения к Bitrix24 — так же, как в прямом списке.

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

Ничего менять не нужно. Если под-вызов batch раньше опирался на выполнение отключённой операции записи — переведите его на выделенный роут сущности. Для batch-list по сущностям с обязательными параметрами (calendar-events) передавайте type и ownerId в params под-вызова. Если batch-list для calendar-events использовал filter — уберите его или перенесите в параметры верхнего уровня, иначе под-вызов вернёт UNSUPPORTED_FILTER.

### NEW-0702-17: GET /:entity/fields отдаёт label и description полей

Ответ `GET /v1/{entity}/fields` теперь по каждому полю может нести человекочитаемое короткое имя `label` и пояснение `description` по-русски — раньше поле описывалось только парой `{type, readonly}`. Это позволяет ИИ-агенту и интерфейсу показывать название и назначение поля, не обращаясь к документации. Поля добавлены для сущностей: Отделы, Смарт-процессы, Хранилища, Папки, Файлы, Рабочие группы, Шаблоны документов, Бронирования, События календаря, Задачи, Реквизиты, Сотрудники. Для Сотрудников дополнительно исправлена подстановка подписей: `GET /v1/users/fields` теперь возвращает настоящие названия полей Битрикс24 из `user.fields` вместо технических кодов. Изменение аддитивное: новые ключи появляются дополнительно к прежним, существующие вызовы продолжают работать без изменений.

### FIX-0702-18: ключ только со скоупами vibe:* теперь выписывается, а не падает

**Было**

Создание ключа авторизации `POST /v1/keys`, у которого все запрошенные права — внутренние права Вайбкода `vibe:*` (например только `vibe:infra`), на портале с режимом dev-key (коробочная Битрикс24 или подключённый облачный портал) отклонялось с `502 DEVKEY_MINT_FAILED`. Права `vibe:*` не передаются в Битрикс24, поэтому набор прав для вебхука Битрикс24 оказывался пустым, и Битрикс24 отклонял выписку, требуя хотя бы одно право.

**Стало**

Ключ только с правами `vibe:*` теперь выписывается успешно. Вебхук Битрикс24 для него не создаётся — он не нужен, такой ключ не обращается к REST Битрикс24, — а ключ работает с внутренними возможностями Вайбкода по своим правам.

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

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

### FIX-0702-19: пагинация /v1/storages — limit больше 50 отдаёт все записи, meta.hasMore корректен

**Было**

`GET /v1/storages` с `limit` больше 50 возвращал максимум 50 записей, а `meta.hasMore` был всегда `false` — даже когда в портале записей больше. Клиент с `?limit=50` при 489 хранилищах видел `hasMore: false` и не знал, что нужно запросить следующую страницу. То же на `POST /v1/storages/search` и в `/v1/batch`.

**Стало**

`limit` больше 50 проходит авто-пагинацию (как у остальных списков) и отдаёт запрошенное число записей, а `meta.hasMore` равен `(offset + число записей) < meta.total` и для `limit` не больше 50. Изменение распространяется на `GET /v1/storages`, `POST /v1/storages/search` и список `storages` в `/v1/batch`.

Примечание: корректный расчёт `meta.hasMore` для ответов списка при `limit` не больше 50 теперь распространяется на все сущности (`GET /v1/{entity}` и `POST /v1/{entity}/search`), а не только на `storages` — ранее на этом пути `meta.hasMore` был всегда `false`.

### FIX-0702-20: infra: кириллица в displayName при создании приложения

**Было**

При [POST /v1/infra/servers](/docs/infra) с кириллическим `displayName` название могло сохраниться как последовательность знаков вопроса (`??????`) — повреждение кодировки при передаче в Битрикс24.

**Стало**

Название передаётся в кодировке UTF-8, кириллица сохраняется корректно.

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

Действий не требуется. Кириллические названия больше не искажаются.

### FIX-0702-21: items: фильтрация по полям связи parentId<N>

**Было**

Фильтр по динамическому полю связи (например `parentId2` — связанная сделка) на [GET /v1/items/:entityTypeId](/docs/entities/items/list) и [POST /v1/items/:entityTypeId/search](/docs/entities/items/search) отклонялся с `400 UNKNOWN_FILTER_FIELD`, хотя поле присутствует в `GET /v1/items/:entityTypeId/fields` и возвращается в ответах.

**Стало**

Поля вида `parentId<N>` принимаются в фильтре и передаются в запрос как есть. Найти смарт-процесс, связанный с конкретной родительской сущностью, теперь можно напрямую через обёртку items.

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

Действий не требуется. Запросы, ранее получавшие `400`, теперь отрабатывают.

### FIX-0702-22: смарт-процессы: сохранение списка значений и названия пользовательского поля

**Было**

На [POST /v1/items/:entityTypeId/userfields](/docs/userfields/smart-processes) поле-список (`userTypeId: enumeration`) создавалось, но варианты значений не сохранялись (список приходил пустым), а название поля, переданное строкой, в интерфейсе оставалось пустым.

**Стало**

Варианты значений принимаются как в `enum`, так и в `list` и корректно сохраняются. Название, переданное строкой, автоматически оборачивается в языковую карту и заполняет подписи в форме, колонке и фильтре.

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

Действий не требуется. Ранее «молча терявшиеся» значения списка и название теперь сохраняются.

### FIX-0702-23: req-family: /fields у адресов и preset-fields отдают ключи в camelCase

**Было**

`GET /v1/addresses/fields` и `GET /v1/requisite-presets/:presetId/fields/schema` возвращали описание полей с сырыми ключами в UPPER_SNAKE_CASE (`TYPE_ID`, `ADDRESS_1`, `FIELD_NAME`, `IN_SHORT_LIST`), хотя данные этих сущностей (`GET /v1/addresses`, список полей пресета) уже приходили в camelCase — схема не совпадала с реальными именами в данных.

**Стало**

Оба эндпоинта нормализуют ключи описания в camelCase (`typeId`, `address1`, `fieldName`, `inShortList`), как и весь остальной V1. Внутренние дескрипторы поля (`type`, `isRequired`, `isReadOnly`, `title`) не меняются.

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

Ключи в ответе `/fields` теперь совпадают с именами полей в данных. Клиент, читавший camelCase-имена из данных, получает согласованную схему; действий не требуется.

## 2026-07-01

### FIX-0701-1: Загрузка файла на Диск принимает файлы больше 1 МБ

**Было**

[POST /v1/files/upload](/docs/entities/files/upload) отклонял тело запроса больше ~1 МБ ошибкой `FST_ERR_CTP_BODY_TOO_LARGE`. Файл передаётся в base64 в JSON-теле, а base64 раздувает размер примерно на треть — поэтому даже файл 1,1 МБ не проходил. Способа загрузить файл крупнее не было.

**Стало**

Лимит тела этого маршрута поднят до 70 МБ — этого хватает на файл около 50 МБ с учётом base64 и JSON-обёртки (запись звонка, типовые вложения). Битрикс24 по-прежнему применяет собственное ограничение на размер файла Диска: при его превышении ошибка приходит в стандартном конверте. Для файлов в сотни МБ нужен отдельный способ загрузки (multipart / presigned) — он пока не реализован.

### NEW-0701-2: иконка приложения сервера: загрузка SVG, анонимная отдача, фавикон

Появился способ задать иконку приложения для сервера. `POST /v1/infra/servers/:id/icon` (multipart/form-data, поле `file`, только SVG до 256 КБ, без скриптов, обработчиков событий и внешних ссылок) загружает иконку; она отдаётся анонимно по стабильному `GET /api/server-icons/:id` и показывается в каталоге приложений Bitrix24. Чтобы иконка стала фавиконом во вкладке браузера, впишите `<link rel="icon" type="image/svg+xml" href="<базовый-URL>/api/server-icons/:id">` в HTML приложения во время сборки — после этого перезаливка иконки обновляет каталог и фавикон автоматически. Формат, требования и порядок — [Иконка приложения](/docs/infra/app-icon).

### NEW-0701-3: Профиль текущего пользователя сообщает права администратора

Ответ [GET /v1/users/me](/docs/entities/users) теперь возвращает рабочее поле `isAdmin`: `true` — пользователь администратор портала, `false` — нет, `null` — определить не удалось (временный сбой; профиль при этом всё равно возвращается). Поле пригодно для серверной проверки прав в вашем бэкенде. На `GET /v1/users/:id` и в списке пользователей поле по-прежнему недоступно — вердикт отдаётся только для текущего пользователя сессии.

### NEW-0701-4: Расширенный статический контракт полей в /v1/guide и указатель schema-discovery в /v1/me

По каждой сущности в ответе [GET /v1/guide](/docs/keys-auth) добавлено поле `data.entities[].fieldsDetailed` — расширенный статический контракт полей: `type`, `readonly`, `required`, `createOnly` и декодирование `enum` (например, значения `status` и `priority` у задач). Оно доступно по одному заголовку `X-Api-Key`, без сессии, и предназначено для маппингов и кодогенерации до появления пользовательской сессии. Компактное поле `fields` сохранено без изменений.

Ответ `GET /v1/me` для ключа авторизации без пользовательской сессии (без заголовка `Authorization: Bearer`) теперь содержит блок `schemaDiscovery` — указатель, где брать статическую схему без сессии (`/v1/guide`) и как получить живые и пользовательские поля (Bearer-сессия или персональный ключ). Эндпоинты `GET /v1/<entity>/fields` и `GET /v1/userfields/*` не изменились.

### BC-0701-5: categoryId в ответе постов теперь массив чисел

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

**Было**

[GET /v1/posts](/docs/feed/posts/list) отдавал `categoryId` с типом, зависящим от количества категорий поста: `null` без категорий, число (`11`) для одной, строка со списком ID через запятую (`"5,7,9"`) для нескольких. Типизированный клиент с полем `categoryId: number | null` работал на постах с одной категорией, но ломался на постах с двумя и более.

**Стало**

`categoryId` — всегда массив чисел `number[]`: `[]` без категорий, `[11]` для одной, `[5, 7, 9]` для нескольких. Тип единый независимо от количества категорий.

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

Читайте `categoryId` как массив: `post.categoryId.length` вместо проверки на `null`, `post.categoryId[0]` для первой категории. Прежнюю ветку «число или строка» можно убрать.

### FIX-0701-6: единые коды ошибок токена и скоупа в разделе Диска

**Было**

Пользовательские операции Диска — [POST /v1/files/:id/moveto](/docs/entities/files/moveto), `copyto`, [POST /v1/files/upload](/docs/entities/files/upload), [GET /v1/files/:id/download](/docs/entities/files/download) и аналоги для папок — при отсутствии токенов портала возвращали `401 NO_TOKENS`, а при нехватке скоупа `disk` — `403 SCOPE_MISSING`. Сгенерированные CRUD-операции того же раздела (list/get/create/update/delete) для тех же условий уже возвращали `401 TOKEN_MISSING` и `403 SCOPE_DENIED` — поэтому в пределах одного раздела клиент видел два разных кода для одной ошибки.

**Стало**

Все операции Диска возвращают единые коды — `401 TOKEN_MISSING` и `403 SCOPE_DENIED`, как и остальной V1 API. HTTP-статусы (401 и 403) не изменились.

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

Если код ветвился по строкам `NO_TOKENS` или `SCOPE_MISSING` на операциях moveto/copyto/upload/download, переключитесь на `TOKEN_MISSING` / `SCOPE_DENIED` (или проверяйте HTTP-статус). Остальным менять ничего не нужно.

### FIX-0701-7: placement CALL_CARD убран из списка допустимых

**Было**

Код placement `CALL_CARD` числился допустимым: `POST /v1/placements/bind` пропускал его через валидацию и передавал в Битрикс24, а `GET /v1/placements/available` выдавал его в списке. Но ни один модуль Битрикс24 не регистрирует этот placement, поэтому `placement.bind` завершался внутренней ошибкой, которую платформа отдавала как `INTERNAL_SERVER_ERROR`.

**Стало**

`CALL_CARD` удалён из списка допустимых: он больше не появляется в `GET /v1/placements/available`, а `POST /v1/placements/bind` с ним отклоняется сразу понятной ошибкой `VALIDATION_ERROR`, без обращения к Битрикс24.

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

Привязка `CALL_CARD` не работала и раньше (возвращала непонятную ошибку 500), поэтому рабочих интеграций изменение не ломает. Для панели приложений в карточке звонка используйте актуальные placement'ы из `GET /v1/placements/available`.

### FIX-0701-8: workday open/close/pause: поле userId теперь применяется

**Было**

Документированное поле тела `userId` в [POST /v1/workday/open](/docs/workday/open), [POST /v1/workday/close](/docs/workday/close) и [POST /v1/workday/pause](/docs/workday/pause) молча игнорировалось: операция всегда выполнялась над владельцем токенов ключа, даже если был передан другой сотрудник. Ответ приходил `success`, но действие затрагивало не того пользователя.

**Стало**

`userId` транслируется в параметр Битрикс24 `USER_ID`, поэтому операция выполняется над указанным сотрудником (при наличии прав администратора или руководителя). Несуществующий `userId` теперь возвращает ошибку Битрикс24, а не мнимый успех. Некорректный `userId` (не положительное целое) отклоняется как `400 INVALID_PARAMS`. Поведение совпадает с уже работавшим [GET /v1/workday/status](/docs/workday/status).

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

Кто не передавал `userId`, изменений не заметит — операция по-прежнему применяется к владельцу токенов. Кто передавал `userId`, теперь получит корректное действие над указанным сотрудником.

## 2026-06-30

### NEW-0630-1: POST /v1/cowork/deploy-key — получить проектный ключ для деплоя из Cowork/Code

Ключ Cowork/Code (`vibe:cowork`) работает только с data-plane и блокируется на control-plane инфраструктуры с 403 `INFRA_FORBIDDEN_FOR_COWORK_KEY`. Новый эндпоинт [POST /v1/cowork/deploy-key](/docs/cowork) позволяет агенту самому получить отдельный проектный ключ с правом деплоя: вызовите его этим же Cowork-ключом, возьмите поле `key` из ответа (тело — плоский объект, без обёртки `data`) и используйте его как заголовок `X-Api-Key` для деплоя/provision/exec под `/v1/infra/*`.

Возвращаемый ключ несёт скоупы `vibe:infra` + `vibe:storage` (без `vibe:cowork`), действует 7 дней и привязан к владельцу и порталу Cowork-ключа. На каждый вызов выдаётся свежий ключ, прежний проектный ключ при этом отзывается (активен всегда один). Требуется скоуп `vibe:cowork` и активная подписка Cowork/Code; коды отказа — 403 `INSUFFICIENT_SCOPE` / 403 `COWORK_NOT_ACTIVATED` / 503 `DEPLOY_KEY_DISABLED` / 503 `INFRA_DISABLED`.

### NEW-0630-2: Self-hosted placement получает одноразовый код авторизации на appUrl

Для приложения с собственным `appUrl` (вне Black Hole), открытого как placement, платформа теперь добавляет к редиректу на `appUrl` одноразовый код авторизации (`?code=...`) вместо внутреннего gateway-токена. Приложение обменивает этот код на `vibe_session` существующим запросом `POST /v1/oauth/token` — `redirect_uri` должен точно совпадать с настроенным `appUrl`. Раньше такие приложения получали невостребуемый токен и не могли авторизовать пользователя.

### NEW-0630-3: Новый эндпоинт POST /v1/oauth/placement-session для self-hosted приложений

Self-hosted приложение (на собственном сервере, не на Black Hole), открытое как placement (iframe) в Битрикс24, теперь может обменять токен пользователя Битрикс24, полученный в placement-колбэке на своём обработчике, на `vibe_session`. Запрос `POST /v1/oauth/placement-session` с телом `{ app_key, access_token, member_id, domain }` (необязательно `refresh_token`, `expires_in`) идёт сервер-к-серверу — токен сессии не попадает в браузер. Раздел авторизации документации описывает обе топологии placement.

### FIX-0630-4: PATCH права OAuth-app-ключа: честный отказ вместо ложной выдачи

**Было**

`PATCH /v1/keys/:id` с добавлением права Bitrix24 к ключу OAuth-приложения (`vibe_app_*`), как и `PATCH /v1/apps/:id` с расширением `scopes` приложения, возвращал `200` и сохранял новый набор прав. Но права OAuth-приложения фиксируются при выпуске и от такой правки на стороне Bitrix24 не меняются, поэтому `GET /v1/me` затем сообщал право, которого Bitrix24 не выдавал, а реальный вызов отклонялся.

**Стало**

Добавление права Bitrix24 к ключу OAuth-приложения или к приложению теперь отклоняется с `403 OAUTH_SCOPE_CHANGE_REQUIRES_REISSUE`. Снятие прав и изменение `vibe:*`-прав работают по-прежнему. Чтобы получить новое право, выпустите новый ключ авторизации с нужным набором.

### FIX-0630-5: ключ со скоупом `tasks` теперь реально открывает методы задач

**Было**

Ключ, выписанный со скоупом `tasks` (множественное число — его предлагает UI-пикер), на dev-key-портале минтил вебхук, который Bitrix24 принимал, но к REST-методам модуля Задач не привязывал. `GET /v1/me` рапортовал `tasks`, но вызовы методов задач отклонялись на стороне Bitrix24. Скоуп `task` (единственное число) работал.

**Стало**

При выписке и правке ключа набор прав канонизируется к написанию, которое Bitrix24 реально привязывает к методам (`tasks` → `task`), поэтому ключ открывает методы задач независимо от выбранного написания. На уже выписанные ключи изменение не распространяется ретроактивно — перевыпустите ключ.

### FIX-0630-6: неверный формат FILES в комментарии таймлайна отклоняется с 400

**Было**

[POST /v1/timelines](/docs/entities/timelines/create) и `PATCH /v1/timelines/:id` принимали поле `FILES` в любом виде и отвечали `200`/`201`. Если форма отличалась от массива пар `[[имяФайла, base64Содержимое]]` — например плоский массив строк или одиночная пара без внешнего массива — комментарий создавался, но файл прикреплялся как мусорный (со случайным именем и нечитаемым содержимым) либо терялся молча, без признаков ошибки.

**Стало**

Поле `FILES`, переданное не в виде массива пар `[[имяФайла, base64Содержимое]]`, отклоняется до обращения к Битрикс24 ошибкой `400 INVALID_FILES_SHAPE` с подсказкой о правильной форме. Пустой `FILES` (`[]`) и отсутствие поля по-прежнему допустимы. Проверка действует на одиночных `POST`/`PATCH` и в батче (`POST /v1/batch`, `POST /v1/timelines/batch`).

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

Кто передаёт `FILES` в задокументированной форме `[[имяФайла, base64Содержимое]]` — изменений нет. Кто полагался на другие формы — теперь получит явную `400` вместо молча испорченного вложения, и сможет исправить запрос.

### FIX-0630-7: пустые поля ботов и сотрудников приходят как null/[], а не false/{}

**Было**

В ответах карточки бота ([GET /v1/bots/:botId](/docs/bots/management/get), [POST /v1/bots](/docs/bots/management/create), [PATCH /v1/bots/:botId](/docs/bots/management/update)) незаполненные поля в `users[]` отдавались неверным примитивом: даты `lastActivityDate`, `mobileLastDate`, `desktopLastDate` приходили как булево `false`, а пустой список `phones` — тоже как `false`. То же поле `lastActivityDate` в `GET /v1/users` приходило как пустой объект `{}`. Из-за этого `new Date(lastActivityDate)` молча давал начало эпохи, а `phones.map(...)` падал с ошибкой типа.

**Стало**

Незаполненная дата на всех путях кодируется единообразно как `null`, а пустой список телефонов — как `[]`. Заполненная дата по-прежнему приходит ISO-строкой, заполненный список — массивом.

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

Менять ничего не нужно — типы стали корректными. Код, который опирался на сравнение с `false` для пустых значений, перестанет срабатывать: проверяйте дату на `null`, а список телефонов — как массив.

**Затронутые эндпоинты:** [GET /v1/bots/:botId](/docs/bots/management/get), [POST /v1/bots](/docs/bots/management/create), [PATCH /v1/bots/:botId](/docs/bots/management/update), `GET /v1/users`

### NEW-0630-8: Пересвязка OAuth-credentials приложения без удаления

Новый эндпоинт [POST /v1/apps/:id/relink-oauth](/docs/apps) обновляет `bitrixClientId` и `bitrixClientSecret` у существующего приложения, не удаляя его. Это нужно, когда локальное OAuth-приложение пересоздали на портале Битрикс24 и у него сменился `client_id`: раньше единственным путём было удалить приложение (что рвало связанные бот, openline и привязки) и создать заново.

Тело запроса: `{ bitrixClientId, bitrixClientSecret }` (оба обязательны). Парный ключ, бот и привязки сохраняются. Если этот `client_id` уже привязан к другому приложению — `409 OAUTH_CLIENT_ID_IN_USE`. Ключом самого OAuth-приложения вызвать нельзя — `403 OAUTH_APP_KEY_CANNOT_RELINK` (нужен личный ключ или кабинет). После пересвязки переустановите приложение на портале — это восстановит подписку на события.

### NEW-0630-9: Веб-поиск: полный текст страниц, изображения, режим новостей и фильтры по доменам в research

**Было**

`POST /v1/search` принимал `include_raw_content` как булев флаг, но полный текст найденных страниц в ответ не попадал. Не было параметров для режима новостей и для запроса изображений. `POST /v1/research` принимал `include_domains` и `exclude_domains`, но молча их отбрасывал.

**Стало**

`POST /v1/search` получил два новых необязательных параметра: `topic` (`general` или `news`, по умолчанию `general`) и `include_images` (булев, по умолчанию `false`). Булев `include_raw_content` теперь действительно возвращает полный текст: у каждого результата появилось поле `rawContent` (полный текст страницы, ограниченный по размеру). В ответе добавились верхнеуровневые `images` (массив объектов с полем `url`) и `ignored_filters` (массив строк — переданные фильтры, которые провайдер не смог применить). Эти поля приходят и в синхронном теле ответа, и в кадре `done` потоковой передачи. Заголовок `X-Search-Filters-Ignored` сохранён и теперь может перечислять `topic` и `include_images`. `POST /v1/research` теперь применяет `include_domains` и `exclude_domains` (до 20 каждый) у провайдеров с поддержкой и сообщает о превышении через `ignored_filters` в кадре `done`. Какие именно возможности доступны у выбранного движка — возвращает `GET /v1/search/providers`. Клиентам со строгой валидацией схемы по `additionalProperties` нужно учесть новые поля ответа.

### NEW-0630-10: Чтение AI-расшифровок звонков клиентов через API

Новый эндпоинт [GET /v1/activities/:activityId/transcript](/docs/entities/activities/transcript) возвращает готовую AI-расшифровку звонка клиента по идентификатору CRM-активности «Звонок». Метод только читает уже готовую расшифровку — генерацию не запускает. Требует скоуп `crm`. Если расшифровки для звонка ещё нет, поле `data.transcription` равно `null` — это штатный ответ, а не ошибка.

## 2026-06-29

### FIX-0629-1: поиск узлов оргструктуры теперь ищет по названию

**Было**

[POST /v1/humanresources/nodes/search](/docs/humanresources/nodes/search) проксировал в `humanresources.node.list`: тип узла задавался внутри `filter`, поиска по названию не было вовсе, а тело `{ "type": ..., "name": ... }` на верхнем уровне (или запрос без тела) возвращало `400` или `500`.

**Стало**

Эндпоинт обёрнут на `humanresources.node.search`. Обязательны два поля на верхнем уровне тела — `type` (`DEPARTMENT` или `TEAM`) и `name` (подстрока названия). Необязательны `parentId` и `pagination.limit` (по умолчанию 50, максимум 200). Возвращаются узлы, чьё название содержит `name`, в плоском `data` с `meta` (`total`, `hasMore`). Поля `filter`, `order` и `select` больше не принимаются.

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

Присылайте `{ "type": "TEAM", "name": "<подстрока>" }` на верхнем уровне вместо прежнего `{ "filter": { "type": "TEAM" } }`. Чтобы перечислить все узлы типа без поиска по названию, используйте [GET /v1/humanresources/nodes](/docs/humanresources/nodes/list) с `?type=...`.

### NEW-0629-2: Расписание выгодных часов AI-квоты (off-peak)

Добавлен эндпоинт `GET /v1/off-peak` — расписание скидок «выгодных часов» (Time-of-Use) для AI-квоты. Ответ содержит множитель цены прямо сейчас (`currentMultiplier`), ближайшее окно, когда станет дешевле (`nextWindow`), сетку 24×7 по часам и дням недели (`grid`), текущую ячейку сетки (`nowCell`) и часовой пояс расписания (`timezone`). Скидка применяется только к квотируемому расходу — в эти часы квота расходуется медленнее; оплата за токены по кошельку не затрагивается. Необязательный параметр `model=<идентификатор>` возвращает расписание конкретной модели вместо общего по умолчанию. Требуется скоуп `vibe:ai`. Пока выгодные часы не включены, ответ — `{ "enabled": false }`.

### BC-0629-3: удалён слаг провайдера vibe-search

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

**Было**

Поле `provider` в [POST /v1/search](/docs/search/run) и [POST /v1/research](/docs/search/research) принимало слаг `vibe-search` — отдельный платформенный движок, добавленный 06.06.2026. Он также присутствовал в перечне слагов [GET /v1/search/providers](/docs/search/providers).

**Стало**

Слаг `vibe-search` удалён. Платформенный поисковый движок на всех инстансах называется `bitrix-search` — конкретный апстрим за ним зависит от инстанса. Запрос с `provider: "vibe-search"` теперь возвращает `400 INVALID_REQUEST` (значение не проходит валидацию). Поддержка research для `bitrix-search` тоже зависит от инстанса — читайте [GET /v1/search/providers](/docs/search/providers).

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

Если в запросе явно передавался `provider: "vibe-search"`, замените его на `bitrix-search` либо опустите поле `provider`, чтобы использовать движок по умолчанию инстанса (его показывает поле `defaultProvider` в [GET /v1/me](/docs/keys-auth)). Слаг `vibe-search` не был движком по умолчанию ни на одном проде, поэтому затронуты только интеграции, прописавшие его явно.

### FIX-0629-4: Значение null в поле через POST /v1/batch больше не пишет в поле строку «null»

**Было**

В составном `POST /v1/batch` создание или обновление со значением поля `null` (например `{"entity":"deals","action":"update","entityId":123,"params":{"comments":null}}`) записывало в поле **литеральную строку `"null"`**.

**Стало**

Поле получает пустое значение, которое Bitrix24 трактует по типу поля: текстовое — очищается, числовое — становится `0`, датовое — остаётся без изменений. Литеральная строка `"null"` больше не пишется, ошибки не возникает. Это совпадает с поведением одиночного `PATCH /v1/{entity}/:id` с `null`. Постраничный путь `/v1/{entity}/batch` по-прежнему пропускает `null`-поле целиком (оставляет значение без изменений у всех типов).

### FIX-0629-5: Поиск и список с фильтром по null теперь отдают больше 50 строк

**Было**

Запрос `POST /v1/{entity}/search` или `GET /v1/{entity}` с фильтром по пустому значению (например `{"filter": {"closedDate": null}}`) и `limit` больше 50 возвращал максимум 50 записей, хотя `meta.total` показывал реальное число совпадений и `meta.hasMore` был `true`. Автопагинация молча обрывалась после первой страницы, и типовой обход «читать, пока строк ровно `limit`» получал неполный результат без единой ошибки.

**Стало**

Такой запрос отдаёт до `limit` записей, как и с любым другим фильтром. Значение `null` в фильтре трактуется как «поле пусто» одинаково на всех страницах выборки.

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

Клиентам, которые из-за обрыва листали вручную через `offset` шагом 50, ручной обход больше не нужен — можно запросить до 5000 записей одним вызовом.

### FIX-0629-6: смена порта и автомаршрутизация деплоя работают на новых серверах-приложениях из коробки

**Было**

Обычный сервер «Опубликовать приложение» поднимался с фиксированным портом агента, поэтому `PATCH /v1/infra/servers/:id/port` и шаг автомаршрутизации деплоя отвечали `409 PORT_NOT_APPLIED` (`NO_SCANNER`), а публичный URL отдавал служебную страницу Black Hole, пока сервис слушал не порт по умолчанию.

**Стало**

Новые обычные серверы-приложения поднимаются с авто-определением порта: сервис на любом порту доступен через туннель сразу, а смена порта и автомаршрутизация деплоя проходят успешно. Серверы агентов и galaxy-хостов поведение не меняют.

### FIX-0629-7: установка приложения через /v1/apps возвращает понятный код ошибки вместо общего BOX_APP_INSTALL_FAILED

**Было**

При сбое установки OAuth-приложения [POST /v1/apps](/docs/apps/create) всегда возвращал `502 BOX_APP_INSTALL_FAILED`, а в `error.message` подставлялся сырой ответ Битрикс24 целиком.

**Стало**

Ответ при сбое классифицируется: `403 B24_INSUFFICIENT_SCOPE` (служебная интеграция потеряла права на портале), `410 STALE_DEVELOPER_KEY` (доступ изменили или удалили — восстановить автоматически нельзя), `502 RECOVERY_FAILED` (временный сбой, можно повторить) или `502 DEVKEY_MINT_FAILED` (прочее). `error.message` больше не содержит сырой ответ Битрикс24 — диагностика переехала в очищенное поле `error.details.b24Body`.

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

Обработка «ответ не 201 — установка не удалась» продолжает работать без изменений. Если код различал именно `BOX_APP_INSTALL_FAILED`, добавьте обработку новых кодов выше.

### FIX-0629-8: поиск по широкому диапазону дат больше не возвращает пусто

**Было**

`POST /v1/deals/search` (и аналогично для leads, contacts, companies, quotes, invoices, items) с фильтром по дате и нижней границей (`>=` / `>`) за период шире 14 дней возвращал `200` с пустым `data` и `meta.total: 0`, хотя записи за этот период существовали.

**Стало**

Такой запрос возвращает все совпадающие записи. Поведение `GET /v1/deals`, узких диапазонов (≤ 14 дней) и параметра `autoWindow: false` не менялось.

### NEW-0629-9: Новый код ошибки CONNECTOR_APP_INSTALL_FORBIDDEN при установке приложения

[POST /v1/apps](/docs/apps) на коробочном портале теперь возвращает `403` с кодом `CONNECTOR_APP_INSTALL_FORBIDDEN`, когда администратор портала Битрикс24 запретил пользователю устанавливать приложения. Поле `error.message` содержит понятное локализованное объяснение с подсказкой обратиться к администратору портала. Прежде такой отказ отдавался как общий `502 CONNECTOR_APP_INSTALL_FAILED` без объяснения причины; этот код по-прежнему используется для прочих сбоев установки.

### NEW-0629-10: Перенос владения ботом на другой ключ

Добавлен эндпоинт [POST /v1/bots/:botId/transfer](/docs/bots/management/transfer) — переносит владение ботом на другой API-ключ того же аккаунта Битрикс24 и того же пользователя (или администратора аккаунта). Решает ситуацию, когда бот «осиротел» после пересоздания приложения: ключ-владелец отозван, и рантайм бота переставал работать. Тело: `{ "targetApiKeyId": "<id>" }`. Целевой ключ должен быть активен, в том же аккаунте, с областью `imbot`. После переноса проверьте B24-привязку нового ключа через `POST /v1/bots/:botId/reauth`.

## 2026-06-28

### FIX-0628-1: редактирование scopes ключа применяется к вебхуку Битрикс24

**Было**

`PATCH /v1/keys/:id` со списком `scopes` сохранял новый набор в Вайбкоде, но на self-hosted (коробочных) порталах Битрикс24 не переносил его на вебхук портала. Вебхук оставался со старым набором scopes, и вызов метода из только что добавленного scope отклонялся Битрикс24 (403), хотя по данным Вайбкода ключ этот scope уже имел.

**Стало**

Изменение `scopes` теперь применяется к вебхуку Битрикс24 в том же запросе. Если синхронизацию выполнить не удалось, ключ не обновляется (Вайбкод и Битрикс24 остаются на прежнем наборе), а ответ несёт код ошибки: `INVALID_SCOPES` (400) — портал не выдаёт один из запрошенных scope, `STALE_DEVELOPER_KEY` (410) или `RECOVERY_FAILED` (502) — ключ доступа портала недействителен, `BOX_NO_DEVELOPER_KEY` (400) — у владельца ключа нет ключа доступа портала, `DEVKEY_SCOPE_SYNC_FAILED` (502) — прочий отказ Битрикс24.

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

Менять ничего не нужно — scopes, добавленные через `PATCH /v1/keys/:id`, теперь работают сразу. Если в ответ пришёл один из кодов выше, набор scopes остался прежним: устраните причину и повторите запрос.

## 2026-06-27

### NEW-0627-1: Свойства товаров каталога — чтение и управление схемой свойств

Добавлен раздел [/v1/catalog-product-properties](/docs/entities/catalog-product-properties) — определения пользовательских свойств торгового каталога (идентификатор, название, тип). Поддерживаются список, получение, создание, изменение, удаление, поиск и справочник полей. Свойства-списки товара приходят в [/v1/catalog-products](/docs/entities/catalog-products) полями вида `propertyNNN`, где `NNN` — идентификатор свойства. Новый раздел сопоставляет этот идентификатор с названием и типом свойства. Фильтр `filter[iblockId]` ограничивает выборку одним каталогом, идентификатор берётся из [/v1/catalogs](/docs/entities/catalogs). Требуется скоуп `catalog`.

## 2026-06-26

### FIX-0626-1: catalog-prices: системные поля priceScale, extraId, timestampX объявлены в схеме

**Было**

[GET /v1/catalog-prices](/docs/entities/catalog-prices/list) и [GET /v1/catalog-prices/:id](/docs/entities/catalog-prices/get) возвращали поля `priceScale`, `extraId` и `timestampX`, но они не были объявлены в схеме: проходили без нормализации (поле `timestampX` приходило в формате со смещением, например `2024-06-17T16:53:24+03:00`) и отсутствовали в ответе [GET /v1/catalog-prices/fields](/docs/entities/catalog-prices/fields).

**Стало**

Три поля объявлены как доступные только для чтения. Теперь они перечислены в [GET /v1/catalog-prices/fields](/docs/entities/catalog-prices/fields), а `timestampX` нормализуется в ISO 8601 UTC (`2024-06-17T13:53:24.000Z`) — единообразно с остальными полями типа datetime.

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

Момент времени в `timestampX` не меняется — меняется только его строковое представление (UTC вместо локального смещения). Клиенты, разбирающие значение стандартным парсером дат, продолжают работать без изменений.

### FIX-0626-2: Скачивание исходников сервера и приложения: подписанный URL больше не отдаёт 403

**Было**

`GET /v1/infra/servers/:id/sources/:versionId/download` и `GET /v1/apps/:id/sources/:versionId/download` возвращали `200` с подписанным URL, но скачивание по этому URL падало с `403 AccessDenied`, если запрос шёл под личным ключом (`vibe_api_*`). Листинг версий при этом работал, и файл физически присутствовал в хранилище.

**Стало**

Подписанный URL теперь привязан к фактическому расположению объекта в хранилище, поэтому скачивание возвращает содержимое архива. Исправление покрывает и снапшоты, у которых ключ хранения принадлежит другому семейству (legacy-снапшоты приложения с привязкой к серверу).

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

Контракт эндпоинтов не меняется — это восстановление задокументированного поведения «`200` + рабочий подписанный URL». Никаких изменений на стороне клиента не требуется.

### NEW-0626-3: Обновление api-bearer токена и причина отказа Gateway

Новый эндпоинт [POST /v1/infra/servers/:id/access-tokens/:tokenId/refresh](/docs/infra/access-tokens/refresh) выпускает свежий JWT (до 10 минут) для уже существующего токена режима `api-bearer` — без создания новой записи, не расходуя лимит активных токенов и лимит выпусков в час. Долгоживущий клиент (CI, AI-агент) обновляет токен перед истечением `jwtExpiresAt` вместо повторного выпуска. Один отзыв гасит и исходный, и все обновлённые JWT одного токена.

Ответ `401 BH_LOGIN_REQUIRED`, который Gateway возвращает на субдомене приложения при отклонении заголовка `Authorization: Bearer`, теперь содержит поле `reason` с конкретной причиной: `expired`, `signature`, `subdomain`, `type`, `revoked`, `malformed` или `invalid`. Поле аддитивное — прежние клиенты его игнорируют.

## 2026-06-25

### FIX-0625-1: нейтральные идентификаторы провайдера, плана и региона в инфраструктурном API

**Было**

На международной (`.com`) поверхности `GET /v1/infra/providers`, `GET /v1/infra/providers/:id/plans` и `GET /v1/infra/providers/:id/regions` отдавали идентификаторы провайдера, планов, типа диска и регионов в сыром виде нижележащей инфраструктуры, а не в нейтральном пространстве имён бренда.

**Стало**

Те же поля приведены к нейтральному пространству имён бренда Bitrix Cloud, как в российском сегменте: провайдер `bitrix-cloud`, планы `bc-small`/`bc-medium`/`bc-large`/`bc-xlarge`, `diskType: "network-ssd"`, регионы `bc-eu-central`/`bc-eu-west`/`bc-us-east`/`bc-us-west`/`bc-ap-southeast`. Поля `name` и `country` остаются человекочитаемыми (например, «Frankfurt (EU Central)», `DE`). Те же значения возвращаются в полях `provider`/`plan`/`region` ответов `GET /v1/infra/servers` и `GET /v1/infra/servers/:id`.

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

Стандартный сценарий не требует изменений: идентификатор, полученный из каталога, по-прежнему передаётся в `POST /v1/infra/servers` без правок. При создании сервера принимаются и новые идентификаторы (`bitrix-cloud`/`bc-small`/`bc-eu-central`), и идентификаторы из прежнего каталога, поэтому существующие интеграции продолжают работать. Поправьте только код, который сверяет идентификаторы ответа с захардкоженными строками из прежнего каталога провайдера, планов или регионов.

### FIX-0625-2: offset внутри подвызовов /v1/batch теперь листает страницы

**Было**

В [POST /v1/batch](/docs/batch) подвызовы `list` и `search` молча игнорировали `offset` в `params`: B24 получал неизвестный ключ `offset` вместо `start`, поэтому каждая страница возвращала один и тот же первый набор записей. Три подвызова `contacts.search` с `offset` 0, 50 и 100 отдавали идентичную первую страницу.

**Стало**

`offset` в подвызове `list`/`search` переводится в `start` Bitrix24 (как в одиночном `POST /v1/{entity}/search`). Те же три подвызова теперь возвращают три разные непересекающиеся страницы. Поведение одиночного эндпоинта не изменилось.

### FIX-0625-3: Файлы и папки: deletedBy у не удалённых объектов теперь null

**Было**

[GET /v1/files/:id](/docs/entities/files/get), [GET /v1/files](/docs/entities/files/list) и эндпоинты папок возвращали `deletedBy: 0` у не удалённого объекта, хотя поле объявлено как `number | null` с «`null` — объект не удалён». Поле-сосед `deletedAt` при этом корректно отдавало `null`, так что два поля с одинаковым контрактом вели себя по-разному, и проверка `deletedBy !== null` ошибочно считала каждый активный объект удалённым.

**Стало**

`deletedBy` нормализуется в `null` для не удалённых объектов (Битрикс24 хранит в колонке `DELETED_BY` ноль как «нет пользователя»; пользователя с id 0 не существует). У удалённых объектов поле по-прежнему содержит id пользователя, выполнившего удаление.

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

Поведение приведено к задокументированному контракту `number | null`. Клиенты, проверявшие `deletedBy === null` / `deletedBy !== null`, теперь получают корректный результат для активных объектов.

### NEW-0625-4: чтение Базы знаний 2.0 — список баз, документы, дерево, поиск

Read-доступ к Базе знаний 2.0: список доступных баз знаний (курсорная пагинация), получение базы знаний и документа по идентификатору (документ — с Markdown-содержимым), дерево документов базы знаний и полнотекстовый поиск по документам. Скоуп `note`.

**Затронутые эндпоинты:** [GET /v1/note/collections](/docs/note/collections) (список), [GET /v1/note/collections/:id](/docs/note/collections) (одна база), [GET /v1/note/collections/:collectionId/documents](/docs/note/documents) (дерево), [GET /v1/note/documents/:id](/docs/note/documents) (документ с Markdown), [GET /v1/note/documents/search](/docs/note/documents) (поиск по `query`).

### FIX-0625-5: методы Базы знаний 2.0 (создание, изменение, загрузка файлов) теперь работают

**Было**

Создание и изменение баз знаний и документов ([POST /v1/note/collections](/docs/note/collections), [PATCH /v1/note/collections/:id](/docs/note/collections), [POST /v1/note/documents](/docs/note/documents), [PATCH /v1/note/documents/:id](/docs/note/documents)) возвращали `400` с ошибкой валидации Битрикс24, а загрузка вложения ([POST /v1/note/documents/:documentId/files](/docs/note/files)) сохраняла файл, но не возвращала его идентификатор в `data.id`.

**Стало**

Методы работают: создание и изменение возвращают `200`, а ответы создания баз знаний, документов и файлов содержат идентификатор в `data.id`. Архивирование, удаление и получение файла работали и раньше.

### FIX-0625-6: /v1/me: supportedVisibilities хранилища теперь в верхнем регистре

**Было**

[GET /v1/me](/docs/keys-auth) в блоке `storage` отдавал `supportedVisibilities: ["private","public"]` в нижнем регистре, а эндпоинты загрузки принимают только `PRIVATE`/`PUBLIC` (верхний регистр). Агент, скопировавший значение из манифеста, получал `STORAGE_INVALID_VISIBILITY`.

**Стало**

`supportedVisibilities` отдаётся как `["PRIVATE","PUBLIC"]` — ровно те значения, которые принимает параметр `visibility` при загрузке.

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

Если клиент брал значение `visibility` из `/v1/me` и приводил его к верхнему регистру сам — ничего не меняется. Если передавал как есть — теперь загрузка проходит без ошибки.

## 2026-06-24

### FIX-0624-1: логи galaxy-приложения отдают вывод контейнера

**Было**

[GET /v1/infra/servers/:id/logs](/docs/infra/deploy/logs) для galaxy-приложения (`kind=GALAXY_APP`) возвращал только системный журнал хоста (`journalctl`), а не логи самого контейнера приложения — увидеть stdout/stderr упавшего приложения было нельзя.

**Стало**

Для galaxy-приложения эндпоинт читает stdout/stderr контейнера (`docker logs`). Чтение read-only: если хост-галактика спит или недоступен, ответ — пустой `data.logs` плюс `data.hint`, хост не будится. Параметр `since` для galaxy-приложений принимает только длительность (`10m`) или метку RFC3339 — человекочитаемые формы `journalctl` («1 hour ago») допустимы лишь для Black Hole-серверов.

### NEW-0624-2: код ошибки GALAXY_APP_START_FAILED при деплое

[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) для galaxy-приложения возвращает `502 GALAXY_APP_START_FAILED`, когда приложение успешно собралось, но упало или ушло в OOM-перезапуск сразу после старта. Это отдельный код от `GALAXY_APP_BUILD_FAILED` (ошибка сборки): по нему видно, что сборка прошла, а проблема в рантайме (например, превышение лимита памяти). Хвост логов контейнера приходит в поле `buildLog`.

### FIX-0624-3: окно блокирующего пробуждения сервера увеличено до ~5 минут

**Было**

Блокирующее пробуждение — [POST /v1/infra/servers/:id/wake](/docs/infra/lifecycle/wake) с `?wait=true` и автопробуждение спящего сервера при [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) — ждало готовности (статус RUNNING плюс подключённый туннель) примерно до 3 минут, после чего возвращало `504 WAKE_TIMEOUT`.

**Стало**

Основная фаза ожидания увеличена с ~3 до ~5 минут, а с учётом фазы перезагрузки полный потолок до `504` — около 6.5 минуты. Глубоко «остывший» хост (например, спавшая несколько дней галактика) успевает загрузиться и подключиться, а не получает ложный таймаут. Код ошибки, форма ответа и потолок со стороны прокси прежние.

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

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

### NEW-0624-4: параметры placement и graduateFrom при создании сервера

[POST /v1/infra/servers](/docs/infra/servers/create) получил два необязательных параметра. `placement` — `auto` (по умолчанию, поведение прежнее) или `dedicated`: на портале с моделью размещения `galaxies-only` значение `dedicated` создаёт не приложение Galaxy, а отдельную виртуальную машину, проходя те же проверки, что и обычное создание сервера — политику `serverCreation` и квоту серверов на пользователя. `graduateFrom` принимает идентификатор вашего приложения Galaxy (`kind=GALAXY_APP`): после создания выделенного сервера это приложение удаляется. `graduateFrom` ограничен владельцем — чужой или не-Galaxy идентификатор вернёт `404`, ничего не удаляя.

Это аддитивно: без `placement` или с `placement: "auto"` запрос ведёт себя в точности как раньше.

Кроме того, при OOM-падении приложения Galaxy ответ [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) с кодом `502 GALAXY_APP_START_FAILED` теперь содержит структурную подсказку `error.hint` с `recoveryAction: "graduate-to-dedicated-vm"` — как пересоздать приложение на выделенном сервере через `placement: "dedicated"` и `graduateFrom`. Подсказка добавляется только когда причина падения — OOM (превышение лимита памяти контейнера), а не обычный краш.

### FIX-0624-5: создание сервера с кодом в source.content принимает архивы до 500 МБ

**Было**

[POST /v1/infra/servers](/docs/infra/servers/create) с inline-архивом в `source.content` (одношаговое создание приложения Galaxy) возвращал `413 FST_ERR_CTP_BODY_TOO_LARGE` уже на архивах больше ~750 КБ, хотя в документации поля `source.content` заявлен лимит 500 МБ на тело запроса. Маршрут наследовал глобальный лимит тела 1 МБ.

**Стало**

Маршрут принимает тело до 500 МБ — как и `POST /v1/infra/servers/:id/deploy` и `POST /v1/infra/servers/:id/upload`. Лимит из документации теперь действует на самом деле.

### NEW-0624-6: группировка реквизитов по ИНН/ОГРН/КПП в aggregate

[POST /v1/requisites/aggregate](/docs/entities/requisites/aggregate) теперь принимает `groupBy` по строковым идентификаторам реквизита: `rqInn`, `rqKpp`, `rqOgrn`, `rqOgrnip`, `rqOkpo`, `rqVatId`, `rqResidenceCountry`, `rqCompanyName`, а также `presetId`, `entityTypeId`, `active`. Раньше группировка по этим полям возвращала `400 INVALID_PARAMS` с пустым списком доступных полей.

Группировка по `rqInn` — самый быстрый способ найти дубли реквизитов одним вызовом: группы с `count > 1` содержат повторяющиеся ИНН. Прежние вызовы (`groupBy` по `entityTypeId`/`presetId`) работают без изменений. Числовые функции (`sum`/`avg`/`min`/`max`) по этим строковым полям по-прежнему недоступны — они только для группировки.

### FIX-0624-7: availableActions спящего сервера показывает wake/start; repair-status сразу `running`

**Было**

Для спящего сервера без туннеля (`SLEEPING` + `blackholeStatus: DISCONNECTED` — обычное состояние остановленного сервера) поле `availableActions` в ответе `422 SERVER_WRONG_STATE` и в `GET /v1/me` (`infra.unhealthyServers`) содержало только `["repair","delete"]` — без очевидного способа поднять сервер. Отдельно: сразу после [POST /v1/infra/servers/:id/repair](/docs/infra/lifecycle/repair) опрос [repair-status](/docs/infra/lifecycle/repair-status) в первые миллисекунды мог вернуть `{status:"idle"}`, и цикл опроса завершался преждевременно.

**Стало**

`availableActions` для любого спящего сервера теперь содержит `["wake","start","repair","delete"]` — оба действия реально принимаются эндпоинтами `/wake` и `/start`. А `repair-status` выставляет `running` синхронно при старте ремонта, поэтому первый же опрос видит `running`, а не `idle`. Прежние вызовы продолжают работать без изменений.

### FIX-0624-8: stage-history?entityType=invoice теперь отдаёт историю смарт-счёта (31)

**Было**

[GET /v1/stage-history](/docs/entities-index)`?entityType=invoice` возвращал историю упразднённого старого счёта (entityTypeId 5, status-based: `statusId`/`statusSemanticId`). Текущий смарт-счёт (31) был доступен только под ключом `entityType=new-invoice`. Клиент, работающий со счетами через `/v1/invoices` (тип 31), запросив историю под `invoice`, получал чужой упразднённый тип.

**Стало**

`entityType=invoice` отдаёт историю текущего смарт-счёта (entityTypeId 31, stage-based: `stageId`/`stageSemanticId`/`categoryId`) — в одном ряду с `/v1/invoices`. Ключ `new-invoice` сохранён как алиас на 31 для обратной совместимости, ломать ничего не нужно.

### NEW-0624-9: эндпоинт эмбеддингов bitrix/embeddings

Появился OpenAI-совместимый эндпоинт [POST /v1/embeddings](/docs/ai/embeddings) — преобразование текста в векторные представления (эмбеддинги) для семантического поиска, кластеризации, дедупликации и поиска похожих карточек CRM. Модель `bitrix/embeddings` бесплатная и платформенная, свой ключ провайдера не требуется. В поле `input` принимается строка или массив строк, ответ возвращается в сыром OpenAI-формате: поле `object` со значением `list`, массив `data` с объектами вида `{ object: "embedding", embedding, index }` и блок `usage`. Поддерживаются необязательные параметры `encoding_format` (`float` или `base64`) и `dimensions`. Список доступных моделей и их возможностей — [GET /v1/models](/docs/ai/models/list), у модели эмбеддингов выставлена возможность `embeddings`.

### FIX-0624-10: типы полей календарных событий приведены к реальным ответам

**Было**

[GET /v1/calendar-events/fields](/docs/entities/calendar-events/fields) объявлял `rrule` как `string`, а `dateCreate` и `updatedAt` — как `datetime`, хотя на чтение `rrule` приходит объектом, а `dateCreate` и `updatedAt` — строкой в формате региональных настроек портала (не ISO 8601). В схеме числилось поле `ownerType`, которого в ответах нет. В объекте `rrule` приходили служебные ключи `~UNTIL` и `UNTIL_TS`, а в элементах списка повторяющихся событий — служебный ключ `RINDEX`.

**Стало**

`/fields` объявляет `rrule` как `object`, а `dateCreate` и `updatedAt` — как `string`. Фантомное поле `ownerType` убрано из схемы. Служебные ключи `~UNTIL`, `UNTIL_TS` и `RINDEX` больше не попадают в ответы.

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

Документированные поля не изменились — клиент, читавший только их, продолжает работать. Для абсолютной метки времени используйте `from` и `to` (ISO 8601). Значения `dateCreate` и `updatedAt` не разбирайте фиксированным парсером — их формат зависит от региональных настроек портала.

### NEW-0624-11: поле provisionReason в ответе серверов

[GET /v1/infra/servers](/docs/infra/servers/list) и [GET /v1/infra/servers/:id](/docs/infra/servers/get) теперь возвращают поле provisionReason со значением oom, crash или null — структурный признак причины ошибки galaxy-приложения. Раньше его отдавали только сессионные роуты кабинета, и в Vibecode API приходилось разбирать свободный текст provisionError. Значение oom — сигнал к «выпуску» приложения на выделенный сервер: создайте сервер с параметрами placement равным dedicated и graduateFrom. Поле необязательное и аддитивное — прежние интеграции работают без изменений.

### FIX-0624-12: graduation-сигнал срабатывает при любой нехватке памяти galaxy-приложения

**Было**

Приложение Galaxy, которому не хватило памяти контейнера — и упёршееся в лимит с перезапусками у предела, и исчерпавшее память сразу на старте (например, грузит большую модель), — классифицировалось как обычный крэш: [GET /v1/infra/servers/:id](/docs/infra/servers/get) возвращал provisionReason crash, а ответ [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy) с 502 GALAXY_APP_START_FAILED шёл без graduation-подсказки. И наоборот, не-OOM краш-луп (необработанное исключение) мог ошибочно помечаться oom.

**Стало**

Реальная нехватка памяти в любой форме — и мгновенная на старте, и постепенный рост до лимита — надёжно даёт provisionReason oom и error.hint с recoveryAction graduate-to-dedicated-vm. Сборочные ошибки и приложения, которые вообще не стартовали (битая команда запуска), остаются crash без graduation.

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

Менять ничего не нужно: значение поля и подсказка теперь точнее отражают нехватку памяти. Агент может надёжно ловить provisionReason oom для любого исчерпания памяти и «выпускать» приложение на выделенный сервер.

## 2026-06-23

### NEW-0623-10: подсказка по настройке push-доставки в ошибках подписки на события

Ответы об ошибке `400 NOT_OAUTH_APP` и `400 NO_USER_TOKEN` у [POST /v1/infra/servers/:id/event-subscriptions](/docs/infra/event-subscriptions) теперь содержат поле `error.hint` — текстовую инструкцию, как получить сервер под ключом авторизации (`vibe_app_`) для push-доставки: создать ключ авторизации через `POST /v1/apps`, авторизовать приложение на портале, создать новый сервер под этим ключом. Отдельной «миграции» существующего сервера с обычного ключа нет. Поле аддитивное — прежние клиенты не затронуты.

### FIX-0623-1: список действий бизнес-процессов

**Было**

[GET /v1/bizproc-activities](/docs/entities/bizproc-activities) возвращал каждый код действия как объект с числовыми ключами по символам — например, `{"0":"D","1":"i", …}` вместо строки `"DiskRead"`. Проверка `Array.includes(code)` не работала.

**Стало**

Эндпоинт возвращает коды действий массивом строк, как и задокументировано.

### FIX-0623-2: ключи ответа поиска дубликатов в camelCase

**Было**

`POST /v1/duplicates/find` возвращал ключи объекта `data` в верхнем регистре (`LEAD`, `CONTACT`, `COMPANY`), в отличие от остального API в camelCase.

**Стало**

Ключи приходят в camelCase (`lead`, `contact`, `company`); значения (массивы идентификаторов) не меняются.

### FIX-0623-3: поле files эпика Scrum массивом идентификаторов

**Было**

[GET /v1/scrum/epics/:id](/docs/scrum) отдавал поле `files` сырым UF-объектом Битрикс24 (с `VALUE_RAW`, `USER_TYPE_ID` и прочими внутренними метаданными).

**Стало**

`files` — массив идентификаторов вложений (`[417]`) или пустой массив, в едином стиле с остальным API.

### BC-0623-4: создание ключа авторизации только для администраторов

> Поддержка старого формата до: не предусмотрена, ограничение действует сразу

**Было**

Создать ключ авторизации ([POST /v1/keys](/docs/keys-auth)) мог любой пользователь портала.

**Стало**

Создание ключа доступно только администраторам портала, остальным запрос отклоняется.

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

Создавайте ключи под учётной записью с правами администратора Битрикс24.

### FIX-0623-5: заголовок Retry-After при ограничении частоты

**Было**

При ответе `429` (превышение лимита частоты) заголовок `Retry-After` не возвращался, и интегратор не знал, через сколько повторить запрос.

**Стало**

Ответ `429` несёт `Retry-After` с интервалом в секундах. Используйте его как паузу перед повтором.

### FIX-0623-6: удалённый сервер снова отдаёт 404

**Было**

[GET /v1/infra/servers/:id](/docs/infra/servers/get) для мягко удалённого сервера возвращал `200` с полным телом и `status: "deleted"`, хотя документация обещает `404`. Клиент, опрашивающий эндпоинт и ожидающий `404` как подтверждение удаления, его не получал.

**Стало**

Эндпоинт возвращает `404 NOT_FOUND` для удалённого сервера — так же, как [список](/docs/infra/servers/list) и [удаление](/docs/infra/servers/delete), и как описано в документации.

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

Если ваш код полагался на `200` с `status: "deleted"`, переключитесь на проверку `404` (либо на отсутствие сервера в [списке](/docs/infra/servers/list)) как на признак удаления.

### NEW-0623-7: Универсальные списки — полный REST API

Появился раздел [Списки](/docs/lists) (scope `lists`): программное управление универсальными списками Битрикс24 — самими списками, их полями, разделами и элементами. Это 24 эндпоинта под `/v1/lists` поверх методов `lists.*`.

Список адресуется типом инфоблока (`iblockTypeId` — `lists`, `lists_socnet` или `bitrix_processes`, по умолчанию `lists`) и идентификатором: числовой сегмент пути трактуется как `IBLOCK_ID`, строковый — как символьный `IBLOCK_CODE`. Поля, разделы и элементы доступны вложенными путями.

Если модуль «Универсальные списки» не подключён на портале, вызов возвращает `409 LISTS_MODULE_NOT_ENABLED` — это признак выключенного модуля, а не ошибка интеграции.

**Затронутые эндпоинты:** `/v1/lists`, `/v1/lists/:iblockId`, `/v1/lists/:iblockId/fields`, `/v1/lists/:iblockId/sections`, `/v1/lists/:iblockId/elements`

### FIX-0623-8: флаг isChildrenListEnabled у связей смарт-процессов

**Было**

Вложенный флаг связи `isChildrenListEnabled` принимался только как `true`/`false`. Значение `Y`/`N`, как у остальных флагов смарт-процесса, молча сохранялось как выключенное.

**Стало**

[POST /v1/smart-processes](/docs/entities/smart-processes/create) и [PATCH /v1/smart-processes/:entityTypeId](/docs/entities/smart-processes/update) приводят `Y`/`N` (а также `1`/`0`, `yes`/`no`) к `true`/`false` для `isChildrenListEnabled` в связях.

### FIX-0623-9: фильтр и выбор пользовательских полей сделок

**Было**

При фильтрации и выборе пользовательских (UF) полей сделки в форме `UF_CRM_*` поле отклонялось с `UNKNOWN_FILTER_FIELD` в фильтре и молча пропускалось из `select`.

**Стало**

Пользовательские поля сделок указываются в camelCase (`ufCrmCheckOut`) и работают без изменений в фильтре и выборе.

**Затронутые эндпоинты:** [GET /v1/deals](/docs/entities/deals/list), [POST /v1/deals/search](/docs/entities/deals/search)

## 2026-06-22

### NEW-0622-1: связь сделки с контактами

Управление набором контактов сделки: чтение, добавление, замена всего набора, удаление. PUT заменяет весь набор разом. Скоуп `crm`.

**Затронутые эндпоинты:** `GET/POST/PUT/DELETE /v1/deals/:id/contacts` — [Контакты сделки](/docs/entities/deals/contacts)

### BC-0622-2: список моделей содержит только GA-модели

> Поддержка старого формата до: не предусмотрена, экспериментальные модели не входили в стабильный контракт

**Было**

[GET /v1/models](/docs/ai/models/list) и список моделей в `/v1/me` включали экспериментальные не-GA модели.

**Стало**

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

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

Берите модель из актуального ответа `GET /v1/models`, не зашивайте идентификаторы экспериментальных моделей.

### FIX-0622-3: авто-пагинация подразделений

**Было**

[GET /v1/departments](/docs/entities/departments/list) возвращал только первую страницу при `limit > 50`.

**Стало**

Авто-пагинация собирает все подразделения в один ответ.

## 2026-06-19

### FIX-0619-1: создание документа

**Было**

[POST /v1/documents](/docs/entities/documents/create) возвращал `422` и не создавал документ.

**Стало**

Эндпоинт создаёт документ из шаблона и возвращает запись.

### FIX-0619-2: авто-пагинация складов

**Было**

`GET /v1/warehouses` и остатки по складу возвращали только первую страницу при `limit > 50`.

**Стало**

Авто-пагинация собирает все записи в один ответ.

**Затронутые эндпоинты:** [GET /v1/warehouses](/docs/entities/warehouses/list), [GET /v1/warehouses/:id/stock](/docs/entities/warehouses/stock)

### FIX-0619-3: сохранение значений списочных пользовательских полей

**Было**

При создании и обновлении пользовательского поля типа «список» значения списка терялись.

**Стало**

Значения списка сохраняются при создании и обновлении.

**Затронутые эндпоинты:** [создание](/docs/userfields/crm/create), [обновление](/docs/userfields/crm/update) пользовательского поля

### FIX-0619-4: частичное обновление позиции корзины

**Было**

[PATCH /v1/basket-items/:id](/docs/entities/basket-items/update) не выполнял частичное обновление позиции.

**Стало**

Частичное обновление работает, в теле обязательно поле `quantity`.

### FIX-0619-5: фильтр и сортировка настроек открытых линий

**Было**

У [настроек открытых линий](/docs/openlines/config/list) фильтр и сортировка работали не для всех полей, а значения при записи не нормализовались.

**Стало**

Фильтр и сортировка учитывают схему полей, булевы значения при записи приводятся к формату Битрикс24 (`Y`/`N`).

## 2026-06-18

### NEW-0618-1: группировка в агрегации сделок

[POST /v1/deals/aggregate](/docs/entities/deals/aggregate) принимает `groupBy: "stageSemanticId"` — разбивка по семантике стадии (в работе, успех, провал) для аналитики воронки.

### NEW-0618-2: пагинация и фильтр истории стадий

`GET /v1/stage-history` поддерживает пагинацию (`meta.total`, `meta.hasMore`) и фильтр по типу сущности `entityTypeId`.

### FIX-0618-3: учёт времени задачи не переназначает автора

**Было**

[PATCH /v1/tasks/:taskId/time/:id](/docs/entities/tasks/time) принимал поле `userId`, но Битрикс24 не переназначает автора записи — значение молча игнорировалось.

**Стало**

Поле `userId` отклоняется с `400` — автора записи учёта времени сменить нельзя.

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

Не передавайте `userId` при обновлении записи учёта времени.

### FIX-0618-4: таймзона события календаря

**Было**

[PATCH /v1/calendar-events/:id](/docs/entities/calendar-events/update) мог сохранять время в таймзоне пользователя Битрикс24, а не самого события.

**Стало**

Таймзона события сохраняется при обновлении.

## 2026-06-17

### NEW-0617-1: База знаний 2.0 (note.*)

Коллекции, документы и вложения базы знаний: создание, изменение, архивирование и удаление баз знаний и документов, загрузка вложений. Скоуп `note`.

**Затронутые эндпоинты (методы записи):** [POST /v1/note/collections](/docs/note/collections), [POST /v1/note/documents](/docs/note/documents), [POST /v1/note/documents/:documentId/files](/docs/note/files), а также парные `PATCH` и `DELETE`. Методы чтения добавлены отдельной записью.

## 2026-06-16

### NEW-0616-1: AI follow-up завершённых звонков

AI follow-up по завершённым звонкам. Скоуп `call`.

**В процессе раскатки** — методы выходят в обновлении Битрикс24 `call 26.600.0` и доступны не на всех порталах. Пока обновление не приехало на портал, метод возвращает `422 METHOD_NOT_YET_AVAILABLE` с целевой версией в ответе — это признак раскатки, а не ошибка интеграции.

**Затронутые эндпоинты:** [POST /v1/calls/followups/list](/docs/calls/followup/list), [GET /v1/calls/followups/:callId](/docs/calls/followup/get)

### FIX-0616-2: формат ответа транскрипции

**Было**

[POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) всегда возвращал JSON-объект, даже при `response_format=text`, `srt` или `vtt`.

**Стало**

`text`, `srt`, `vtt` отдают сырое тело в формате `text/plain`, SubRip или WebVTT. `json` и `verbose_json` отдают JSON-объект.

## 2026-06-12

### BC-0612-1: поле payed заказа только для чтения

> Поддержка старого формата до: не предусмотрена, поле стало read-only

**Было**

`payed` принимался в теле создания и обновления заказа.

**Стало**

`payed` доступно только для чтения — при записи отклоняется.

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

Уберите `payed` из тела `POST /v1/orders` и `PATCH /v1/orders/:id`.

**Затронутые эндпоинты:** [POST /v1/orders](/docs/entities/orders/create), [PATCH /v1/orders/:id](/docs/entities/orders/update)

## 2026-06-11

### NEW-0611-1: Scrum API

Эпики, привязка задач к эпикам и чтение чата задачи. Скоуп `tasks`.

**Затронутые эндпоинты:** `/v1/scrum/epics`, `/v1/scrum/epics/:id`, `/v1/scrum/tasks/:taskId` — раздел [Scrum](/docs/scrum)

### NEW-0611-2: оценка звонка при завершении

[POST /v1/calls/:callId/finish](/docs/telephony/crm/finish) принимает оценку завершённого звонка и передаёт её в Битрикс24.

## Смотрите также

- [Обзор API](/docs/quickstart)
- [Коды ошибок](/docs/errors)
- [Batch-запросы](/docs/batch)
