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

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

### BC-0909-1: Расшифровка аудио сохраняет тарификацию по подписке при отключении Cowork/Code

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

**Было**

При отключённом Cowork/Code запросы к [POST /v1/audio/transcriptions](/docs/ai/audio/transcriptions) уже выданным ключом `vibe:cowork` обходили квоту подписки, даже если подписка была активна и для доступной модели была назначена цена аудио. Исчерпание квоты подписки не препятствовало успешному ответу HTTP 200.

**Стало**

Отключение продукта не меняет тарификацию: при активной подписке и назначенной цене аудио за минуту или за вызов расход учитывается в её квоте, как у чата. При исчерпании окна запрос получает HTTP 402 `cowork_quota_exhausted` с заголовком `Retry-After`. Без активной подписки сохраняется прежний путь тарификации без списания квоты подписки.

**Что изменить в интеграции**

Обрабатывайте HTTP 402 `cowork_quota_exhausted` и при отключённом продукте: дождитесь сброса окна по `Retry-After` или увеличьте тариф подписки. Ключ и формат запроса менять не требуется.

### NEW-0909-2: Идентификаторы методов каталога 1С поддерживают Unicode

`GET /v1/onec/tools` и `POST /v1/onec/tools/{method}/call` поддерживают идентификаторы методов из букв, комбинируемых знаков и чисел Unicode, а также `._-`. Предел — 100 кодовых единиц UTF-16. Передавайте в URL точный идентификатор из каталога через `encodeURIComponent(method)`: регистр и Unicode-представление сохраняются. Существующие латинские идентификаторы продолжают работать.

### BC-0909-3: Универсальные списки: отсутствующий список отвечает «не найдено», а неверное смещение — ошибкой

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

**Было**

Один и тот же несуществующий список отвечал по-разному в зависимости от того, какой адрес
дёрнуть: перечень элементов, перечень полей, набор типов полей, отдельный элемент, отдельное
поле и файлы элемента отвечали `422` с текстом от Битрикс24 — то есть ошибкой «что-то пошло не
так», а не «такого списка нет». Логика «прочитать, а если нет — создать» на этом ломалась.

Постраничный обход тех же перечней принимал любое значение смещения. `start=-5`, `start=abc`,
`start=1.5` и `start=1e2` не отклонялись: смещение молча не применялось либо применялось
искажённым (`1e2` превращалось в `1`, и вместо сотой записи приходила вторая). Ответ при этом
был `200`, поэтому ошибка в вызывающем коде оставалась невидимой — при том что соседние
параметры того же запроса, `iblockTypeId` и `sort`, отклоняются с внятным сообщением.

**Стало**

Отсутствующий список отвечает `404` с кодом `LIST_NOT_FOUND` на шести адресах, где Битрикс24
сообщает об этом машинным кодом. Смещение принимается только как неотрицательное целое в
обычной записи; всё прочее отклоняется `400` с кодом `INVALID_PARAMS`, и в сообщении назван
именно тот параметр, который не подошёл. Проверяются оба параметра, `start` и `offset`, даже
когда спор о старшинстве между ними выигрывает первый.

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

Если ваш код ловил `422` от перечней списков как «списка нет» — переведите его на `404`.
Распознаётся машинный код Битрикс24, а не текст сообщения, поэтому ответ не зависит от языка
портала. Сам код замерен на международной платформе: если ваш портал сообщает об отсутствии
списка иначе, распознавание не сработает и ответ останется прежним.
Если смещение собиралось из внешнего источника и могло прийти отрицательным или дробным,
теперь вместо страницы придёт ошибка: почините источник, а не обход. Отдельно назовём один
вход, который прежде понимался ВЕРНО: `?start=+5` — плюс в строке запроса декодируется
пробелом, и прежний разбор читал такое как 5. Теперь это отказ. Если ваш сборщик кодирует
плюсом, уберите его: `?start=5`.

Отдельно про запись параметров квадратными скобками. `?start[]=7` и `?iblockTypeId[]=lists`
раньше проходили: платформа склеивала такой список в одно значение и работала дальше. Теперь
это отказ — скобки означают структуру, а перечисленные параметры структуру не принимают.
Если ваш сборщик запроса ставит скобки автоматически на всякий список, передавайте эти три
параметра простым значением: `?start=7`, `?offset=7`, `?iblockTypeId=lists`. Скобочные формы с именем
внутри (`?start[x]=1`) прежде роняли запрос внутренней ошибкой — теперь на них приходит
понятный отказ.

Ещё один параметр в том же ряду — набор возвращаемых полей: `?select[x]=1` прежде роняло
запрос внутренней ошибкой, теперь набор просто не применяется и приходит полный состав полей;
пустые элементы вида `?select[]=&select[]=NAME` больше не уезжают в портал пустой строкой.

То же касается тела запроса при создании раздела и элемента: `"iblockSectionId": [5]`
раньше принималось за пятёрку и клало запись под этого родителя, теперь значение
отбрасывается и запись попадает в корень. Присылайте число или строку с числом.

Тип инфоблока в теле сузился так же, только отвечает иначе: `"iblockTypeId": ["lists"]`
раньше проходил (список склеивался в одно значение), теперь приходит `400` с кодом
`INVALID_IBLOCK_TYPE` — на восьми адресах, где этот параметр вообще читается из тела:
создание и изменение списка, поля, раздела и элемента. Удаления его не принимают и не
затронуты. Присылайте строку: `"iblockTypeId": "lists"`.

**Чего правка НЕ меняет**

Чтение разделов по-прежнему отвечает жалобой на неверный тип инфоблока, а чтение самого
списка — отказом в правах, если номер списка числовой. По символьному номеру этот же адрес
отвечал «не найдено» и до правки: там Битрикс24 возвращает пустой результат, а не отказ.
Битрикс24 сообщает об отсутствии списка на этих адресах теми же словами, что и о настоящей
нехватке прав и о настоящем неверном типе, поэтому распознавать их как «не найдено» означало
бы прятать реальные отказы.

### BC-0909-4: фантомное поле contacts удалено у компаний, лидов и сделок

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

**Было**

Схема полей и ответы компаний, лидов и сделок могли содержать `contacts`, хотя это поле нельзя было надёжно прочитать из Битрикс24. Явный `select: ["contacts"]` принимался и отвечал `200`.

**Стало**

`contacts` не публикуется в схеме полей и не возвращается в записях. Явный `select: ["contacts"]` без `*` или `UF_*` отклоняется с `400 UNKNOWN_SELECT_FIELD`. Запрос всех полей через `*` или `UF_*` по-прежнему выполняется, но `contacts` из ответа удаляется.

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

Удалите `contacts` из явных списков `select`. Для связей с контактами у компаний используйте `contactIds`, а у лидов и сделок — `contactId` и `contactIds`.

**Затронутые эндпоинты:** [GET /v1/companies/fields](/docs/entities/companies/fields), [GET /v1/leads/fields](/docs/entities/leads/fields), [GET /v1/deals/fields](/docs/entities/deals/fields), операции чтения и записи из разделов [компаний](/docs/entities/companies), [лидов](/docs/entities/leads) и [сделок](/docs/entities/deals), [POST /v1/batch](/docs/batch), `POST /v1/{entity}/batch`.

### FIX-0909-5: на коробочном портале выписывается ключ, которому вебхук не нужен

**Было**

На коробочном портале у владельца без ключа разработчика выдача и перевыпуск ключа с правами
только `vibe:*` (например `vibe:infra` + `vibe:storage`) отвечали `400 BOX_NO_DEVELOPER_KEY`,
хотя входящий вебхук такому ключу на портале не регистрируется вовсе. Затрагивало
`POST /v1/keys`, `POST /v1/keys/{id}/rotate` и выдачу личного ключа приложения Коворка.

**Стало**

Набор прав без единого права Битрикс24 проверяется раньше коробочного гейта: ключ выдаётся,
поле `webhookUrl` остаётся пустым, к порталу обращений нет. Отказ `400 BOX_NO_DEVELOPER_KEY`
сохраняется для наборов, где хотя бы одно право Битрикс24 есть, — такому ключу вебхук нужен,
а снять его без ключа разработчика владельца нечем. Перевыпуск требует ещё одного условия:
у прежней строки не должно остаться вебхука. Ключ без прав Битрикс24, за которым вебхук всё же
числится, отвечает прежним `400` — снять этот вебхук без ключа разработчика нечем, и успешный
ответ скрыл бы, что прежний доступ продолжает работать.

### FIX-0909-6: замена ключа приложения больше не выдаёт секрет с истёкшим сроком

`POST /v1/cowork/applications/{id}/key` переносил срок жизни заменяемого ключа на новый как есть.
Если срок уже прошёл, владелец получал секрет, не проходящий ни одного запроса, а прежний
протухший ключ получал сутки жизни и на эти сутки снова начинал работать.

**Было**

Приложение с ключом, срок которого истёк три дня назад. Ответ 201, `issued: "rotated"`,
`key.expiresAt` — та же прошедшая дата, `previousKey.graceUntil` — сутки вперёд от момента замены.

**Стало**

Ответ по-прежнему 201, `issued: "rotated"`. `key.expiresAt` отсчитывается заново по сроку,
принятому на портале (его же отдаёт `GET /v1/cowork/applications/defaults` полем
`keyExpiresInDays`), — новый ключ работает. `previousKey.graceUntil` не превышает собственного
срока заменяемого ключа: у протухшего он остаётся в прошлом, и суточная отсрочка его не
воскрешает. Действующий срок и его отсутствие переносятся как раньше — бессрочный ключ остаётся
бессрочным, живой срок повторяется один в один.

Та же поправка к отсрочке действует у `POST /v1/keys/{id}/rotate`: перевыпуск ключа с истёкшим
сроком больше не даёт прежнему ключу сутки работы.

### FIX-0909-7: публичный числовой адрес в baseUrl и proxyUrl больше не отбивается как частный

**Было**

`POST /v1/ai/credentials` и `PATCH /v1/ai/credentials/{id}` с публичным числовым адресом в
`baseUrl` или `proxyUrl` — например `http://203.0.113.10:8080` — отвечали `400` с кодом
`BASE_URL_PRIVATE` или `PROXY_URL_PRIVATE`. Сохранить такой ключ удавалось, только записав тот же
адрес доменным именем.

**Стало**

Такой запрос отвечает успешно — как и с доменным именем. Частный числовой адрес по-прежнему
получает `400` с теми же кодами: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`,
`100.64.0.0/10`, `127.0.0.0/8`, `169.254.0.0/16`, `0.0.0.0/8`, а для IPv6 — `::1`, `::ffff:`,
`fc00::/7` и `fe80::/10`. **Только для числовых адресов** к этому перечню добавились
зарезервированные диапазоны, которыми числовой адрес раньше пройти не мог в принципе:
`192.0.0.0/24`, `198.18.0.0/15`, `224.0.0.0/4`, `240.0.0.0/4` (вместе с `255.255.255.255`), а
для IPv6 — `fec0::/10`, `ff00::/8` и `100::/64`. Для доменных имён перечень запрещённых
диапазонов не менялся: имя, ведущее в любой из этих зарезервированных диапазонов, работает как
работало, и запрос с доменным именем отвечает успешно, как прежде. Исключение — адреса,
встраивающие IPv4 в IPv6-форму (`::a.b.c.d`, NAT64 `64:ff9b::/96`, 6to4 `2002::/16`): они
отбиваются одинаково, и когда записаны числами, и когда имя ведёт на такой адрес.

### FIX-0909-8: спека объявляет отказ 429 у ручек с собственным лимитом частоты

**Было**

Двадцать шесть операций несут собственный лимит частоты и отвечают `429 RATE_LIMITED`, когда он исчерпан, а публичная спека `GET /v1/openapi.json` этот ответ у них не объявляла. Клиент, собранный по спеке, считал `429` неописанным статусом и не строил ветку повтора запроса вовсе. Затронуты `POST /v1/batch`, `GET /v1/bots/:botId/events`, `GET /v1/chats/recent`, `POST /v1/chats/events/subscribe`, `GET /v1/connect/authorize`, `POST /v1/connect/token`, `POST /v1/connect/revoke`, `GET /v1/app/blueprints/:slug`, `GET /v1/me/sources`, `POST /v1/feedback/attachments`, `GET /v1/workday/records` и пятнадцать ручек инфраструктуры — `POST /v1/infra/servers`, `GET /v1/infra/servers/:id/ssh`, `POST /v1/infra/servers/:id/reboot`, `PATCH /v1/infra/servers/:id/sleep`, `PATCH /v1/infra/servers/:id/port`, `POST /v1/infra/servers/:id/wake-schedules`, `PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId`, `DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId`, `POST /v1/infra/servers/:id/sources`, `POST /v1/infra/servers/:id/exec`, `POST /v1/infra/servers/:id/upload`, `POST /v1/infra/servers/:id/icon`, `GET /v1/infra/servers/:id/logs`, `POST /v1/infra/servers/:id/deploy` и `POST /v1/infra/servers/:id/unstick`.

**Стало**

У каждой из этих операций отказ `429` объявлен в спеке. Описание отправляет за действующим значением в заголовок ответа `x-ratelimit-limit`, а за сроком повтора — в `Retry-After`. Число в спеку не вписано намеренно: заголовок — единственный источник, который не расходится с настройкой. У `POST /v1/infra/servers`, `POST /v1/infra/servers/:id/upload` и `POST /v1/infra/servers/:id/deploy` на этом же статусе назван второй код `DEPLOY_BACKEND_BUSY` — он приходит, когда заняты все места под встроенное тело запроса, отказывает конкретному телу, а не вызывающему, и несёт `Retry-After: 30`. Общие сведения о лимитах остаются на странице [лимитов](/docs/errors/limits).

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

Лимиты и поведение ручек не менялись, менять запросы не нужно. Клиенту, сгенерированному из спеки до этой правки, стоит пересобрать его — тогда обработка `429` появится там, где раньше отказ приходил как неописанный.

### FIX-0909-9: истёкший пользовательский токен OAuth возвращает ошибку авторизации

**Было**

Если приложение не могло обновить истёкший пользовательский токен, отказ Битрикс24 `expired_token` возвращался из V1 API как `422 BITRIX_ERROR`, то есть как ошибка данных запроса.

**Стало**

Одиночный вызов V1 API возвращает `401 TOKEN_EXPIRED` с подсказкой повторно открыть приложение из меню Битрикс24. В пакетном вызове код `TOKEN_EXPIRED` приходит в ошибке отдельного вызова, а общий HTTP-ответ остаётся `200`. Подробнее: [ошибки API](/docs/errors).

### FIX-0909-10: отказ по частоте на выдаче токена стал машиночитаемым

**Было**

При срабатывании ограничителя частоты на входе в платформу `POST /v1/connect/token` отдавал HTML-страницу и не ставил заголовок `Retry-After`. Приложение не могло ни разобрать тело, ни узнать, через сколько повторять. Для входа по коду устройства это единственный отказ по частоте, до которого реально доходит живой опрос.

**Стало**

Тот же отказ приходит с телом в форме RFC 6749 — `{"error":"slow_down","error_description":"..."}` — и с заголовком `Retry-After`, который называет минимальную паузу в секундах. Форма совпадает с той, что эндпоинт уже объявляет для остальных отказов, поэтому готовая OAuth-библиотека разбирает её без доработок. Статус ответа остаётся `429`, поэтому отказ по частоте по-прежнему отличим от состояний авторизации, которые приходят с кодом `400`. Прежние вызовы работают без изменений.

**Важно:** ограничителей частоты на этом маршруте два, и тела у них разные. В форме RFC 6749, описанной выше, отвечает только ограничитель на входе в платформу. Ограничитель самого эндпоинта отвечает тем же статусом `429`, но в общем конверте API — `{ "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } }`, — поэтому ветка вида «на `429` разобрать тело и сравнить `error` со `slow_down`» распознает лишь половину отказов, а вторую примет за неизвестную ошибку. Различить ярусы можно по заголовку `X-RateLimit-Limit`: отказ на входе в платформу его не несёт, отказ самого эндпоинта несёт. Значение `Retry-After` — минимальная пауза, а не обещание, что следующий запрос примут, поэтому приложению нужно наращивать и собственную паузу. Оба отказа и оба тела описаны в разделе [Partner Connect](/docs/partner-connect).

### FIX-0909-11: reasoning модели для Cowork больше не попадает в финальный текст

**Было**

Когда Cowork не передавал настройку reasoning явно, модель могла вернуть внутренний рабочий текст
как обычную часть финального ответа.

**Стало**

[POST /v1/chat/completions](/docs/ai/chat/completions) для ключей с областью `vibe:cowork`
применяет объявленную моделью настройку reasoning по умолчанию. Рассуждение остаётся в отдельном
канале, а самостоятельный финальный текст сохраняется. Ответ остаётся HTTP 200.

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

Изменения клиентов не требуются. Вызовы без области `vibe:cowork` сохраняют прежнее поведение.

### BC-0909-12: коробочная покупка идёт на кассу своей страны

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

**Было**

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

**Стало**

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

`GET /v1/cowork/subscription/preview` для такой лицензии отдаёт валюту своей страны вместо `RUB`.

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

Читать валюту из ответа, а не подразумевать рубль: `currency` у пакетов и в корне ответа теперь может быть `BYN`, `KZT` или `UZS`, а пока пополнение для страны недоступно — `null` при `topUpAvailable: false`. Суммы приходят в минорных единицах своей валюты — пересчёт в рубли на стороне интегратора даст неверные числа.
