# Изменения API: 18 сентября 2026

[← Журнал изменений](/docs/changelog) · [Сентябрь 2026](/docs/changelog/2026-09)

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

**Было**

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

**Стало**

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

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

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

**Затронутые эндпоинты:** [GET /v1/quotes](/docs/entities/quotes/list), [POST /v1/quotes/search](/docs/entities/quotes/search), [POST /v1/quotes/aggregate](/docs/entities/quotes/aggregate), [POST /v1/{entity}/batch](/docs/batch), [POST /v1/batch](/docs/batch).

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

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

**Было**

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

**Стало**

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

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

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

**Затронутые эндпоинты:** `POST /v1/cowork/applications/:id/key`,
[GET /v1/applications](/docs/applications/list),
[GET /v1/applications/:id](/docs/applications/get) и тринадцать пар, на которых работает ключ
участника: [GET /v1/infra/servers/:id](/docs/infra/servers/get),
[POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy),
[POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec),
[POST /v1/infra/servers/:id/upload](/docs/infra/deploy/upload),
[GET /v1/infra/servers/:id/logs](/docs/infra/deploy/logs),
[POST /v1/infra/servers/:id/wake](/docs/infra/lifecycle/wake),
[GET /v1/infra/servers/:id/sources](/docs/source-storage/servers),
[POST /v1/infra/servers/:id/sources](/docs/source-storage/servers),
[GET /v1/infra/servers/:id/sources/:versionId](/docs/source-storage/servers),
[GET /v1/infra/servers/:id/sources/:versionId/download](/docs/source-storage/servers),
[DELETE /v1/infra/servers/:id/lock](/docs/infra/deploy/lock),
[POST /v1/infra/servers/:id/access-tokens](/docs/infra/access-tokens/create),
[DELETE /v1/infra/servers/:id/access-tokens/:tokenId](/docs/infra/access-tokens/delete).

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

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

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

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

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

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

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

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

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

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

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

**Было**

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

**Стало**

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

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

**Было**

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

**Стало**

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

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

**Было**

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

**Стало**

У хода с вызовом функции `content` равен `null`, рассуждение приходит только в `reasoning_content`, как в [примере вызова функций](/docs/ai/chat/tools). Ответы без рассуждения и ответы с итоговым текстом не изменились.

**Затронутые эндпоинты:** [POST /v1/chat/completions](/docs/ai/chat/completions).

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

**Было**

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

**Стало**

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

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

**Было**

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

**Стало**

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

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

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

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

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

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

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