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

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

### FIX-0920-1: спецификация OpenAPI и справочник API называют обязательные ключи фильтра и считают `count` по `"*"`

**Было**

Девять сущностей не выполняют чтение без обязательного ключа фильтра: календарь (`type`, `ownerId`), разделы календаря, файлы и папки Диска, комментарии таймлайна (`entityType`, `entityId`), товары и разделы каталога (`iblockId`), значения списочных свойств товаров (`propertyId`) и узлы оргструктуры (`type`). Без ключа операция отвечает `400 MISSING_REQUIRED_PARAMS` или `400 MISSING_REQUIRED_FILTER`. Машинная спецификация `GET /v1/openapi.json` об этом молчала: у списка `GET` ни одной из девяти сущностей, у поиска `POST /search` шести из них и у агрегации [POST /v1/catalog-products/aggregate](/docs/entities/catalog-products/aggregate) фильтр был описан как необязательный. Примеры справочника вели в тот же отказ: поиск печатал пустой `filter`, список — вызов без параметров. Пример агрегации у двадцати с лишним сущностей считал `count` по полю, хотя операция принимает `count` только по `"*"` и отвечает `400 INVALID_PARAMS`, а перечень допустимых полей агрегации в спецификации `"*"` не содержал. Пакетный вызов сущности `POST /v1/{entity}/batch` в примере передавал `limit` рядом с `params`, где подвызов его не читает.

**Стало**

Спецификация объявляет обязательные ключи там, где операция их требует. У поиска и агрегации они указаны в описании `filter`, у поиска файлов и папок ключ можно передать и на верхнем уровне тела. Значение `folderId` / `parentId` описано как непустой скаляр. У списка каждый ключ описан отдельным параметром запроса, и его можно передать как `?type=…` или как `filter[type]=…`, как и раньше. Машиночитаемый перечень ключей добавлен расширением `x-required-filter-keys`, а признак, поднимает ли пакетный вызов обязательные параметры подвызова, — расширением `x-batch-list-lifts-params`. Каждый ключ назван вместе с кодом отказа. Перечень полей агрегации содержит `"*"`. Подвызов пакетного вызова описан полем `params`. Примеры справочника передают обязательные ключи, агрегация считает `count` по `"*"`, пакетный пример кладёт параметры в `params`. Там, где пакетный `list` не может получить обязательные параметры, пример показывает `get`, а у разделов календаря — `create`. Ответы операций не изменились.

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

Рабочие запросы не затронуты: операции отказывали без этих ключей и раньше. Клиент, сгенерированный по спецификации, после обновления получит обязательный `filter` у поиска и агрегации этих сущностей. Исключение — поиск файлов и папок: там обязателен ключ, в `filter` или на верхнем уровне тела.

### BC-0920-2: правка и удаление несуществующего поля сотрудника отвечают 404

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

**Было**

[PATCH /v1/userfields/users/:id](/docs/userfields/users/update) и [DELETE /v1/userfields/users/:id](/docs/userfields/users/delete) на идентификатор, которого на портале нет, отвечали `422 BITRIX_ERROR` с сообщением `Access denied.` — тем же ответом, что и настоящий отказ по правам. Повторное удаление уже удалённого поля отвечало так же, а [GET /v1/userfields/users/:id](/docs/userfields/users/get) на тот же идентификатор отвечал `404`. Идентификатор с ведущими нулями `GET` не находил: `007` отвечал `404`, хотя правка и удаление того же поля по `007` работали.

**Стало**

Перед правкой и удалением платформа проверяет, существует ли поле. Поля нет — ответ `404 ENTITY_NOT_FOUND`, и запрос на изменение на портал не уходит. Тот же код приходит на повторное удаление и на идентификатор поля, которое не является полем сотрудника. Отказ по правам остаётся прежним: владельцу ключа, не являющемуся администратором портала, приходит `422 BITRIX_ERROR` с сообщением `Access denied.`. Заодно `GET /v1/userfields/users/:id` теперь находит поле по идентификатору с ведущими нулями: `007` читается как `7`, как это давно работает у правки и удаления.

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

Ветку «поля нет» переведите с `422 BITRIX_ERROR` на `404 ENTITY_NOT_FOUND` — так же, как она уже написана для полей CRM. Повторный `DELETE`, получивший `404`, означает «поле уже удалено», а не ошибку. Ветку `422 BITRIX_ERROR` с сообщением `Access denied.` сохраните: теперь она означает нехватку прав либо гонку, когда поле удалили между проверкой и записью. Правка и удаление стали на один запрос к порталу дольше.

### FIX-0920-3: два одновременных создания сервера приложению дают одну машину

**Было**

Два запроса `POST /v1/infra/servers`, пришедшие почти одновременно ключом одного приложения, у которого сервера ещё нет, выполнялись оба: каждый видел приложение без сервера, и каждый создавал платную машину. К карточке приложения привязывалась одна из них, вторая оставалась работать и тарифицироваться, но не показывалась ни в карточке, ни в переиспользовании — найти её можно было только в списке `GET /v1/infra/servers`, а удалять приходилось руками.

**Стало**

Речь об обычном создании — запросе БЕЗ `placement: "dedicated"`. Место приложения под сервер занимается на платформе Вайбкод неделимо, в тот же момент, когда запись сервера создаётся в базе, и до обращения в облако. Это касается и приложений, вызывающих API ключом авторизации: раньше их карточка получала сервер только при выкладке, а до неё каждое следующее создание заводило ещё одну машину — теперь сервер встаёт в карточку сразу, и повторный вызов возвращает его же. Поэтому вторая машина больше не рождается: запрос, опоздавший к свободному месту, останавливается раньше, чем что-либо оплачено, и в ответ получает `201` с сервером, который создал первый запрос, и `reused: true`, так что повторять запрос не нужно. Если такой опоздавший вызов нёс исходники, судьбу их называет сам ответ, и она зависит от того, есть ли что портить. Когда на возвращаемом сервере прямо сейчас идёт выкладка первого запроса — и всегда на обычном сервере, куда создание исходники не кладёт в принципе, — вторая выкладка поверх чужой не запускается: ответ несёт `sourceIgnored: true`, следите за состоянием сервера, а свои исходники выложите отдельным вызовом, если они отличались. Когда портить нечего (сервер приложения ещё ни разу не выложен), исходники опоздавшего вызова выкладываются обычным порядком и ответ несёт `deploying: true`. Смотрите на эти поля, а не предполагайте исход заранее. Если сойтись на этот сервер не удалось, ответ будет `409` с новым кодом `APPLICATION_SERVER_SLOT_TAKEN`, и машина по нему всё равно не создаётся. Такой ответ означает, что пригодного к выдаче сервера у приложения сейчас нет: его карточку успели удалить либо стоящая на ней машина отдана быть не может — она мертва или это общий хост. Повтор запроса в обоих случаях заведёт НОВЫЙ сервер, а не вернёт прежний, поэтому перед повтором стоит прочитать `GET /v1/applications`.

Защита работает и тогда, когда карточки приложения ещё нет: первый вызов заводит сервер, второй получает `409` с кодом `APPLICATION_FIRST_SERVER_IN_PROGRESS`, и повтор возвращает уже существующий сервер, как только карточка появилась. Коды отказа тут намеренно разные, потому что и делать по ним надо разное: после `APPLICATION_FIRST_SERVER_IN_PROGRESS` повтор вернёт сервер, а после `APPLICATION_SERVER_SLOT_TAKEN` — заведёт новый. Различайте их по коду: текст сообщения для этого не предназначен. Границ у защиты две, обе намеренные. Первая — запрос с `placement: "dedicated"`: им дополнительную машину просят осознанно, и отказывать в ней нельзя. Вторая — аккаунт, где раздел «Приложения» выключен: карточек там не заводят вовсе, обе машины остаются видны в списке серверов, и ни одна не теряется. На этих двух путях собственную защиту от повторных вызовов снимать рано. Успешные ответы одиночных созданий не изменились.

### FIX-0920-5: статус сервера перестаёт показывать «подключён» при мёртвом туннеле

**Было**

Когда соединение сервера с платформой обрывалось незаметно, статус сервера мог навсегда остаться `CONNECTED`: карточка сервера показывала «Работает», хотя вызовы внешнего API приложения уже не доходили. Вызывающий получал `503 APP_API_UNAVAILABLE`, но состояние само не исправлялось — сервер оставался в этом виде до случайного переподключения, и по статусу отличить «приложение сейчас молчит» от «соединения нет вовсе» было нельзя.

**Стало**

Получив отказ из-за оборванного соединения, платформа сверяется с фактическим списком живых соединений и, если соединения действительно нет, снимает неверный `CONNECTED` — статус сервера становится `DISCONNECTED`, и сервер подхватывает штатное восстановление. Сверка идёт только на этой ветке отказа и только при подтверждённом отсутствии соединения: пока список живых соединений недоступен или соединение в нём есть, статус не меняется. Ответ вызывающему при этом прежний — `503 APP_API_UNAVAILABLE`, новых кодов ошибок нет.

### FIX-0920-6: схема OpenAPI объявляет отказ по скоупу на читающих операциях сущностей

**Было**

Читающие операции сущностей — список, чтение по идентификатору, `fields`, `search`, `aggregate`, чтения связей и позиций товаров — не объявляли в схеме OpenAPI ответ `403`, хотя API Вайбкод отвечает `403` с кодом `SCOPE_DENIED`, когда у ключа нет скоупа, который требует операция. Требование скоупа при этом публиковалось: расширение `x-required-scope` и фраза «Requires scope» в описании операции. Клиент, сгенерированный по схеме, не имел модели ошибки для самого обычного отказа и получал её как непредусмотренный ответ сервера.

**Стало**

Каждая читающая операция сущности объявляет `403` с кодом `SCOPE_DENIED` и перечисляет коды, которые приходят на том же статусе от общего гейта ключа и от самого Битрикс24. У `fields` кодов Битрикс24 в этом перечислении нет намеренно, и причин тут две. Там, где у сущности есть живой метод набора полей, запрос выполняется по мере возможности, а его отказ приходит успешным ответом с предупреждением `fields_partial`. Там, где такого метода нет, набор полей отдаётся по объявленному контракту и обращения в Битрикс24 не происходит вовсе — это сказано в описании самой операции.

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

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

### BC-0920-7: пакетные записи и импорт больше не отвечают успехом, когда Битрикс24 не применил стадию или ручную сумму

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

**Было**

Одиночные `PATCH /v1/deals/{id}`, `POST /v1/deals/{id}/move` (и те же у лидов) уже отвечали `422 STAGE_NOT_APPLIED`, а `POST` / `PATCH /v1/items/{entityTypeId}` — `422 AMOUNT_NOT_APPLIED`, когда Битрикс24 принял запись, но не применил стадию, воронку или явно запрошенный ручной режим суммы. Те же записи через `POST /v1/{entity}/batch`, общий `POST /v1/batch` и `POST /v1/{entity}/import` отвечали `success: true` по каждому элементу: сделка оставалась на прежней стадии (при импорте — ложилась на стадию по умолчанию), сумма элемента смарт-процесса становилась нулём, а клиент видел успех.

**Стало**

В `POST /v1/{entity}/batch` элемент, чью стадию, воронку или ручную сумму Битрикс24 не применил, приходит с `success: false`, кодом `STAGE_NOT_APPLIED` либо `AMOUNT_NOT_APPLIED` в `error`, пояснением в `message` и `details.unappliedFields` / `details.currentValues`; такой элемент сохраняет `id` — запись уже создана или изменена, повторять её не нужно. В общем `POST /v1/batch` такой подвызов переходит из `data.results` в `data.errors` под своим `id`, с теми же `code` / `message` / `details`, а запись, которую Битрикс24 вернул в ответе на подвызов, приходит в `data.errors.<id>.data`. В `POST /v1/{entity}/import` строка результата получает те же поля и тоже сохраняет `id`. Для обеих пакетных дверей лишних обращений к Битрикс24 не появилось: сверяется запись, которую Битрикс24 и так возвращает в ответе на каждый подвызов. Импорт после всех пачек один раз перечитывает созданные записи страницами по пятьдесят, и только те, у которых была стадия, воронка или явный ручной режим суммы, — остальные импорты стоят ровно столько, сколько раньше; импорт не запускает правила автоматизации, поэтому перечитанная запись — итог самого импорта. Одиночное создание `POST /v1/deals` и `POST /v1/leads` стадию не сверяет и отвечает как прежде: там автоматизация портала работает, и робот «при создании — сменить стадию» выдавал бы ложный отказ. Лид, который портал в простом режиме CRM сам перевёл в «сконвертирован» при создании, за неприменённую стадию не считается; при изменении уже закрытого лида неприменённая стадия отказывается, как и в одиночном `PATCH`. Записи с валидными значениями отвечают как прежде; сумма без явного `isManualOpportunity: true` по-прежнему не проверяется.

### FIX-0920-8: рассуждение прошлого хода снова доходит до модели

**Было**

В диалоге с инструментами рассуждение прошлого хода ассистента, возвращённое в `messages[].reasoning_content`, до модели не доходило. Модель теряла нить между вызовами инструментов, а текст рассуждения не попадал во входные токены.

**Стало**

Платформа передаёт рассуждение под тем именем, которое шаблон модели читает, и в диалоге с инструментами модель снова видит свои прошлые рассуждения. Поле запроса не изменилось: возвращайте рассуждение в `messages[].reasoning_content`, как описано в [вызове функций](/docs/ai/chat/tools). Текст рассуждения теперь учитывается во входных токенах, поэтому `usage.prompt_tokens` у таких запросов выше.

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