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

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

### FIX-0908-1: склады не подставляют чужой номер и открыто отказывают в неподдерживаемом фильтре

**Было**

Номер склада в адресе запроса читался нестрого: `GET /v1/warehouses/12.5` не отклонялся, а тихо превращался в `12`, и чтение, изменение или удаление уходило в другой, реально существующий склад под видом успеха. Так же вели себя `12abc`, `1e2`, `007`, номер с пробелом и номер за пределом точности целых чисел; то же нестрогое чтение применялось к `productId` и `limit`. Параметр `filter[]` на списке складов, на остатках склада и на сводке остатков не читался вовсе: любое условие — и выдуманное, и настоящее имя поля — молча игнорировалось, а в ответ приходил полный список. В описании API семь операций складов и операция агрегации объявляли единственный вариант ответа — успех, а удаление склада было объявлено ответом 200, хотя отвечало пустым 204.

**Стало**

Номер склада, `productId` и `limit` принимаются только в канонической форме — одни цифры, без знака, ведущего нуля, дроби и экспоненты, в пределах точности целых чисел. Всё прочее отклоняется кодом 400 `INVALID_PARAMS` до обращения к Битрикс24, поэтому опечатка в номере больше не может попасть в чужой объект. Переданный `filter[]` на трёх перечисленных ручках отклоняется кодом 400 `UNSUPPORTED_FILTER` с перечнем полученных ключей — вместо полного списка, выглядевшего как отфильтрованный. Запросы с каноническим номером и без `filter[]` работают как прежде, и их успешный ответ 200 не изменился. В описании API у операций складов и у агрегации появились реальные коды отказа (400, 401, 403, 404, 422), а удаление склада объявлено как 204 — тем ответом, который оно и отдавало.

### BC-0908-2: Значение не того типа в поле записи отклоняется вместо тихой порчи данных

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

**Было**

Поле, объявленное в схеме сущности числом, принимало любую строку: `{"amount":"сто тысяч"}`
на [POST /v1/deals](/docs/entities/deals/create) отвечал `201`, а сумма сохранялась нулевой —
Битрикс24 приводит нечисловую строку к `0`. На обновлении это стирало уже записанную сумму:
`PATCH` с непарсящимся значением отвечал `200` и обнулял поле. Так же вели себя все прочие
числовые поля реестра (`sort` у справочников и товаров), булевы поля принимали любое слово, а
строковое поле `color` у статусов заказа молча обрезалось до ширины колонки базы.

**Стало**

Такое значение отклоняется `400` с кодом `INVALID_PARAMS` до обращения к Битрикс24: числовое
поле принимает JSON-число или числовую строку с точкой как десятичным разделителем; булево —
`true`/`false` и распознаваемые строковые формы (`"yes"`/`"no"`, `"y"`/`"n"`, `"1"`/`"0"`,
`"true"`/`"false"`); строковое поле с объявленным ограничением длины — значение в пределах этой
длины. Поля `priority` и `status` у задач принимают только значения из своего перечня.

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

Присылайте денежные суммы числом или строкой с точкой: `1234.56`, не `"1234,56"` — запятая как
десятичный разделитель отклоняется явной ошибкой, а не переводится в точку молча. Проверьте
места, где значения собираются из внешних систем строками: раньше такой запрос отвечал успехом,
а данные терялись, теперь он честно вернёт ошибку с указанием поля.

### FIX-0908-3: Постраничное чтение последних диалогов не теряет и не дублирует записи

**Было**

[GET /v1/chats/recent](/docs/chats/discovery/recent) передавал размер страницы и смещение в
Битрикс24 как есть, а там они ограничивают внутреннее соединение таблиц, а не число диалогов.
Из-за этого страница отдавала меньше записей, чем запрошено, при обещании следующей страницы;
последняя запись страницы приходила с пустыми полями чата и последнего сообщения; соседние окна
перекрывались, так что обход `смещение += размер` выдавал одни диалоги дважды, а другие терял.

**Стало**

Страница читается с запасом и режется на стороне Вайбкод: окно содержит ровно запрошенное число
полностью заполненных записей, недозаполненные строки не отдаются, а признак наличия следующей
страницы считается от фактически отданного окна. Окно за концом списка приходит пустым и следующей
страницы больше не обещает. Запрос без указания размера страницы отдаёт 50 записей — раньше размер
выбирал Битрикс24, теперь он назван явно.

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

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

Ничего: запросы прежние, ответ стал честным. Если ваш код обходил дубли вручную — эта защита
больше не нужна.

### BC-0908-4: Поля, которые Битрикс24 назначает сам, объявлены только для чтения

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

**Было**

Пять полей были объявлены доступными для записи, принимались с ответом `200`/`201`, но
Битрикс24 их не сохранял — значение оставалось пустым, и отличить это от успешной записи по
ответу было нельзя: признаки происхождения у воронок продаж, модуль-владелец у
[шаблонов документов](/docs/entities/doc-templates/update), а также пометка проблемной оплаты
и её причина у [платежей](/docs/entities/payments/update) при изменении.

**Стало**

Эти поля объявлены назначаемыми сервером: попытка записи отклоняется `400` с кодом
`READONLY_FIELD` до обращения к Битрикс24, а описание поля прямо говорит, что значение
проставляет платформа. Пометка проблемной оплаты и её причина по-прежнему принимаются при
создании платежа — закрыто только их изменение, где значение и терялось. Признаки
происхождения воронки раньше вовсе не были объявлены в схеме и подхватывались как
записываемые; теперь они объявлены и отклоняются.

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

Уберите эти поля из тела запроса на изменение. Модуль шаблона платформа проставляет сама,
признаки происхождения воронки не сохраняются вовсе, а пометку проблемной оплаты задавайте
при создании платежа.

### FIX-0908-5: поиск и сводные отчёты отвечают ошибкой на параметр неверного типа, а не тихой пустотой

**Было**

В теле `POST /v1/{entity}/search` поля `limit` и `select` принимали значение любого типа. Нечисловой `limit` (например, `"abc"`) давал `HTTP 200` с пустым списком записей и одновременно `meta.hasMore: true` — клиент, который листает страницы, пока платформа обещает продолжение, уходил в бесконечный цикл, не получая ни одной записи и ни одной ошибки. Значение `select`, не являющееся строкой или перечнем строк, превращалось в имя поля вида `"999"` или `"[object Object]"`, уходило в Битрикс24 и возвращалось как `HTTP 502` с кодом `BITRIX_UNAVAILABLE` — платформа сообщала о собственной недоступности там, где ошибка была в запросе клиента.

В теле `POST /v1/{entity}/aggregate` поле `groupBy` проверялось, только если пришло строкой или перечнем строк. Число, логическое значение или объект молча отбрасывались: ответ приходил с `HTTP 200`, без ключа `groups` и без предупреждения — клиент просил разрез отчёта, а получал общий итог и не мог этого заметить.

В `meta.total` списочных ответов на страницах за пределами коллекции попадало выдуманное число, растущее вместе со смещением: на портале с пятью хранилищами `GET /v1/storages?offset=100` отвечал `total: 100`, а `GET /v1/storages?offset=1000` — `total: 1000`.

**Стало**

`limit` неверного типа отклоняется кодом `INVALID_LIMIT`, `select` неверного типа — кодом `INVALID_SELECT_TYPE`, `groupBy` неверного типа — кодом `INVALID_PARAMS`; все три отказа приходят как `HTTP 400` с указанием, какого типа значение пришло. Числовой `limit` работает как раньше, включая строковую запись числа (`"50"`), а `null`, пустая строка и пустой перечень по-прежнему означают «параметр не задан» и ошибкой не считаются.

Ключ `meta.total` не выдаётся, когда страница пуста, а смещение больше нуля: подтвердить счёт в этом случае нечем, а описание поля и без того предупреждает, что на ненулевом смещении ключа может не быть. Там, где страница непуста или смещение нулевое, `meta.total` приходит как прежде, и ответ по-прежнему HTTP 200.

### FIX-0908-6: избранное, комментарии и записи времени задачи отвечают честно

**Было**

Добавление задачи в избранное и удаление из него отвечали `200` и `success: true` даже для задачи, которой нет: Битрикс24 подтверждает это действие для любого идентификатора, ничего при этом не сохраняя. Обещанный в описании API код `404 TASK_NOT_FOUND` не приходил никогда.

Список комментариев задачи на запросе без фильтра и с сортировкой по идентификатору не учитывал `offset` — одна и та же страница приходила при любом значении, — а `meta` считалась по сырым сообщениям чата. На задаче, у которой в срезе только системные уведомления, ответ был `data: []` вместе с `total: 1` и `hasMore: true`, поэтому обход `while (hasMore) offset += limit` не завершался.

Обращение к несуществующему пункту чек-листа или записи учёта времени давало `422 BITRIX_ERROR` с текстом внутреннего исключения Битрикс24 (`TASKS_ERROR_EXCEPTION_#512; …; 512/TE/ITEM_NOT_FOUND_OR_NOT_ACCESSIBLE`), причём на маршруте учёта времени этот текст говорил о чек-листе.

Параметры `from` и `to` у `GET /v1/task-time` не проверялись: `?from=notadate` молча отдавал весь диапазон со статусом `200`, как будто период не запрашивали.

**Стало**

Перед добавлением в избранное и удалением из него сервис проверяет, что задача существует и доступна ключу, и отвечает `404 TASK_NOT_FOUND`, если нет. На существующей задаче ответ по-прежнему `200`. Ограничение скорости и отказ авторизации задачей «не найдено» не подменяются.

Список комментариев учитывает `offset` на всех путях чтения, а `meta.hasMore` и `meta.total` считаются по комментариям, а не по сырым сообщениям чата: срез из одних системных уведомлений дочитывается дальше, и `hasMore: false` с `total: 0` означает, что комментариев нет.

Отсутствующий пункт чек-листа и отсутствующая запись времени отвечают `404 NOT_FOUND` — без внутреннего текста Битрикс24 и в терминах запрошенного ресурса. Код объявлен в описании API у всех методов, которые обращаются к одной записи.

Неразбираемое значение `from` или `to` отвечает `400 INVALID_PARAMS` до обращения к Битрикс24; принимаются `YYYY-MM-DD` и ISO 8601. Пустое значение по-прежнему означает отсутствие фильтра. В описании API у `GET /v1/tasks/{taskId}/comments` объявлены реально работающие `limit` и `offset`.

### BC-0908-7: Конвертация лида проставляет обратные связи и отвечает в общем формате чтения

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

**Было**

[POST /v1/leads/{id}/convert](/docs/entities/leads/convert) отвечал `200`, но созданные сделка,
контакт и компания не несли ссылки на лид, из которого возникли: запрос сделок с фильтром по
лиду ничего не находил, и связать сделку с её источником было нельзя. Сам ответ приходил в сыром
формате Битрикс24 — с ключами в верхнем регистре через подчёркивание и числами-строками, —
единственный такой ответ среди маршрутов раздела: платформа читала созданные записи устаревшими
методами, чьи имена полей не совпадали со схемой, и значения проходили мимо нормализации.

**Стало**

Каждая созданная запись получает ссылку на исходный лид, а сам лид — ссылки на созданные контакт
и компанию (только на те, что действительно созданы). Ответ читается тем же методом, что и
обычное чтение сделки, контакта и компании, поэтому его формат совпадает с
[GET /v1/deals/{id}](/docs/entities/deals/get) и соседними — имена полей в общем стиле платформы,
числа числами.

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

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

### BC-0908-8: валюта оплаты, расходящаяся с валютой заказа, больше не подменяется молча

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

**Было**

[Создание оплаты](/docs/entities/payments/create) принимало любую `currency` с ответом `201`,
а Битрикс24 сохранял оплату в валюте её заказа — присланная валюта отбрасывалась без единого
замечания. Описание поля при этом обещало свободный выбор со ссылкой на
`GET /v1/currencies`, так что расхождение всплывало только при сверке.

**Стало**

Если `currency` передана и не совпадает с валютой заказа `orderId`, запрос отклоняется `409`
с кодом `CURRENCY_MISMATCH` до обращения к Битрикс24; сообщение называет обе валюты и заказ.
Совпадающая валюта принимается по-прежнему, поэтому оплата, прочитанная и отправленная
обратно целиком, работает как раньше. То же правило действует в
[batch](/docs/batch)-создании оплат. Если валюту заказа прочитать не удалось, запрос
пропускается, а не отклоняется.

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

Не передавайте `currency` вовсе, чтобы унаследовать валюту заказа, либо передавайте ровно
ту, что стоит у заказа: её показывает `GET /v1/orders/:id`.

### NEW-0908-9: Политика авто-сна standalone-сервера теперь видна в API

[POST /v1/infra/servers](/docs/infra/servers/create) возвращает сохранённый `data.sleepAfterMinutes` для каждого успешного создания, reuse и idempotency replay. Это поле ответа, а не новый параметр создания. Успешный standalone-деплой возвращает тот же снимок политики в `data.sleepAfterMinutes`, а SSE — в `sleepAfterMinutes` события `done`. Для обычного standalone-сервера, не защищённого политикой авто-сна агента или бота, при числовом таймауте и отсутствии включённых повторяющихся окон пробуждения в конец `warnings[]` добавляется предупреждение с префиксом `AUTO-SLEEP POLICY:` и двумя вариантами: окно пробуждения для задачи, которая успевает завершиться до таймаута, либо осознанный режим «Всегда онлайн» на невытесняемом тарифе. Окно только будит сервер и не удерживает фоновый процесс включённым. Для [POST /v1/infra/servers/:id/sleep-now](/docs/infra/lifecycle/sleep-now) теперь документирован существующий успешный исход `slept: false`, `reason: "WAKE_IMMINENT"`, когда ближайшее окно пробуждения уже на подходе. Сам ответ HTTP 200 не изменился.

### BC-0908-10: запись суммы у смарт-процесса без товарной части больше не отвечает ложным успехом

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

**Было**

`POST /v1/items/{entityTypeId}` и `PATCH /v1/items/{entityTypeId}/{id}` принимали `opportunity` и `isManualOpportunity` у любого типа смарт-процесса и отвечали `201`/`200`. Если у типа выключена товарная часть (`isLinkWithProductsEnabled: false`), Битрикс24 эти поля не сохраняет — сумма пропадала молча, а клиент видел успех и уходил.

**Стало**

Платформа сверяет запрошенное с перечитанной записью — она и раньше перечитывалась перед ответом, так что лишних обращений к Битрикс24 не появилось. Если запрос ЯВНО просил ручной режим (`isManualOpportunity: true`) и не получил его, приходит `422 AMOUNT_NOT_APPLIED`: `error.details.unappliedFields` перечисляет неприменённые поля, а `data` несёт фактическое состояние уже созданной или изменённой записи. Сумма, присланная БЕЗ этого флага, не проверяется: по ответу её не отличить от законного пересчёта по товарным позициям, который работает штатно, — поэтому такие запросы отвечают как раньше. Проверка не затрагивает сделки, лиды, счета и коммерческие предложения — у них товарная часть есть всегда.

### FIX-0908-11: хост из galaxyId теперь доступен для чтения

**Было**

Ответ создания galaxy-приложения уже содержал `galaxyId`, но [GET /v1/infra/servers](/docs/infra/servers/list) не показывал этот общий хост, а [GET /v1/infra/servers/:id](/docs/infra/servers/get) отвечал `404 NOT_FOUND` тому же API-ключу.

**Стало**

Оба вызова чтения возвращают ограниченную проекцию хоста с `access.via: "galaxy-reference"`. Она содержит только безопасные поля для чтения. Управление хостом и операции приложения по-прежнему требуют ресурса текущего ключа.

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

Изменять запросы не нужно. Клиенту, который использует появившийся в ответе общий хост, следует проверять `access.via`: значение `galaxy-reference` означает доступ только для чтения.

### NEW-0908-12: выдача ключа говорит, получил ли он доступ к данным Битрикс24

`POST /v1/connect/token` теперь отдаёт необязательное поле `b24_credentials`, а
`GET /v1/cowork/me` — такое же `b24Credentials`. Оно отвечает на вопрос, может ли выданный
ключ читать данные аккаунта Битрикс24: `ready: true` — права рабочие, `ready: false` — нет, и
тогда приезжает `reason` из закрытого набора (`WEBHOOK_NOT_CONFIGURED`, `WEBHOOK_MINT_FAILED`,
`WEBHOOK_MINT_REFUSED_BY_PORTAL`, `B24_MARKET_SUBSCRIPTION_REQUIRED`, `B24_MARKET_TRIAL_USED`,
`INT_TARIFF_REQUIRED`, `VIBE_SCOPES_ONLY`), а для причин про оплату — ещё и `upgradeUrl`.
Тот же набор причин уже несёт тело `401 TOKEN_MISSING`, так что клиент разбирает его одной
веткой кода.

Зачем это нужно. Ключ выдаётся и тогда, когда аккаунт отказал платформе подключить его к
своим данным: сам ключ работает для вызовов ИИ, а каждый запрос к данным аккаунта отвечает
`401 TOKEN_MISSING`. Раньше клиент узнавал об этом, только напоровшись, и не мог отличить
отсутствующее право от сетевого сбоя. Теперь состояние приезжает вместе с ключом и
переспрашивается на `GET /v1/cowork/me` в любой момент.

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

### FIX-0908-13: история рабочих дней возвращается, когда день ещё не закрыт

**Было**

`GET /v1/workday/records` отвечал `502 BITRIX_UNAVAILABLE`, если в выборку попадала запись
незакрытого рабочего дня. У такой записи ещё нет времени окончания, длительности и признака
подтверждения, а ручка требовала эти поля заполненными и отбраковывала всю страницу целиком —
включая закрытые дни, которые пришли в том же ответе. На практике это означало отказ у любого
сотрудника, который сейчас на смене.

**Стало**

Запись незакрытого дня возвращается как есть, вместе с остальной страницей: ответ остаётся
HTTP 200, поля окончания приходят такими, какими их отдал Битрикс24. Проверки сохранены там,
где ответ иначе стал бы неверным незаметно: страница по-прежнему отбраковывается, если запись
принадлежит другому сотруднику или у неё нет корректного времени начала — от него считается
`tzOffset`. Клиентам ничего менять не нужно.

### NEW-0908-14: агенты доступны для привязки к Открытым линиям

Добавлен `GET /v1/agents`: метод возвращает агентов владельца API-ключа и `bitrixBotId`, который можно передать в `welcomeBotId` конфигурации Открытой линии. Поле `agentId` для Hermes-агента не используется; `queue` по-прежнему содержит ID операторов-людей.

### NEW-0908-15: include отделов в записях сотрудников

Параметр `include=department` добавляет в запись сотрудника массив `_included.departments` с карточками всех отделов из `departmentId`. Для сотрудника без отдела приходит пустой массив. Запросу нужны скоупы `user` и `department`.

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

### NEW-0908-16: названия и описания всех базовых полей

В ответе `GET /v1/{entity}/fields` все объявленные базовые поля теперь содержат локализованные название и описание: русские для русскоязычных порталов и английские для международных.

### NEW-0908-17: защита от записи поверх более свежих исходников

`POST /v1/infra/servers/{id}/sources` принимает необязательный заголовок
`X-Parent-Version: v<N>` — «я правил вот эту версию». Если к моменту записи последняя версия
уже другая, запись отбивается кодом `SOURCE_VERSION_CONFLICT` (409), а
`error.details.latestVersionId` называет актуальную версию (`null` — живых версий нет вовсе).
Кривая форма заголовка — `400 INVALID_VERSION_ID`. Без заголовка ручка работает как прежде.

«Актуальная» здесь — самая свежая ЖИВАЯ версия, та же, что стоит первой в списке версий и
скачивается: названную в отказе версию всегда можно взять и повторить запись. Удалённые версии
в счёт не идут, но их номера не переиспользуются. Дедупликация исключением не является: архив,
побайтно совпавший с уже существующей версией, тоже получит `409`, если верхушка ушла вперёд.

Гарантия действует в одну сторону: ваша запись не ляжет поверх более свежей версии. Обратной
половины нет — со стороны браузера исходники сохраняет автосейв деплоя, которому родителя
взять неоткуда, поэтому следующая выкладка из браузера всё ещё может лечь сверху. Отказ
выносится там же, где создаётся версия, поэтому два запроса с одним родителем оба выиграть не
могут, а архив отбитого не остаётся в хранилище.

Ответ `POST /v1/cowork/deploy-key` получил два поля о переносе на новый ключ:
`repointTruncated` — переехало не всё, остаток уедет на следующем вызове, и
`applicationSlotBlocked` — карточка приложения осталась на прежнем ключе, потому что слот
свежего занят другой карточкой. Раньше частичный исход был виден только в журнале платформы.

**Затронутые эндпоинты:** `POST /v1/infra/servers/{id}/sources`, `POST /v1/cowork/deploy-key`

### NEW-0908-18: замена ключа приложения одним запросом и маска ключа в карточке

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

Появилась ручка `POST /v1/cowork/applications/{id}/key`, которая
приводит ключ приложения в рабочее состояние одним запросом. Слот личного ключа пуст — ключ
выдаётся, занят — перевыпускается; что именно произошло, говорит поле `issued`
(`minted` или `rotated`). Сырой ключ приходит один раз, как при создании приложения.
Заголовок `Idempotency-Key` обязателен, повтор с тем же ключом отвечает
`rawApiKey: null` и `KEY_NOT_REPLAYABLE`. Если запрос упал уже ПОСЛЕ выдачи (500 с
`orphanKeyId` в теле), ключ идемпотентности считается израсходованным навсегда: повтор с ним
получит `IDEMPOTENCY_KEY_ALREADY_USED`, а не второй ключ. Прочитайте карточку и повторяйте
с НОВЫМ ключом идемпотентности. Отдельный случай — ключ выдан, а карточку прочитать не
удалось: ответ всё равно `201` с секретом, `application` приезжает пустым, и об этом говорит
код `APPLICATION_CARD_UNAVAILABLE` в `warningCodes`. Карточку возьмите
`GET /v1/applications/{id}`: терять из-за неё единственную копию секрета незачем.

Прежний ключ не отзывается мгновенно: ему ставится срок в сутки, поэтому идущая публикация
доработает. Поле `previousKey.graceUntil` называет момент, когда он перестанет
аутентифицироваться. Тело запроса принимает `syncServerEnv: true` — тогда платформа заменит
ключ в переменных окружения задеплоенного сервера и перезапустит приложение, а поле `envSync`
скажет, чем это кончилось: заменено, строки в файле нет, сервер спит, перезапуск не удался и
так далее. Без этого приложение продолжит работать со старым ключом и через сутки начнёт
получать отказы.

Отказы приходят отдельными кодами, а не общим `400`: `NOT_APPLICATION_OWNER` — приложение
чужое; `KEY_ROTATE_OAUTH_APP_KEY`, `KEY_ROTATE_SYSTEM_KEY`, `KEY_ROTATE_LIVE_OAUTH_GRANT`,
`KEY_ROTATE_NOT_ACTIVE` — ключ в слоте заменить нельзя, причина в коде;
`KEY_ROTATE_KEY_VANISHED` — ключ исчез, пока летел запрос;
`KEY_LIMIT_REACHED` — исчерпана квота ключей;
`APPLICATION_KEY_REPLACE_IN_PROGRESS` — замена этого приложения уже идёт. Замена идёт по одной
на приложение, с каким бы ключом идемпотентности ни пришёл второй запрос: иначе оба выдали бы
по ключу, а слот достался бы тому, кто финишировал вторым. Брошенная упавшим запросом замена
перестаёт мешать через две минуты.

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

Карточка приложения получила блок `key`: `slot` (`auth` или `api` — какой ключ показан),
`present`, `prefix`, `suffix`, `status`, `expiresAt`, `lastUsedAt`, `rotatable` и
`rotateBlockedReason` (сработает ли замена и почему нет), `affectsShownKey` (тронет ли она
именно показанный ключ). Маска собирается клиентом из `prefix` и `suffix`. Поле `rotatable` говорит о том,
что платформе УЖЕ известно, и гарантией не является ни одной стороной: `false` — «известен
отказ, вот его причина» (`rotateBlockedReason`, в него входит и право доступа), `true` —
«известных отказов нет». Кнопку по нему рисовать удобно — гасите её при `false` и показывайте
причину, — но тупиком `false` делать не стоит: отказ мог уже отпасть, а карточка ещё не узнать
(про запаздывание и про два гейта вне поля — ниже). Судите по ответу двери, а не только по
полю. Блок приходит ВСЕГДА; тому, кому приложение просто открыли, поля приезжают
пустыми (`present: false`), а `rotatable` — `false` с причиной `NOT_MANAGER`: это значит
«не положено», а не «ключа нет». На ПУСТОМ слоте, где вызов выдал бы ключ, поле учитывает
и гейты выдачи: `ISSUANCE_BLOCKED` (у аккаунта нет доступа к платформе), `INFRA_DISABLED`
(инфраструктура выключена), `KEY_QUOTA_REACHED` (квота ключей исчерпана), `READONLY_POLICY`
(аккаунт выдаёт только ключи для чтения). Про сам вызывающий ключ поле говорит теми же кодами,
что и дверь, и они повторяются на КАЖДОЙ карточке ответа: `INSUFFICIENT_SCOPE` (у ключа нет
скоупа `vibe:cowork`) и `COWORK_HARNESS_KEY_FORBIDDEN` (ключ выписан внешнему агенту). Точность
у поля односторонняя намеренно: `false` на пустом слоте читается по уже известному платформе
состоянию доступа — только что изменившийся доступ может ещё показываться как `false`, пока это
состояние не обновится. ДВА гейта Коворк/Код остаются ВНЕ поля и могут отказать при `true`:
платформенный рубильник (503 `COWORK_FEATURE_DISABLED`) и состояние вашего места Коворк/Код
(403 `COWORK_NOT_ACTIVATED`, о котором и так говорит `GET /v1/cowork/state`). Ни то ни другое не
относится к самой карточке, поэтому по карточкам не размножается — обрабатывайте отказ двери, а
не читайте `true` как гарантию.

**Затронутые эндпоинты:** `POST /v1/cowork/applications/{id}/key`,
[GET /v1/applications](/docs/applications), `GET /v1/applications/{id}`

### NEW-0908-19: чтение списка, активного спринта и спринта по ID

API Вайбкод добавляет [GET /v1/scrum/sprints](/docs/scrum/sprints/list) для чтения доступных спринтов, [GET /v1/scrum/sprints/active](/docs/scrum/sprints/active) для чтения текущего спринта проекта и [GET /v1/scrum/sprints/:id](/docs/scrum/sprints/get) для чтения одного спринта по ID. Ответ содержит название, даты, статус и связь с проектом. Если активного спринта нет, соответствующая ручка возвращает `data: null`.

### BC-0908-20: приостановленное место Коворк/Код больше не открывает Битрикс24 в обход подписки Маркетплейса

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

**Было**

Ключ десктопа Коворк/Код и ключ агента открывали REST Битрикс24 на аккаунте без подписки Маркетплейса независимо от того, действует ли само место. Приостановленное место сохраняло эту возможность до тех пор, пока не гасился ключ, — около месяца.

**Стало**

Возможность привязана к состоянию места. Пропуск действует только у места в состоянии `ACTIVE`, включая место с назначенной отменой — до конца оплаченного срока. У приостановленного, отменённого и припаркованного места ключ обслуживается общим правилом аккаунта: там, где подписка Маркетплейса куплена, не меняется ничего, а без неё вызовы к Битрикс24 получают тот же отказ аккаунта, что и любое другое приложение.

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

Возобновить подписку Коворк/Код либо оформить подписку Маркетплейса на аккаунте. Текущее состояние места приходит в поле `subscription.state` ответа [GET /v1/cowork/state](/docs/cowork/state).

### NEW-0908-21: каталог инструментов 1С доступен через клиентский API

`GET /v1/onec/tools` возвращает последний проверенный каталог инструментов портала вместе с признаками `loaded`, `stale`, версиями и временем обновления. Каждый инструмент содержит `method`, `title`, `description` и `inputSchema`. Эндпоинт требует scope `vibe:onec`, включённый коннектор на портале и сегмент RU, но не требует связи текущего пользователя с пользователем 1С. Пока каталог не загружен, ответ явно содержит `loaded: false`, пустой массив `tools` и `null` в полях ревизии и времени.

`POST /v1/onec/tools/{method}/call` теперь принимает вложенные JSON-значения в `filters`: до 200 элементов в массиве, 50 ключей в объекте, 1024 символов в строке, глубины 6 и 2000 узлов на весь объект `filters`. Ключи `__proto__`, `prototype` и `constructor` запрещены на любом уровне. Поля `user`, `portal` и `requestId` по-прежнему назначает сервер. Вызов остаётся асинхронным: успешный запрос отвечает `202`, а результат читается через `GET /v1/onec/operations/{operationId}`.

### FIX-0908-22: реестр исходников больше не обещает дверь, которая ответит вызывающему ключу 403

**Было**

[GET /v1/me/sources](/docs/source-storage/registry) считал `reachableViaApi` по одной
достижимости СТРОКИ — «жив ли сервер, не удалено ли приложение» — и не смотрел на КЛЮЧ,
которым пришли. А двери, на которые ведут его указатели, скоуплены по ключу и отбивают ключ
OAuth-приложения, если сервер принадлежит не ему.

Наблюдаемо это выглядело так. Выдача перечисляет владельцев снимков по ПОЛЬЗОВАТЕЛЮ, а не по
ключу, поэтому, придя ключом своего приложения, вы получали свои серверы, заведённые ЛИЧНЫМИ
ключами, с `reachableViaApi: true` и рабочим с виду `listEndpoint` — а
[GET /v1/infra/servers/{id}/sources](/docs/source-storage/servers) по этому адресу отвечал
тому же ключу `403 NOT_AUTHORIZED`.

Та же ложь была на строке приложения: автор приложений A и B, пришедший ключом приложения B,
видел строку A с рабочим указателем, по которому дверь отвечала
`403 SOURCE_APP_ID_MISMATCH`.

**Стало**

`reachableViaApi` отвечает на один вопрос: пустит ли ВЫЗЫВАЮЩИЙ КЛЮЧ детальный переход. Строка,
чью дверь этот ключ не откроет, несёт `reachableViaApi: false`, а `listEndpoint` и
`latestDownloadEndpoint` — `null`. Обе `kind`-ветки выдачи (`server` и `legacy-app`) считают это
тем же предикатом, что и сама дверь.

Строка из выдачи НЕ пропадает: снято ложное обещание, а не видимость. Владелец по-прежнему
видит, что снимки существуют, и по `user.id` понимает, чьи они.

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

Проверяйте `reachableViaApi` перед тем, как идти по `listEndpoint` или
`latestDownloadEndpoint`. Оба поля и раньше были объявлены как `string | null` и уже приходили
пустыми у осиротевшего сервера и мягко удалённого приложения, а `reachableViaApi: false` уже был
документированным значением для тех же двух случаев, — поэтому клиент, который соблюдал контракт,
менять ничего не обязан. Получили `false` там, где ждали `true`, — значит вы пришли ключом приложения:
повторите вызов личным ключом (своим или владельца сервера) либо ключом администратора портала.
Для строки приложения то же самое: ключ приложения открывает только своё приложение.

**Чего это НЕ меняет**

Доступ. Ни одна дверь не стала пускать или не пускать кого-то нового: правило доступа осталось
прежним, изменилось только то, что выдача перестала обещать доступ, которого нет. Сведение дверей
одного сервера к единой модели владения — отдельное ломающее изменение, и оно едет отдельной
записью; там же поедут подсказки отказов publish и deploy, потому что у них тот же адрес снимается
ВМЕСТЕ с полем `hint.requiredAction`, а это уже удаление документированного поля.
