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

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

### FIX-0907-1: погашение купона на своём портале закрыто новым кодом отказа

**Было**

`POST /v1/cowork/coupon/redeem` смотрел только на купон, кампанию и место. Кто именно гасит код
и чей это портал, ручку не интересовало: сотрудник платформы, имеющий право выдавать купоны, мог
выписать код и активировать его на портале, за которым стоит он сам, — тариф уезжал за счёт
платформы, а в ответе было обычное `HTTP 200`.

**Стало**

Погашение отбивается кодом `COUPON_SELF_PORTAL` (`HTTP 409`), если за порталом стоит сотрудник
платформы с правом выдавать купоны — сам гасящий либо любой участник портала, владелец
API-ключа или владелец сервера на нём. Успешное погашение при этом не меняется: на портале без
таких людей ответ остаётся HTTP 200 с тем же телом, и трогать интеграцию не нужно. Попытку в
счётчике подбора кодов отказ не расходует — причина в составе портала, а не в том, что человек
не угадал код. Код добавлен в таблицу ответов ручки в документации.

### NEW-0907-2: самоописание отвечает, остановлена ли инфраструктура за неуплату

Ответы [GET /v1/me](/docs/keys-auth/me) и [GET /v1/cowork/state](/docs/cowork/state)
несут новый блок `infraState`: `frozen` — остановлены ли за неуплату серверы, деплой и
хранилище, `reason` — код причины (`DEBT` либо `null`), `topupUrl` — адрес пополнения
кабинета либо `null`.

Блок отвечает на вопрос, который до сих пор было неоткуда прочитать: что именно
остановлено у портала с долгом. Строки внутри блока машинные, текст для человека пишет
клиент.

⚠️ Смысл поля зависит от того, включена ли на портале развязка «долг за инфраструктуру
гасит только инфраструктуру» (`FIX` в этом же выпуске). Пока не включена, отрицательный
баланс отбивает вызовы V1 широко, включая `GET /v1/cowork/state`; читается в этом режиме
только `GET /v1/me` — он от заморозки освобождён. `infraState.frozen` там означает
«остановлено практически всё». После включения поле означает
ровно то, что написано: инфраструктура остановлена, а вызовы в пределах месячной квоты
тарифа продолжают работать.

Блок добавлен, существующие поля не изменились.

### FIX-0907-3: исправлена пагинация списков действий и роботов бизнес-процессов

**Было**

Запрос с `offset=50` возвращал те же коды, что и запрос с `offset=0`. Ответ оставался успешным с HTTP 200.

**Стало**

`offset` пропускает указанное число кодов в полном списке. `meta.total` показывает размер полного списка, а `meta.hasMore` — остались ли коды после текущей страницы. То же поведение действует для list-подвызовов этих сущностей в глобальном пакетном запросе, включая оба канала total. Ответ по-прежнему возвращает HTTP 200, требования к OAuth-авторизации не изменились.

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

Действия не требуются. Интеграции, использующие `offset`, теперь получают запрошенную страницу вместо повторной первой страницы; формат ответа и требования к авторизации не изменились.

**Затронутые эндпоинты:** [GET /v1/bizproc-activities](/docs/entities/bizproc-activities/list), [GET /v1/bizproc-robots](/docs/entities/bizproc-robots/list), [POST /v1/batch](/docs/batch).

### FIX-0907-4: долг за инфраструктуру больше не выключает то, что оплачено отдельно

**Было**

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

**Стало**

Заморозка счёта применяется к тому, что платится кошельком: серверы, хранилище, деплой,
поиск и research за деньги платформы, расход сверх месячной квоты, выписка ключей
развёртывания и создание приложения. Вызов по месячной квоте тарифа, оплаченный период подписки Коворка и REST-прокси к своему
порталу проходят при любом состоянии кошелька.

⚠️ Новое поведение включается по порталам постепенно. Пока оно на портале не включено, ответы
прежние.

### FIX-0907-5: завышенный числовой CRM ID отклоняется до обращения к Битрикс24

**Было**

Числовые ID CRM проверялись только на форму: строка из цифр без ведущих нулей проходила гард целиком. Это затрагивало обычный `:id` записи в пути (`/v1/deals/{id}`, `/v1/contacts/{id}` и аналогичные сущности), а также положительные `entityTypeId` smart-processes и соседних CRM-операций. ID длиннее безопасного диапазона целых уезжал в Битрикс24, где мог округлиться до другого значения, а часть методов `crm.item` отвечала на него сырой ошибкой PHP вместо внятного отказа.

**Стало**

Такие ID отклоняются с ошибкой невалидных параметров до вызова Битрикс24. Проверка действует для обычных record ID в path и batch, а также для `entityTypeId` smart-processes, dynamic-параметров и связанных CRM-операций. Граница проходит по безопасному целому: `9007199254740991` по-прежнему принимается, `9007199254740992` и всё, что длиннее, отклоняется. ID `0` остаётся допустимым там, где он был разрешён раньше (например, для основной воронки сделок).

### FIX-0907-6: конвертация лида учитывает выбранную воронку сделки

**Было**

`POST /v1/leads/{id}/convert` игнорировал параметр `categoryId`, поэтому новая сделка попадала в воронку по умолчанию.

**Стало**

Если передать `categoryId`, новая сделка сразу создаётся в указанной воронке. Значение `0` по-прежнему выбирает воронку по умолчанию.

Целочисленная неотрицательная строка остаётся допустимой для совместимости и приводится к числу. Остальные формы значения по-прежнему игнорируются и оставляют сделку в воронке по умолчанию; `null` и отсутствие параметра равнозначны.

### NEW-0907-7: номер сотрудника Битрикс24 у автора приложения в ответах /v1/apps

Ответы семейства приложений теперь несут два новых поля: `authorBitrixUserId` —
числовой идентификатор сотрудника Битрикс24, создавшего приложение, и
`authorBitrixUserIdSource` — признак того, откуда этот идентификатор взят.
`authorBitrixUserId` — тот же идентификатор, что приходит в поле `id` ответа
[GET /v1/users](/docs/entities/users/list), поэтому он служит ключом связи между
двумя ответами: раньше у приложения был только `authorId` — идентификатор
пользователя платформы Вайбкод, — и сопоставить создателя с карточкой сотрудника
было нечем.

Значения `authorBitrixUserIdSource`: `member` — идентификатор взят из
подтверждённого участия автора в этом аккаунте Битрикс24, на такой идентификатор
ссылку ставить можно; `snapshot` — идентификатор взят из значения, снятого при
создании приложения, это оценка по возможности, и она может указывать на другого
сотрудника, чем текущий автор приложения; `null` — идентификатор неизвестен,
`authorBitrixUserId` тогда тоже `null`. Реестру, который не должен ошибаться,
ссылку на карточку сотрудника стоит ставить только при `member`. На аккаунтах в
коробке и на аккаунтах с микросервисными учётными данными значение `snapshot` не
выдаётся вовсе: там происхождение снятого значения не связано с подтверждением
личности. Подтверждённое участие на таких аккаунтах работает как обычно, поэтому
идентификатор там либо `member`, либо пуст.

Имя и фамилию автора ответы приложений НЕ отдают: это решение принято сознательно
и сужает исходную заявку, которая просила ещё и имя. Персональные данные не
уезжают в ответ, который читает любой ключ аккаунта, включая ключ, вшитый в код
развёрнутого приложения; имя запрашивается по `authorBitrixUserId` из
[GET /v1/users](/docs/entities/users/list), где доступ к нему ограничен
разрешением `user`.

Существующие запросы продолжают работать без изменений: поля добавлены, ни одно
не убрано и не переименовано.

**Затронутые эндпоинты:** [GET /v1/apps](/docs/apps/list),
[GET /v1/apps/:id](/docs/apps/get), [POST /v1/apps](/docs/apps/create),
[PATCH /v1/apps/:id](/docs/apps/update),
[POST /v1/apps/:id/publish](/docs/apps/publish),
[POST /v1/apps/:id/unpublish](/docs/apps/unpublish),
[POST /v1/apps/:id/relink-oauth](/docs/apps/relink-oauth)

### BC-0907-8: Поле companies удалено из API контактов

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

**Было**

Поле `companies` публиковалось в [GET /v1/contacts/fields](/docs/entities/contacts/fields) и принималось в `select` для операций чтения контактов. Платформа Битрикс24 не возвращала значение этого поля, поэтому успешный ответ мог не содержать ключ `companies`.

**Стало**

Поле `companies` больше не публикуется и всегда удаляется из ответов контактов. Попытка запросить его в `select` отклоняется с `400 UNKNOWN_SELECT_FIELD`.

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

Не запрашивать `companies`. Для идентификаторов связанных компаний использовать поле `companyIds`.

**Затронутые эндпоинты:** [GET /v1/contacts](/docs/entities/contacts/list), [GET /v1/contacts/:id](/docs/entities/contacts/get), [POST /v1/contacts/search](/docs/entities/contacts/search), [POST /v1/contacts/aggregate](/docs/entities/contacts/aggregate), [POST /v1/contacts/batch](/docs/batch), [GET /v1/contacts/fields](/docs/entities/contacts/fields).

### FIX-0907-9: составная загрузка заменяет крупный объект под прежним ключом

**Было**

[POST /v1/storage/objects/multipart/create](/docs/storage/upload/multipart-create) возвращал `409 STORAGE_KEY_EXISTS`, если живой объект приложения уже занимал тот же `key`. Поэтому содержимое размером больше 10 МБ нельзя было заменить без смены ключа. Для личного ключа совет использовать multipart вёл к тому же отказу.

**Стало**

Для уже загруженного объекта приложения запрос возвращает `200` и открывает сессию замены с прежним `objectId`. Пока [POST /v1/storage/objects/multipart/complete](/docs/storage/upload/multipart-complete) не опубликовал новые байты, чтение отдаёт старое содержимое; успешный ответ подтверждает новую версию. Подтверждённый [POST /v1/storage/objects/multipart/abort](/docs/storage/upload/multipart-abort) до публикации сохраняет старую версию. Если complete вернул `502 STORAGE_BUCKET_ERROR`, публикация могла состояться: по этому ответу нельзя определить текущую версию, сессия остаётся активной и блокирует удаление объекта до разрешения администратором платформы. Видимость объекта не меняется.

[POST /v1/storage/objects/multipart/create](/docs/storage/upload/multipart-create) для уже существующего живого объекта личного ключа возвращает `409 STORAGE_KEY_EXISTS` и не открывает сессию загрузки. Сначала удалите объект, затем снова начните составную загрузку под тем же `key`. Это неатомарная операция: между удалением и успешным завершением новой загрузки объект недоступен.

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

Файлы приложения больше 10 МБ можно обновлять составной загрузкой под прежним `key`. Операции multipart create, complete и abort могут вернуть повторяемый `409 STORAGE_KEY_CONFLICT`, когда другой запрос одновременно изменяет тот же `key` или состояние сессии изменилось. Для create и complete повторяйте ту же операцию. `409` на abort во время финализации требует повторить complete с теми же `parts`; если complete вернул `502 STORAGE_BUCKET_ERROR`, не делайте вывод о текущей версии и обратитесь к администратору платформы. Если abort вернул `502 STORAGE_BUCKET_ERROR`, повторяйте abort, пока освобождение сессии не будет подтверждено. [DELETE /v1/storage/objects/{key}](/docs/storage/objects/delete) во время активной multipart-сессии возвращает `409 STORAGE_MULTIPART_IN_PROGRESS`: сначала завершите или отмените сессию, учитывая особый случай финализации выше.

### FIX-0907-10: перевод сделки/лида на несуществующую стадию теперь отвечает ошибкой, а не тихим успехом

**Было**

`POST /v1/deals/{id}/move`, `POST /v1/leads/{id}/move`, `PATCH /v1/deals/{id}` и `PATCH /v1/leads/{id}` отвечали `200 success:true` и возвращали полный объект сделки/лида даже тогда, когда переданная `stageId` (или `categoryId` у сделки) не существовала в справочнике — Битрикс24 тихо игнорировал значение, стадия оставалась прежней, а вызывающий не мог отличить реальный перевод от отклонённого без ручной сверки поля `data.stageId`.

**Стало**

Ответ по-прежнему `200`, если переданная стадия или воронка существует и применяется — поведение для валидных значений не изменилось. Когда переданное значение не применилось (Битрикс24 принял вызов, но фактическая стадия осталась прежней), ответ — `422 STAGE_NOT_APPLIED` с деталями в `error.message`: какое значение запросили и какое оказалось на самом деле после перечитывания записи.

### FIX-0907-11: Подсказка привязки встройки говорит «trial», а не «demo»

**Было**

[GET /v1/me](/docs/keys-auth/me) отдавал в подсказке возможности `apps.bindPlacements` старый термин:
«…403 B24_MARKET_SUBSCRIPTION_REQUIRED (or B24_MARKET_TRIAL_USED if the demo was
already used)». Запись FIX-0904-7 уже перевела латинскую терминологию на слово trial,
поэтому эта строка осталась последней, расходившейся с кодом отказа, который она сама
и описывает. Тот же термин держался в двух статьях документации — про запуск
пробного периода Маркетплейса и про имя тарифа в `GET /v1/me`.

**Стало**

Подсказка читается как «…if the trial was already used», документация переведена на тот
же термин. Коды отказов, статусы и поля ответа не изменились, успешный ответ остался
успешным. Строка не локализована и отдаётся одинаково на обеих установках.

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

На логику — никакого. Клиенту, который сравнивает эту подсказку по подстроке, стоит
перепроверить сравнение.

### FIX-0907-12: спека объявляет все ответы переключения режима сервера

**Было**

Публичная спека `GET /v1/openapi.json` объявляла у [PATCH /v1/infra/servers/:id/mode](/docs/infra/access/mode) только ответы `200`, `400`, `403` и `404`. Клиент, собранный по спеке, считал остальные исходы невозможными, хотя ручка их отдавала: отказ по исчерпанному балансу, отказ роли сервера, конфликт параллельного запроса и два отказа применения сетевой политики.

**Стало**

Спека дополнена ответами, которые ручка отдаёт: `401`, `402` `ACCOUNT_FROZEN` и `OPEN_MODE_REQUIRES_COMMERCIAL`, `409` `SERVER_NOT_RUNNING`, `AGENT_NOT_CONNECTED`, `MODE_SWITCH_SEALED_ROLE` и `CONFLICT`, `502` `IPTABLES_FAILED`, `PROVIDER_NOT_CONFIGURED`, `GATEWAY_UNREACHABLE` и `TUNNEL_NOT_FOUND`, `503` `SECURITY_GROUP_ATTACH_FAILED` и отказ, код которого начинается с `GATEWAY_TIMEOUT`. Описание `400` дополнено кодами `SAME_MODE`, `NO_SUBDOMAIN` и `MODE_SWITCH_STANDALONE_ONLY`. Те же коды заведены в таблицу ошибок страницы: до этой правки она не называла ни одного из трёх отказов состояния — сервер не запущен, агент не на связи, у сервера нет субдомена. Из таблицы ошибок на странице ручки снят код `PROVIDER_ERROR`: переключение режима его не отдаёт. Статус `429` объявления не получил намеренно — своего лимитера у ручки нет, а общий пограничный лимит платформы описан на странице [лимитов](/docs/errors/limits) и в спеке по маршрутам не объявляется. Поведение ручки не изменилось: успешный ответ остаётся HTTP 200 с тем же телом, менять запросы не нужно, и правка касается только объявления и документации.

### NEW-0907-13: `/v1/cowork/me` и `/v1/cowork/state` называют портал, к которому привязан ключ

Обе ручки самоописания Коворк/Код получили новое поле `portal`:

```json
{
  "portal": { "id": "8f3c…", "domain": "acme.bitrix24.ru" },
  "tier": "PRO",
  "state": "ACTIVE"
}
```

Поле отвечает на вопрос, о каком портале говорят остальные поля тела. Сиденье
Коворк/Код определено на паре «владелец ключа + портал», а ключ привязан к
одному порталу навсегда, поэтому у человека с несколькими порталами приложение
докладывает про тариф и квоту одного портала, а браузер — другого. Раньше домен
приезжал ровно один раз, при обмене кода на ключ (`POST /v1/connect/token`), и
переспросить его было негде.

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

### BC-0907-14: частичный отказ конвертации лида отвечает 422, а не 200

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

**Было**

[POST /v1/leads/:id/convert](/docs/entities/leads/convert) создаёт сделку, контакт и компанию отдельными записями одна за другой. Когда часть из них не создавалась, ответ всё равно приходил со статусом `200`: поле `success` было `false`, а `error`, `code` и `details` лежали на верхнем уровне тела, причём `error` был строкой, а не объектом. Клиент, который ветвится по HTTP-статусу или читает `error.code`, принимал частично выполненную запись в CRM за успех.

**Стало**

Частичный отказ отвечает `422` с кодом `CONVERSION_FAILED` в обычном конверте ошибок: `error` — объект с полями `code` и `message`. Исход каждой запрошенной операции переехал в `error.details` — по ключу на сделку, контакт и компанию, с признаком `success` и либо идентификатором созданной записи, либо текстом отказа Битрикс24. Верхнеуровневых `error`-строки и `code` в ответе больше нет. Лид, который не удалось прочитать, отвечает `404` с кодом `ENTITY_NOT_FOUND` в том же конверте. Успешная конвертация по-прежнему отвечает `200` с полем `data`.

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

Ветвиться по HTTP-статусу и по `error.code`, а не по полю `success` и не по верхнеуровневому `code`; исходы отдельных записей читать из `error.details`, а не из `details`. Уже созданные записи по-прежнему не откатываются, а статус лида не меняется, поэтому перед повторным вызовом сверяйте, что уже появилось в CRM: повтор создаёт второй комплект записей. Окна поддержки прежнего ответа со статусом `200` нет: он сообщал об успехе там, где часть записей в CRM не создалась, поэтому сохранять его означало бы и дальше выдавать отказ за успех.

### FIX-0907-15: Проверка корневого типа тела на создании, обновлении, batch и поиске сущностей

**Было**

Тело запроса — JSON-литерал `null` — роняло `POST /v1/{entity}`, `PATCH /v1/{entity}/{id}` и `POST /v1/{entity}/batch` в `500 INTERNAL_ERROR`; этот статус не был объявлен ни для одного из трёх маршрутов. JSON-массив или JSON-строка в теле `POST /v1/{entity}` создавали настоящую запись с пустыми или автосгенерированными полями и отвечали `201`, как будто запрос был корректным. На `PATCH /v1/{entity}/{id}` JSON-строка проходила проверку пустого тела и отвечала `200`, фактически ничего не изменив. На `POST /v1/{entity}/search` тело-массив отвечало `400 INVALID_FILTER_SHAPE` с сообщением, неверно называющим присланный тип данных («получена function» вместо «массив»); тело-скаляр (число, строка, boolean) проходило как «фильтр не указан» и отвечало `200` с неотфильтрованным списком записей.

**Стало**

Корректное тело (JSON-объект; для `/search` — включая пустой `{}`, это законный запрос «без фильтра») обрабатывается без изменений. Ответ по-прежнему `201` при создании и по-прежнему `200` при обновлении, batch и поиске — ничего из этого не поменялось. Тело, чей корневой JSON-тип не является объектом (`null`, массив, строка, число, boolean), теперь на любом из четырёх маршрутов отвечает `400`, а сама сущность не создаётся и не изменяется: `EMPTY_CREATE_BODY` на создании, `EMPTY_UPDATE_BODY` на обновлении, `INVALID_BATCH_ACTION` на пакетном маршруте сущности, `INVALID_REQUEST` на поиске. Сообщение об ошибке поиска теперь называет фактически присланный тип данных, а не внутренний артефакт реализации.

### FIX-0907-16: отсутствующая запись отвечает 404 на складах, каталогах, статусах заказа, позициях корзины и смарт-процессах

**Было**

Один и тот же сценарий — «записи с таким `id` нет» — отвечал в пределах одного раздела разными
кодами. `GET /v1/order-statuses/{id}` и `GET /v1/basket-items/{id}` отдавали `422 BITRIX_ERROR`,
хотя соседние заказы, счета, платежи и предложения на том же шаге отдавали `404`. Так же вели себя
`GET /v1/catalogs/{id}`, `GET /v1/warehouses/{id}` и `GET` карточек свойств товара, при том что
товары и цены каталога уже отвечали `404`. Проверка «удалилось ли» по коду ответа на этих путях
молча не срабатывала.

Отдельно на портале с нерусским языком интерфейса `PATCH` и `DELETE` для
`/v1/smart-processes/{id}` на несуществующий `entityTypeId` отвечали `400 INTERNAL_ERROR` — код,
который для `DELETE` даже не объявлен в спецификации. На русском портале то же действие честно
отвечало `404 SMART_PROCESS_NOT_FOUND`, а `GET` был верен на любом языке.

**Стало**

Отсутствующая запись отвечает `404` на всех перечисленных путях. `GET`, `PATCH` и `DELETE`
для складов, каталогов, статусов заказа и позиций корзины отдают `404 ENTITY_NOT_FOUND`; текст
портала в поле `message` не меняется, меняется только классификация ответа. Тот же `404` приходит
и на страницах свойств товара и их значений.

`PATCH`, `DELETE` и `POST /v1/smart-processes/batch` отвечают `404 SMART_PROCESS_NOT_FOUND`
независимо от языка портала — так же, как это давно делал `GET`. Клиент может опираться на один
код существования записи во всём API Вайбкод, а не заводить исключения по разделам и по языку.
