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

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

### FIX-0819-1: отправка сообщений ботом принимает верхнеуровневый dialogId

**Было**

При отправке сообщения ботом через MCP верхнеуровневый `dialogId` не попадал в запрос [POST /v1/bots/:botId/messages](/docs/bots/messages/send), а запрос без адресата возвращал ошибку платформы.

**Стало**

Верхнеуровневый `dialogId` передаётся как канонический адресат, а прежний `body.dialogId` остаётся совместимым запасным форматом. Запрос без непустой строки `dialogId` возвращает `MISSING_PARAMS` до отправки сообщения.

### FIX-0819-2: заголовок `X-Tariff-Checked-At` отдаётся только после удачной сверки тарифа

**Было**

Отметка ставилась после ЛЮБОЙ попытки сверки тарифа с Битрикс24, включая
неудачную: портал, у которого сверка упала по таймауту или по нехватке прав
ключа, всё равно получал свежий `X-Tariff-Checked-At`. Отличить «тариф прочитан
час назад» от «час назад попытались и не смогли» по заголовкам было нельзя.

**Стало**

Заголовок отдаётся, только когда последняя сверка действительно прочитала тариф.
Неудачная попытка заголовок не выставляет вовсе — его отсутствие теперь значит
«достоверной сверки нет», а не «портал никогда не проверяли». Заголовок
`X-Tariff-Is-Commercial` не изменился и приходит как раньше.

### FIX-0819-3: уточнено встраивание личным ключом и обработан OAuth без state

**Было**

`GET /v1/me` ошибочно утверждал, что личный ключ вообще не может опубликовать встроенное приложение. Если Битрикс24 возвращал OAuth-код без `state`, `/v1/bitrix-handler` оставался на промежуточной странице.

**Стало**

`portalEmbedding` описывает рабочий цикл `POST /v1/apps` → OAuth-авторизация → `POST /v1/apps/:id/publish` и отдельно предупреждает, что личный ключ не вызывает `placements/bind` напрямую и сам не даёт transparent auth. OAuth-callback без `state` безопасно перенаправляется на контролируемую страницу ошибки без передачи кода.

### BC-0819-5: поле freshnessWindowMinutes больше не приходит в ответах

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

**Было**

[GET /v1/me](/docs/keys-auth/me) отдавал в блоке `capabilities.apps.sourceStorage` поле `freshnessWindowMinutes` со значением `10`, а отказ `409 SNAPSHOT_REQUIRED` у [POST /v1/apps/:id/publish](/docs/apps/publish) нёс то же поле внутри `hint`. Поле называло окно в минутах, в пределах которого сохранённый снапшот исходников считался пригодным для публикации.

**Стало**

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

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

Читайте `freshnessWindowMinutes` как необязательное поле: если ваш код требует его наличия, приводит к числу без проверки или строит на нём таймер повторного сохранения — уберите эту зависимость. Отсутствие поля трактуйте как «возраст снапшота не проверяется». Поле вернётся только в том случае, если публикация снова начнёт проверять возраст, и тогда его значение снова будет осмысленным. Полная схема подсказки — [Хранилище исходников](/docs/source-storage).

### BC-0819-6: публикация отказывает, если файлы сохранённой версии исходников удалены

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

**Было**

[POST /v1/apps/:id/publish](/docs/apps/publish) выбирал сохранённую версию исходников по записи о ней, не проверяя, живы ли её файлы. Поэтому версия, у которой файлы уже вычищены из хранилища или помечены на удаление, годилась для публикации: запрос отвечал `200`, приложение получало статус `PUBLISHED`, а скачивание опубликованной версии затем отвечало `410 SOURCE_VERSION_BYTES_PURGED`. Опубликованными оказывались исходники, которых нет.

**Стало**

Версии без файлов публикация не выбирает вовсе. Если других сохранённых версий у приложения или у сервера нет, ответ — `409 SNAPSHOT_REQUIRED` с `hint.reason` = `app_snapshot_missing` или `server_snapshot_missing`: файлов нет, значит и снапшота нет. Если такая версия передана явным `sourceVersionId`, ответ тот же `409`, а `hint.lastSnapshot` приходит как `null`. Это изменение не связано с проверкой возраста снапшота и действует всегда.

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

Проверяйте ответ публикации на `409 SNAPSHOT_REQUIRED` и в том сценарии, где раньше он приходить не мог, — у приложения, чьи файлы исходников удалены из хранилища или помечены на удаление. Восстановление одно: сохраните архив заново через `POST /v1/apps/:id/sources` или `POST /v1/infra/servers/:id/sources` и повторите публикацию. Срок хранения и удаление файлов описаны в разделе [Хранилище исходников](/docs/source-storage).

### FIX-0819-7: публикация приложения больше не отказывает из-за возраста сохранённых исходников

**Было**

[POST /v1/apps/:id/publish](/docs/apps/publish) отвечал `409 SNAPSHOT_REQUIRED`, если сохранённый снапшот исходников был старше десяти минут, даже когда сам код не менялся. Проверка измеряла не соответствие исходников, а время с последнего сохранения, поэтому обычный порядок «деплой, затем авторизация приложения на портале, затем публикация» упирался в отказ: шаг авторизации выполняет человек, и он легко занимает больше окна. Чтобы пройти дальше, приходилось повторно сохранять тот же самый архив через `POST /v1/apps/:id/sources` или `POST /v1/infra/servers/:id/sources`. В подсказке отказа приходило `hint.reason` со значением `app_snapshot_stale` или `server_snapshot_stale`.

**Стало**

Возраст сохранённых исходников публикацию не ограничивает: подойдёт снапшот любой давности. Отказ `409 SNAPSHOT_REQUIRED` остаётся только тогда, когда пригодного снапшота нет вовсе, и в этом случае `hint.reason` содержит `app_snapshot_missing` или `server_snapshot_missing`. Значения `app_snapshot_stale` и `server_snapshot_stale` в ответе больше не встречаются — они возвращаются, только если публикация снова начнёт проверять возраст снапшота.

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

Менять ничего не нужно: у публикации просто исчез один из поводов для отказа. Повторное сохранение неизменившегося архива перед публикацией теперь лишнее — его можно убрать из сценария. Если ваш код разбирает `hint.reason`, ветки под значения `app_snapshot_stale` и `server_snapshot_stale` перестанут срабатывать, но остаются валидными: набор значений поля не менялся.

### FIX-0819-8: Публикация приложения больше не помечает версию исходников как успешно задеплоенную

**Было**

После успешной публикации платформа записывала на выбранную версию исходников статус деплоя `success` — независимо от того, чем деплой этой версии закончился и был ли он вообще. В списке версий ([GET /v1/infra/servers/:id/sources](/docs/source-storage)), в файле описи и в реестре источников версия неудачного деплоя после публикации выглядела успешной, а версия, сохранённая вручную, получала статус деплоя, которого не было.

**Стало**

Публикация записывает только отметку о себе — `linkedDeployId` вида `publish:<время>`. Поле `deployStatus` остаётся тем, чем его сделал деплой: `success`, `failed` либо пусто у версий, сохранённых вручную.

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

Менять ничего не нужно. Если вы читали `deployStatus` как признак «эта версия опубликована» — это никогда не было его смыслом; признак публикации несут пометка `published` в `tags` и префикс `publish:` в `linkedDeployId`.

### BC-0819-9: промокод на закрытом рабочем месте: код отказа `COUPON_SEAT_CANCELLED` больше не возвращается

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

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

Из-за этого пропал код отказа `COUPON_SEAT_CANCELLED`: возвращать его больше нечему. Если ваш код разбирает причины отказа списком, уберите его — ветка стала недостижимой.

Один отказ на закрытом месте остался, но с другим кодом. Место, закрытое досрочно при неистёкшем оплаченном сроке, отвечает `COUPON_SEAT_ALREADY_PAID_LONGER`: погашение перезаписало бы оплаченный срок подарочным, и купленные месяцы исчезли бы со строки, по которой считается возврат.

**Затронутые эндпоинты:** `POST /v1/cowork/coupon/preview`, `POST /v1/cowork/coupon/redeem`

### FIX-0819-10: промокод больше не съедает оплаченный срок на понижённом месте

**Было**

Если место понижали с предоплаченного тарифа, на строке оставался
оплаченный срок (дата окончания в будущем и цена купленного месяца), но списание уже не
планировалось. Погашение промокода на таком месте проходило: `POST /v1/cowork/coupon/redeem`
отвечал успехом, а выданный подарок переписывал дату окончания оплаченного срока на срок
подарка. Купленные месяцы исчезали со строки, по которой считается возврат.

**Стало**

Такое погашение отклоняется кодом `COUPON_SEAT_PAID_TERM_ACTIVE` — новым в наборе отказов
`POST /v1/cowork/coupon/redeem`. Оплаченный срок на месте не меняется, промокод остаётся у
человека и гасится после окончания срока. Прочие отказы набора и их условия прежние.

### BC-0819-11: отвязка мест встраивания, которую не удалось подтвердить, больше не выглядит успехом

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

**Было**

[POST /v1/apps/{id}/unpublish](/docs/apps/unpublish) отвечал `200` и очищал `placements` независимо от того, сняла ли платформа привязки на Битрикс24. [POST /v1/apps/{id}/publish](/docs/apps/publish) и [PATCH /v1/apps/{id}](/docs/apps/update) с изменённым набором мест встраивания отвечали `200`, даже если снять лишние места не удалось. [POST /v1/placements/unbind](/docs/apps/placements/unbind) удалял место встраивания из списка приложения, не дождавшись подтверждения от аккаунта.

**Стало**

Снятие с публикации по-прежнему отвечает `200` и переводит приложение в `UNPUBLISHED`, но `placements` теперь содержит коды, снять которые не удалось, а `warnings` — причину по каждому. Публикация и `PATCH` при неподтверждённом снятии отвечают `502` с кодом `PLACEMENT_UNBIND_FAILED` и списком кодов в `error.placements`; приложение при этом не публикуется и метаданные каталога не сохраняются. Отвязка одного места встраивания при неподтверждённом ответе аккаунта отвечает `502` с кодом `BITRIX_UNAVAILABLE` и оставляет место в списке приложения.

Отдельно: [POST /v1/apps/{id}/publish](/docs/apps/publish) и [PATCH /v1/apps/{id}](/docs/apps/update) начали отвечать `503` с кодом `NETWORK_DEVKEY_REQUIRED`, если аккаунт переведён на транспорт ключа разработчика, а у автора приложения такого ключа нет. Раньше этот код у обеих ручек не встречался.

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

Возможность раскатывается постепенно и включается по аккаунтам: до включения на вашем аккаунте все четыре эндпоинта ведут себя как прежде, неподтверждённое снятие по-прежнему выглядит успехом, а коды `PLACEMENT_UNBIND_FAILED` и `NETWORK_DEVKEY_REQUIRED` не встречаются вовсе. Код `BITRIX_UNAVAILABLE` на отвязке одного места был и до включения — меняется только его условие: после включения им же отвечает неподтверждённое снятие. После включения проверяйте `placements` в ответе снятия с публикации: пустой список означает, что снято всё. На `502` с кодом `PLACEMENT_UNBIND_FAILED` повторите запрос — перечисленные места встраивания на аккаунте остались. Если сценарий полагался на `200` как на признак снятия, замените проверку на пустоту `placements`. На `503` с кодом `NETWORK_DEVKEY_REQUIRED` повтор не поможет: попросите автора приложения переподключить аккаунт.

**Затронутые эндпоинты:** [POST /v1/apps/{id}/publish](/docs/apps/publish), [POST /v1/apps/{id}/unpublish](/docs/apps/unpublish), [PATCH /v1/apps/{id}](/docs/apps/update), [POST /v1/placements/unbind](/docs/apps/placements/unbind)

### NEW-0819-12: коробочный портал может подключить демо Маркетплейса

**Было**

`POST /v1/portals/{id}/activate-market-trial` и `POST /v1/cowork/activate-market-trial`
отказывали коробочному порталу всегда: `409 TRIAL_ACTIVATION_UNAVAILABLE`, причина
недоступности в `/v1/cowork/state` — `not_cloud`. Демо Маркетплейса было доступно
только облачным порталам.

**Стало**

Коробочный портал активирует демо через те же ручки. Доступность решает Битрикс24 по
лицензии коробки, поэтому причина отказа стала точнее: `demo_used`, когда демо выдать
нельзя, и `not_supported`, пока доступность ещё не прочитана. Ответы и коды ошибок не
менялись.

Возможность включается постепенно и по умолчанию выключена.

### BC-0819-13: разбор тела запроса у POST /v1/cowork/deploy-key

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

**Было**

Запрос с заголовком `Content-Type: application/json` и пустым телом отвечал `400`. Запрос с заголовком `Content-Type: text/plain` и непустым телом принимался.

**Стало**

Пустое тело принимается с любым заголовком: ручка тело не читает. Непустое тело неизвестного типа отвечает `415`.

**Что делать**

Убрать `Content-Type: text/plain` из вызова либо не передавать тело. Прежнее поведение снимается в момент выката, окна поддержки нет.

### NEW-0819-14: отзыв ключа устройства Cowork/Code самим ключом

**Было**

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

**Стало**

Появился `DELETE /v1/cowork/key`. Ручка гасит предъявленный ключ — идентификатор не передаётся, поэтому отозвать чужой ключ нельзя. Нужны скоуп `vibe:cowork` и ключ класса десктопа Cowork/Code, иначе `403 COWORK_DESKTOP_KEY_REQUIRED`; без скоупа — `403 INSUFFICIENT_SCOPE`. Повторный вызов тем же секретом отвечает `401 KEY_INACTIVE`. Темп — 5 запросов в минуту на ключ, сверх лимита `429 RATE_LIMITED`.

Ручка работает и при нулевом балансе, и при исчерпанной суточной квоте: аварийный выход не запирается.
