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

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

### 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`.

Отдельно: англоязычная подсказка про модуль «Универсальные списки» больше не содержит русского названия на международных порталах.

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