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

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

### NEW-0917-2: у сервера появился режим работы: `runMode` и `workSchedule` в чтениях

`GET /v1/infra/servers` и `GET /v1/infra/servers/{id}` отдают два новых поля.

`runMode` — `IDLE`, `SCHEDULE` или `ALWAYS`. Он выводится из уже публичных полей: назначенного
расписания, `createdVia` и `sleepAfterMinutes`. По одному `sleepAfterMinutes` эти состояния было не
различить: у машины, которая просто не засыпает, и у агента, которому засыпать запрещено, там
одинаково `null`. У агента и бота `runMode` всегда `ALWAYS` — число в `sleepAfterMinutes` на это не
влияет.

`workSchedule` — `null`, пока сервер не работает по расписанию. Иначе объект:

```json
{
  "id": "…",
  "name": "Смены склада",
  "presetKey": null,
  "timezone": "Europe/Moscow",
  "windows": [
    { "isoDay": 1, "start": "08:00", "end": "13:00" },
    { "isoDay": 1, "start": "14:00", "end": "20:00" }
  ]
}
```

`isoDay` — 1…7 от понедельника, `start` и `end` — местное время расписания в виде `ЧЧ:ММ`. Окна
приходят списком, а не парой полей «начало/конец»: в одном дне их может быть два — например, смена с
перерывом. У готового расписания платформы `name` пустое, а опознаётся оно по `presetKey`.

Поля добавочные: прежние запросы и разбор ответов продолжают работать без изменений.

### FIX-0917-4: удалённое приложение показывает понятное состояние места показа

**Было**

Оставшееся в Битрикс24 место показа локально удалённого приложения открывало сырой ответ `401 APP_RESOLVE_FAILED`.

**Стало**

[POST /v1/bitrix-handler](/docs/infra/app-runtime) возвращает `410 Gone` с локализованной страницей о недоступности приложения и ссылкой назад в Битрикс24. Ответ содержит `Cache-Control: no-store`, `Pragma: no-cache` и `Referrer-Policy: no-referrer`.

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

Изменения интеграции не требуются. Пользователь видит конечное состояние вместо ошибки авторизации.

### FIX-0917-5: внешнее API приложений возвращает 503 при перегрузке

**Было**

При высокой нагрузке новые запросы внешнего API приложений не получали специальный ответ о временной недоступности.

**Стало**

При временной перегрузке внешний API приложений отклоняет новый запрос с HTTP 503 `APP_API_UNAVAILABLE` и заголовком `Retry-After`. Форматы успешных ответов приложений не изменились.

**Затронутые эндпоинты:** [GET, POST, PUT, PATCH, DELETE, HEAD /v1/applications/:id/api/*](/docs/applications/external-api).

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

Изменения не требуются. После HTTP 503 `APP_API_UNAVAILABLE` клиент может повторить запрос через время, указанное в `Retry-After`.

### FIX-0917-6: предупреждения успешной установки рантайма теперь видны в ответе деплоя

**Было**

Шаг `runtime` возвращал `status: "ok"` и длительность, когда установка завершалась успешно, и всё, что
установщик рантайма напечатал по ходу дела, до вызывающей стороны не доезжало — ни в обычном ответе, ни
в потоке `?stream=true`. Установщик при этом мог сообщить, что поставил рантайм не задуманным путём:
например, один из источников пакетов он отложил в сторону, чтобы apt оставался читаемым, или взял
рантайм из запасного источника. Деплой был зелёный, узнать об этом можно было только зайдя на сервер.

**Стало**

Если установка прошла успешно, но установщик сообщил о себе, шаг `runtime` возвращает
`status: "warning"`, текст сообщений лежит в `stdout` этого шага, а в `warnings[]` ответа появляется
строка-указатель на него. Сам ответ остаётся успешным: `success: true` и HTTP 200 не меняются, поле
`duration` шага на месте, форма ответа прежняя. Когда установщику сказать нечего, шаг по-прежнему
возвращает `status: "ok"` без `stdout`.

### FIX-0917-8: создание папки через глобальный POST /v1/batch больше не отклоняется Битрикс24

**Было**

Под-вызов `{ "entity": "folders", "action": "create" }` в [POST /v1/batch](/docs/batch) отправлял поля папки одним конвертом `fields[...]`, тогда как метод Битрикс24 ждёт родительскую папку верхним параметром `id`, а остальные поля — под `data[...]`. Каждое такое создание возвращалось с ошибкой `ERROR_ARGUMENT`, хотя одиночный [POST /v1/folders](/docs/entities/folders/create) и пер-сущностный `POST /v1/folders/batch` с тем же телом работали.

**Стало**

Глобальный batch собирает создание папки в той же форме, что и одиночный роут, поэтому под-вызов действительно создаёт папку. Пропущенный `parentId` отклоняется кодом `MISSING_PARENT_ID` до обращения к Битрикс24 — тем же, что и на одиночном роуте, вместо сырой ошибки портала.

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

Менять ничего не нужно. Если вы обходили дефект одиночными вызовами создания папки, теперь папки можно создавать пачкой в одном batch.

### FIX-0917-9: список записей учёта времени отвечает 404 на отсутствующую задачу

**Было**

[GET /v1/tasks/:taskId/time](/docs/entities/tasks/time/list) на задачу, которой нет или которая недоступна ключу, отвечал `422 BITRIX_ERROR`, а в поле `message` приезжал внутренний текст исключения Битрикс24 вида `TASKS_ERROR_EXCEPTION_#1; Task not found or not accessible; 1/TE/TASK_NOT_FOUND_OR_NOT_ACCESSIBLE`. Соседние маршруты этого же раздела на такой же случай уже отвечали `404`.

**Стало**

Тот же запрос отвечает `404` с кодом `TASK_NOT_FOUND` и коротким сообщением `Task not found or not accessible.`; внутренний текст исключения наружу больше не уходит. Успешный ответ не изменился: задача на месте — по-прежнему `200` с тем же телом. Прочие ошибки чтения, включая отбой по лимиту запросов, сохраняют прежние статусы.

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

Менять код не требуется. Если обработчик различал «задачи нет» по статусу `422` или по тексту сообщения, переключите его на `404` и код `TASK_NOT_FOUND`.

### FIX-0917-10: отказ «на портале нет учёта рабочего времени» получил собственный код

**Было**

Состояние портала, при котором учёт рабочего времени недоступен, приезжало в операции раздела «Рабочий день» двумя разными ответами, и ни один не называл причину. Если модуль «Рабочее время» не установлен или не входит в тариф, Битрикс24 отвечает «Метод не найден» на любой метод `timeman.*` — и клиент получал `404 ENTITY_NOT_FOUND`, то есть сообщение о ненайденной сущности, хотя сама сущность была в порядке и недоступен был метод. Этого кода не было ни в одной таблице ошибок раздела. Если же модуль установлен, но учёт рабочего времени выключен в настройках портала, тот же по сути отказ приезжал общим `422 BITRIX_ERROR` с текстом Битрикс24, то есть был неотличим от ошибки данных.

**Стало**

Оба состояния отвечают `409 TIMEMAN_MODULE_NOT_ENABLED` — код, который раздел уже использовал в [GET /v1/workday/records](/docs/workday/records). Сообщение называет обе возможные причины и действие: попросить администратора портала включить учёт рабочего времени. Ответ одинаков на всех операциях раздела, поэтому совет из документации истории рабочих дней — «позовите текущий статус, тот же отказ означает выключенный учёт» — теперь работает буквально. Такой отказ больше не учитывается защитой от циклов ошибок, то есть остаётся понятным при любом числе повторов, а не подменяется на `429 ERROR_LOOP_DETECTED`.

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

Действий не требуется: отказ как был, так и остаётся ответом класса 4xx. Интеграция, которая различала это состояние по тексту сообщения Битрикс24, может перейти на код `409 TIMEMAN_MODULE_NOT_ENABLED`. Прежние `404 ENTITY_NOT_FOUND` и `422 BITRIX_ERROR` документацией раздела для этого состояния не обещались.

**Затронутые эндпоинты:** [POST /v1/workday/open](/docs/workday/open), [POST /v1/workday/close](/docs/workday/close), [POST /v1/workday/pause](/docs/workday/pause), [GET /v1/workday/status](/docs/workday/status), [GET /v1/workday/settings](/docs/workday/settings), [GET /v1/workday/schedule](/docs/workday/schedule), [GET /v1/workday/records](/docs/workday/records)

### BC-0917-11: команда в галактике без статуса выхода больше не считается успешной

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

**Было**

[POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) на приложении в галактике (`kind: "GALAXY_APP"`): если хост отвечал, но статус выхода не присылал, вызов возвращал `success: true` и `exitCode: -1`. Завершение при этом утверждалось там, где платформа его не наблюдала, а команда, действительно вышедшая с кодом `-1`, была от этого случая неотличима.

```json
{ "success": true, "data": { "exitCode": -1, "stdout": "", "stderr": "", "duration": 0, "truncated": false } }
```

**Стало**

Тот же случай возвращает `EXEC_NO_EXIT` с `success: false` — так же, как это давно работает на отдельной виртуальной машине. Ответ остаётся HTTP 200. Полей `exitCode`, `duration` и `truncated` в нём нет: их значения платформе неизвестны, а накопленный до обрыва вывод приходит в `data`.

```json
{ "success": false, "error": { "code": "EXEC_NO_EXIT", "message": "Exec stream ended without an exit status from the agent" }, "data": { "stdout": "", "stderr": "" } }
```

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

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

Обработайте код `EXEC_NO_EXIT` там, где вызываете команду на приложении в галактике: случай «поток кончился без статуса выхода» теперь приходит именно так, а не успехом с `exitCode: -1`.

⚠️ Это не делает `exitCode: -1` в ответе однозначным во всех случаях. Остаётся ещё одна ситуация, в которой приходит `-1`: агент прислал терминальный чанк, но кода в нём не назвал. Она встречается редко, форму ответа этим изменением не меняет и от настоящего выхода с кодом `-1` в теле ответа по-прежнему неотличима. Различает их только запись операции: там код сохраняется, лишь когда агент его назвал, иначе приходит `null`. Поэтому если вам важно отличить «вышла с -1» от «кода не назвали», читайте исход по идентификатору через [GET /v1/infra/operations/:operationId](/docs/infra/deploy/operation-status), а не по полю ответа.

### BC-0917-12: пустое значение параметра в списке операций больше не подменяется умолчанием

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

**Было**

[GET /v1/infra/servers/:id/operations](/docs/infra/deploy/operations) с пустым значением параметра — `?limit=` — отвечал `200` и отдавал ленту с умолчанием, как будто параметр не передавали вовсе.

```
GET /v1/infra/servers/srv_123/operations?limit=
→ 200, 5 последних строк
```

**Стало**

Тот же вызов отвечает `400 INVALID_LIMIT`. Пустое значение — это переданное значение, а не опущенный параметр: оно рождается из шаблона с незаполненной переменной, то есть сузить выдачу хотели и не сумели. Умолчания `limit=5` и `kind=deploy` действуют только тогда, когда ключа в строке запроса нет вовсе.

```json
{ "success": false, "error": { "code": "INVALID_LIMIT", "message": "limit must be a non-negative safe integer" } }
```

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

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

Проверьте места, где строка запроса собирается шаблоном. Если значение может оказаться пустым, не подставляйте ключ вовсе — тогда действует умолчание. Тот же разбор применяется к `?kind=`, но этот параметр в том же выпуске и появился, поэтому прежнего поведения у него не было.

### NEW-0917-13: исход команды на сервере читается после обрыва ожидания

У команды [POST /v1/infra/servers/:id/exec](/docs/infra/deploy/exec) появился устойчивый идентификатор запуска, по которому исход читается отдельным запросом. Раньше исход существовал только внутри текущего ответа, поэтому транспортный таймаут или разрыв соединения теряли его навсегда, и узнать, выполнилась команда или нет, было нечем. Повтор при этом небезопасен: транспортный таймаут прекращает только ожидание на стороне клиента, а команда на сервере может доработать до конца уже после того, как клиент получил ошибку.

Идентификатор приходит полем `operationId` в теле (`data.operationId` при успехе, `error.operationId` при отказе) и заголовком ответа `X-Vibe-Operation-Id`. На отдельной виртуальной машине заголовок уходит до начала выполнения команды, поэтому доживает до клиента даже когда тело потеряно, а при `?stream=true` то же значение приходит первым кадром `event: operation`. У приложения в галактике раннего канала нет: та ветка не открывает поток и отдаёт заголовок только с терминальным ответом, поэтому при обрыве ожидания идентификатора не будет — такой запуск ищется в списке операций сервера.

Исход читается через [GET /v1/infra/operations/:operationId](/docs/infra/deploy/operation-status) и хранится 7 суток. Поле `kind` в ответе этой ручки теперь принимает значение `exec` наряду с `deploy`, а у команды приходит `exitCode` — код возврата, если агент его подтвердил, иначе `null`. Статус `succeeded` у команды означает, что она отработала до конца, и ненулевой код возврата приходит с этим же статусом. Статус `failed` ставится, только когда платформа может доказать, что команда не запускалась, например при `EXEC_BUSY`. Пока команда идёт, статус — `running`. Во всех остальных случаях обрыва приходит `unknown`: команда может продолжать выполняться на сервере, поэтому повторять изменяющую команду по этому статусу нельзя без сверки.

В списке [GET /v1/infra/servers/:id/operations](/docs/infra/deploy/operations) появился параметр `kind` со значением `deploy` по умолчанию. Без параметра приходит тот же набор строк, что и раньше, — только выкладки, и каждая строка при этом дополнительно несёт поле `kind`. `kind=exec` показывает команды, `kind=all` — смешанную ленту. Недопустимое значение отвечает `400 INVALID_KIND`.

Запись несёт исход попытки — статус, код возврата и причину отказа. Вывод команды в ней не сохраняется: если он нужен после обрыва, запускайте команду фоновой задачей и читайте журнал через [GET /v1/infra/servers/:id/logs](/docs/infra/deploy/logs).

### FIX-0917-14: у занятого сервера машинное поле восстановления называет ручку с проверкой

**Было**

При `409 EXEC_BUSY` на отдельной виртуальной машине (`kind: "STANDALONE"`) поле `error.hint.recoveryAction` называло [`DELETE /v1/infra/servers/:id/lock`](/docs/infra/deploy/lock) — вызов, который снимает замок безусловно, не проверяя, идёт ли операция. Поле это единственное в подсказке, по которому клиент действует, не читая прозу, поэтому автоматический вызов мог прервать живую выкладку.

```json
{ "error": { "code": "EXEC_BUSY", "hint": { "recoveryAction": "DELETE /v1/infra/servers/:id/lock" } } }
```

**Стало**

Там же приходит [`POST /v1/infra/servers/:id/unstick`](/docs/infra/servers/unstick) — он сам отвечает `409 OPERATION_IN_PROGRESS`, пока замок ещё наблюдается — и когда операция доказанно жива, и когда доказать её смерть нечем, — а снимает замок сам только если это exec-мьютекс, переживший собственный срок; когда признаков замка нет вовсе, отказывать нечему и вызов проходит, и дополнительно перетряхивает туннель агента. Снятие замка из контракта не исчезло: оно осталось в тексте `error.hint.recovery` вторым шагом. Право на него даёт только независимо подтверждённый простой — состояние сервера и логи. Отсутствие `error.hint.holder` таким подтверждением не является: чтение держателя fail-open, поэтому «держателя нет» приходит и тогда, когда прочитать его не удалось.

```json
{ "error": { "code": "EXEC_BUSY", "hint": { "recoveryAction": "POST /v1/infra/servers/:id/unstick" } } }
```

Заодно текст подсказки перестал предлагать проверять «нет ли идущей операции» по списку операций. Список отдаёт только запуски вашего же ключа, поэтому операция, начатая другим ключом того же портала, в нём не видна, и пустой список ничего не доказывал. Признак держателя приходит в том же ответе — `error.hint.holder`.

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

Клиент, который исполняет `recoveryAction` как есть, менять ничего не должен — он начнёт вызывать защищённую ручку сам. Если вызов `DELETE /lock` записан у вас в коде постоянной строкой, перечитайте `error.hint.recovery`: там сказано, при каком условии этот шаг уместен.

### FIX-0917-15: приложения с интерфейсом больше не создаются в режиме «только API»

**Было**

[POST /v1/apps](/docs/apps/create) отвечал `201` и возвращал `mobile: true`, но платформа Вайбкод не сообщала порталу режим установки, и портал подставлял свой умолчательный — «только API». Приложение с интерфейсом сохранялось без интерфейса: REST отвечал, а самого приложения не было ни в меню портала, ни в мобильном клиенте.

**Стало**

Режим передаётся явно. Приложение с интерфейсом устанавливается как приложение с интерфейсом; приложение без пунктов меню по-прежнему устанавливается как «только API».

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

Ответ `201` и его состав не меняются, действий не требуется. Приложение, созданное раньше, платформа не переделывает — снимите режим «только API» в карточке приложения на портале либо создайте приложение заново.

### BC-0917-16: создание приложения Коворк/Код: живое платное место и проверка скоупов

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

**Было**

Аккаунт без доступа к платформе получал на [POST /v1/cowork/applications](/docs/cowork/applications-create) отказ ещё до
обращения к Битрикс24, даже когда у пришедшего было живое платное место Коворк/Код:
402 с причиной доступа либо 403, если план аккаунта прочитать не удалось.

Скоуп модуля-эксклюзива, недоступного аккаунту, эта ручка принимала молча: запрос
отвечал 201, и ключ уезжал со скоупом, который соседние ручки выдачи ключей тому же
аккаунту отказывают.

**Стало**

Живое платное место открывает эту ручку: приложение создаётся и ключ выдаётся. Прочие
ручки ВЫПИСКИ ключа отвечают как раньше — правило касается только этой.

Скоуп недоступного аккаунту модуля теперь отклоняется явно: 403
`SCOPE_NOT_AVAILABLE_ON_PORTAL`, как на соседних ручках выдачи ключей. Отказ приходит до
создания записи, приложение не заводится и ключ не выписывается.

Послабление по месту приезжает РАСКАТКОЙ и включается по аккаунтам. Пока она не дошла до
вашего аккаунта, вызов отвечает прежним отказом — 402 с причиной доступа либо 403; это
признак раскатки, а не ошибки интеграции. Проверка скоупов раскаткой не гейтится и
действует сразу.

Место снимает ТОЛЬКО отказ по доступу аккаунта к платформе, и только его. Исчерпанный
баланс им не лечится — счёт по-прежнему отвечает `ACCOUNT_FROZEN`, эта проверка идёт раньше.
Не снимаются местом и недоступный регион с коробочным аккаунтом без подтверждённого
партнёрства — такому аккаунту ждать раскатки незачем.

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

Передавайте в `b24Scopes` только те скоупы, которые доступны аккаунту. Состав доступных
модулей читается из карточки аккаунта; ранее выданные ключи не меняются. Если запрос
получил 403 `SCOPE_NOT_AVAILABLE_ON_PORTAL`, уберите названный скоуп из тела и повторите
запрос с новым ключом идемпотентности.

### FIX-0917-17: порог простоя на сервере с расписанием больше не молчит

**Было**

`PATCH /v1/infra/servers/{id}/sleep` на сервере, которому назначено расписание работы, отвечал
`200`, записывал порог и не менял ничего: расписание сильнее порога, и машина продолжала работать
по своим окнам. Счёт приходил прежним, а по ответу это было не отличить от применённой настройки.
Сгенерированные расписанием строки пробуждения при этом попадали в `GET
/v1/infra/servers/{id}/wake-schedules` наравне с обычными, хотя снять или поправить их было нельзя —
следующая синхронизация расписания возвращала их обратно.

**Стало**

Запись порога снимает расписание: число переводит сервер в режим «засыпает, когда нет запросов»,
`null` — в «круглосуточно». Ответ остаётся `200` и несёт новое поле `workScheduleCleared` — по нему
видно, что расписание было снято. Сгенерированные расписанием строки пробуждения в списке окон
больше не показываются, а обращение к такой строке по идентификатору в `PATCH`/`DELETE` отвечает
`404 NOT_FOUND`: этими строками управляет расписание сервера, а не список окон. Заведённые вручную
окна не затрагиваются.

### BC-0917-18: схема счёта достроена: одиннадцать полей ответа объявлены, семь из них — только для чтения, contacts помечено как не возвращаемое

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

**Было**

[GET /v1/invoices/{id}](/docs/entities/invoices/get) (а также список, поиск и `include`) отдавал тринадцать полей, которых не было ни в схеме, ни в справочнике, ни в машинном описании OpenAPI: `contactIds`, `observers`, `taxValue`, `locationId`, `webformId`, `lastActivityBy`, `utmSource`, `utmMedium`, `utmCampaign`, `utmContent`, `utmTerm`, а также `parentId2` и `parentId7`. Страница полей описывала восемь из них строками из живых метаданных портала — с типами Битрикс24 (`user`, `crm_contact`, `double`…) и без признака «только чтение»; пяти `utm*` не было нигде. Явный `select` любого из первых одиннадцати отвечал `200` с предупреждением `UNKNOWN_SELECT_FIELD`, хотя в `GET /v1/invoices/{id}` значение приходило. Семь из них Битрикс24 проставляет сам и присланное значение не хранит — `taxValue`, `lastActivityBy` и пять `utm*` — но создание, изменение и импорт отвечали успехом (`201`/`200`), а значение исчезало. Страница полей обещала, что `contacts` у счёта приходит в ответе и работает в `select`; на деле ключа в ответе нет никогда, а попытка записать `contacts` отвечала сырой ошибкой ORM Битрикс24 `422 BITRIX_ERROR`. Пустое `locationId` приходило пустой строкой.

**Стало**

Одиннадцать полей объявлены в схеме, справочнике, машинном описании и на [странице полей](/docs/entities/invoices/fields) с типами по живому замеру: `contactIds`, `observers` (массивы; изменение заменяет набор, пустой список снимает привязки), `locationId` (строка), `webformId` (число) — доступны на запись; `taxValue`, `lastActivityBy`, `utmSource`, `utmMedium`, `utmCampaign`, `utmContent`, `utmTerm` — только для чтения. Явный `select` этих полей больше не даёт предупреждения. Значение, присланное в поле только для чтения, — которое Битрикс24 и раньше не сохранял — теперь отклоняется: создание и изменение — `400 READONLY_FIELD`, импорт — `400 IMPORT_ITEM_VALIDATION`, пакетный запрос сущности — `400 BATCH_ITEM_VALIDATION`, общий пакетный запрос — `READONLY_FIELD` под вызовом в `data.errors`. Поле `contacts` объявлено как не возвращаемое (`notReturned`) и только для чтения: страница и `GET /v1/invoices/fields` честно говорят, что ключа в ответе не бывает, а попытка записи отклоняется тем же гардом и с теми же кодами по дверям, что у полей только для чтения (создание и изменение — `400 READONLY_FIELD`, импорт — `400 IMPORT_ITEM_VALIDATION`, пакет сущности — `400 BATCH_ITEM_VALIDATION`, общий пакет — `READONLY_FIELD` под вызовом) вместо сырой ошибки портала; привязанные контакты — в `contactIds`. Пустое `locationId` приходит как `null`, как и остальные пустые строки записи. Как у любого объявленного скалярного поля, объект или массив в `locationId` и `webformId` (и нечисловая строка в `webformId`) теперь отклоняются `400 INVALID_PARAMS` до отправки на портал — раньше такое значение уезжало в Битрикс24 без проверки. Пустой список в `contactIds` или `observers` снимает привязки на `PATCH /v1/invoices/{id}`; пакетный запрос сущности и общий пакетный запрос пустой список передать не могут (подкоманда уходит строкой запроса, где у пустого массива нет записи — ключ пропадал, а ответ был успешным) и теперь отклоняют его: `400 BATCH_ITEM_VALIDATION` и `INVALID_PARAMS` под вызовом в `data.errors`. Списки и поиск без `select` по-прежнему несут пользовательские поля `ufCrm_*` и связи `parentId2`/`parentId7`; явный `select` пяти `utm*` на списке и поиске Битрикс24 игнорирует — эти поля приходят только в полной записи. В `filter` и `order` эти поля, как и прежде, не принимаются (`400 UNKNOWN_FILTER_FIELD` / `400 UNKNOWN_SORT_FIELD`). Поля связей `parentId2` и `parentId7` остаются динамическими: их тип и подписи по-прежнему берутся из живых метаданных портала, фильтр по ним работает, сортировка — нет.

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

Если вы отправляли `taxValue`, `lastActivityBy` или `utm*` при создании, изменении или импорте счёта — уберите их из тела: Битрикс24 их не хранил и раньше, налог задаётся через товарные позиции, автора активности и UTM-метки проставляет портал. Если вы отправляли `contacts` — замените на `contactIds` (полный список привязок). Если вы проверяли `locationId` на пустую строку — проверяйте на `null`. Если в `select` были поля из списка выше — предупреждение пропало, менять ничего не нужно; `utm*` на списке и поиске читайте без `select`. Если вы снимали привязки или наблюдателей пустым списком через пакетный запрос — это никогда не срабатывало; делайте это одиночным `PATCH /v1/invoices/{id}`.

**Затронутые эндпоинты:** [GET /v1/invoices/{id}](/docs/entities/invoices/get), [GET /v1/invoices](/docs/entities/invoices/list), [POST /v1/invoices/search](/docs/entities/invoices/search), [GET /v1/invoices/fields](/docs/entities/invoices/fields), [POST /v1/invoices](/docs/entities/invoices/create), [PATCH /v1/invoices/{id}](/docs/entities/invoices/update), [POST /v1/invoices/import](/docs/import), [POST /v1/{entity}/batch](/docs/batch), [POST /v1/batch](/docs/batch).

### FIX-0917-19: Idempotency-Key защищает создание и переиспользование galaxy-приложений

**Было**

Первое galaxy-создание не резервировало `Idempotency-Key`: потеря ответа и повтор могли создать второй ресурс. Переиспользование слота также не сохраняло ключ и могло повторно запустить сборку.

**Стало**

После включения защиты `POST /v1/infra/servers` резервирует ключ при создании и переиспользовании galaxy-приложения. Ответ остаётся HTTP 201: повтор в течение 15 минут возвращает тот же ресурс с `Idempotent-Replayed: true`, без повторной сборки и выдачи SSH-секретов. Разные ключи могут указывать на один переиспользованный слот. После окна занятый ключ получает `409 IDEMPOTENCY_KEY_ALREADY_USED` либо обычный отказ политики. Записанные ключи защищены и после выключения защиты; прежние galaxy-создания без записи ключа восстановить нельзя.

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

Используйте тот же ключ при потере ответа. После ошибки сборки вызывайте явный deploy. Защита включается по порталам постепенно: пока она не включена, первое galaxy-создание ведёт себя как раньше, поэтому проверьте список ресурсов перед повтором.

### BC-0917-20: ошибка режима ключа указывает на доступный переключатель

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

**Было**

Текстовые подсказки о `403 WRITE_BLOCKED_READONLY_KEY` в самоописании ключа и описаниях операций отправляли любой ключ в `/keys`. Ключи авторизации приложений на этой странице не показываются, поэтому переключить их режим по подсказке было невозможно.

**Стало**

Подсказки теперь учитывают вид вызывающего ключа. Форма ответа не изменилась: готовый путь по-прежнему находится в обязательном поле `error.details.switchUrl` и ведёт на `/keys` для личного ключа, `/management-keys` для management-ключа, `/applications` для ключа с карточкой Application и `/apps` для самостоятельного OAuth-приложения.

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

Для `WRITE_BLOCKED_READONLY_KEY` менять обработку ответа не нужно. Если интерфейс показывает действие, используйте `error.details.switchUrl` как готовый путь.

**Миграция ротации OAuth:** конкурентная операция над тем же приложением теперь может вернуть `409 REISSUE_IN_PROGRESS`, пока изменение выполняется, или `409 KEY_NOT_ACTIVE`, если исходный ключ уже изменился. Перечитайте состояние ключа и повторяйте ротацию только для всё ещё действующей исходной строки. Прежний небезопасный конкурентный сценарий без явного отказа не поддерживается.

**Затронутые эндпоинты:** [GET /v1/me](/docs/keys-auth/me), [GET /v1/openapi.json](/docs/api-reference), [POST /v1/placements/bind](/docs/apps/placements/bind), [POST /v1/placements/unbind](/docs/apps/placements/unbind), [PATCH /v1/apps/:id](/docs/apps/update), [POST /v1/apps/:id/relink-oauth](/docs/apps/relink-oauth), `POST /v1/keys/:id/rotate`.

### BC-0917-21: ответы по контактам и сделкам без служебной строки поискового индекса Битрикс24

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

**Было**

[GET /v1/contacts/{id}](/docs/entities/contacts/get) и [GET /v1/deals/{id}](/docs/entities/deals/get) (а также список, поиск, `include`, пакетные запросы на чтение и ответы на создание, изменение, перенос сделки по стадиям и конвертацию лида) отдавали поле `searchContent`, которого нет ни в описании полей, ни в справочнике: служебную строку полнотекстового индекса Битрикс24 — номер записи, название транслитом, имя ответственного, стадия и даты, склеенные в одну строку. Запросить это поле через `select` или отфильтровать по нему было нельзя (`400 UNKNOWN_SELECT_FIELD`, `400 UNKNOWN_FILTER_FIELD`); сортировка по нему у контактов отклонялась (`400 UNKNOWN_SORT_FIELD`), у сделок — проходила. Поле просто приходило в нагрузку. У [лидов](/docs/entities/leads/get) и [предложений](/docs/entities/quotes/get) такая же строка из ответа уже исключена.

**Стало**

Поле `searchContent` исключено из ответов по контактам и сделкам; ответ на чтение остаётся `200`, остальные поля не меняются. `select` и `filter` по этому полю, как и прежде, не принимаются; сортировка по нему (`sort`, `order` в запросе списка и в теле поиска) у сделок теперь тоже отвечает `400 UNKNOWN_SORT_FIELD`, как у контактов. Параметр `order` внутри подвызова [общего пакетного запроса](/docs/batch) по-прежнему передаётся в Битрикс24 как есть — используйте там `sort`. У [компаний](/docs/entities/companies/get) поле остаётся объявленным и описанным как служебное — там ничего не меняется.

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

Если вы читали `searchContent` из ответов по контактам или сделкам — поля больше нет; полнотекстовый поиск ведите фильтрами по обычным полям (`name`, `lastName`, `title` с `$contains`); ответственный — это `assignedById`, его имя — по этому идентификатору из [GET /v1/users/{id}](/docs/entities/users/get). Если вы сортировали сделки по `searchContent` — сортируйте по объявленному полю, например `id`. Если вы не обращались к этому полю — менять ничего не нужно.

**Затронутые эндпоинты:** [GET /v1/contacts/{id}](/docs/entities/contacts/get), [GET /v1/contacts](/docs/entities/contacts/list), [POST /v1/contacts/search](/docs/entities/contacts/search), [POST /v1/contacts](/docs/entities/contacts/create), [PATCH /v1/contacts/{id}](/docs/entities/contacts/update), [GET /v1/deals/{id}](/docs/entities/deals/get), [GET /v1/deals](/docs/entities/deals/list), [POST /v1/deals/search](/docs/entities/deals/search), [POST /v1/deals](/docs/entities/deals/create), [PATCH /v1/deals/{id}](/docs/entities/deals/update), [POST /v1/deals/{id}/move](/docs/entities/deals/move), [POST /v1/leads/{id}/convert](/docs/entities/leads/convert), [POST /v1/{entity}/batch](/docs/batch), [POST /v1/batch](/docs/batch).

### FIX-0917-22: обновление smart-processes в batch использует документированный идентификатор

**Было**

[POST /v1/batch](/docs/batch) мог отклонить корректное обновление `smart-processes` из-за неверного идентификатора, хотя в запросе был передан документированный публичный `entityId`.

**Стало**

[POST /v1/batch](/docs/batch) корректно обновляет `smart-processes`, когда в запросе передан тот же документированный публичный `entityId`.

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

Клиентам ничего менять не нужно.

### FIX-0917-23: схема фильтров поиска учитывает возможности сущности

**Было**

Сгенерированная схема `POST /v1/{entity}/search` объявляла MongoDB-операторы фильтрации для всех сущностей, включая сущности без поддержки фильтров и сущности только с точным совпадением.

**Стало**

OpenAPI-схема выводит допустимые поля и формы фильтра из возможностей сущности. Для фильтров только с точным совпадением доступны скалярные значения и, где они поддерживаются, непустой массив или `$in`. Неподдерживаемые поля и операторы больше не объявляются допустимыми.

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

Поведение API не изменилось. Генераторы клиентов и средства проверки запросов теперь точнее отражают уже действующие ограничения runtime.

### FIX-0917-24: подсказка при недоступном каталоге пакетов Galaxy

**Было**

При недоступности каталога Node.js или Debian во время сборки Galaxy API возвращал `502 GALAXY_APP_BUILD_FAILED` без категории и подсказки.

**Стало**

Такие отказы возвращают прежний `502 GALAXY_APP_BUILD_FAILED` с категорией `INSTALL_REGISTRY_UNAVAILABLE` и подсказкой для повторного деплоя.

### BC-0917-25: семь полей реквизитов клиента у предложения снова возвращаются и объявлены только для чтения

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

**Было**

С записи BC-0914-21 семь колонок реквизитов клиента `clientTitle`, `clientAddr`, `clientContact`, `clientEmail`, `clientPhone`, `clientTpId`, `clientTpaId` были исключены из ответа [GET /v1/quotes/{id}](/docs/entities/quotes/get) (а также списка, поиска и `include`) как служебные: через API они не заполнялись. Замер прямым вебхуком на тестовых порталах Битрикс24 показал иное: привязка компании и контакта их не заполняет, item-API, через который пишет платформа Вайбкод, их не принимает — но устаревшие методы `crm.quote.add` / `crm.quote.update` (ими пользуются старые интеграции) их сохраняют, и портал возвращает эти значения при чтении. На таких порталах читатель терял реквизиты клиента, а `select=clientTitle` отвечал `400 UNKNOWN_SELECT_FIELD`. Присланное на запись значение принималось с ответом `201` / `200` и подсказкой `UNRECOGNIZED_WRITE_FIELD`, хотя портал его отбрасывал.

**Стало**

Семь полей `client*` объявлены в описании и справочнике как поля только для чтения: они возвращаются в ответах на чтение (ответ остаётся `200`), принимаются в `select`, `filter` и `order`, а присланное на запись значение отклоняется вместо тихой потери: создание и изменение — `400 READONLY_FIELD`, импорт — `400 IMPORT_ITEM_VALIDATION`, пакетный запрос сущности — `400 BATCH_ITEM_VALIDATION`, общий пакетный запрос — `READONLY_FIELD` под вызовом в `data.errors`. У предложений, в которые снимок никто не записывал (в том числе созданных через API), эти поля приходят как `null`, как и остальные незаполненные строковые поля.

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

Если вы отправляли `client*` при создании или изменении предложения — уберите их из тела (в том числе в пачках и импорте): портал их никогда не сохранял, теперь запрос отклоняется. Реквизиты клиента для новых предложений берите из привязанных [контакта](/docs/entities/contacts/get) и [компании](/docs/entities/companies/get) по `contactId` и `companyId`. Если вы читаете предложения, заведённые старыми интеграциями, — снимок реквизитов снова доступен в этих полях.

**Затронутые эндпоинты:** [GET /v1/quotes/{id}](/docs/entities/quotes/get), [GET /v1/quotes](/docs/entities/quotes/list), [POST /v1/quotes/search](/docs/entities/quotes/search), [GET /v1/quotes/fields](/docs/entities/quotes/fields), [POST /v1/quotes](/docs/entities/quotes/create), [PATCH /v1/quotes/{id}](/docs/entities/quotes/update), [POST /v1/{entity}/import](/docs/import), [POST /v1/{entity}/batch](/docs/batch), [POST /v1/batch](/docs/batch).

### NEW-0917-26: новое значение PARKED у состояния места Коворка

Поле состояния места теперь может принимать значение `PARKED` вдобавок к прежним — и в `subscription.state` ответа `GET /v1/cowork/state`, и в `state` ответа `GET /v1/cowork/me`. Это одна и та же колонка подписки, отданная двумя ручками. Место получает его, когда владелец перестал быть активным участником портала: за такое место не списывается, доступа оно не даёт, оплаченный срок и тариф на нём сохраняются, а администратор компании может отдать его другому сотруднику.

Стоянка не бессрочна, и это важно знать тому, кто на неё опирается: когда оплаченный срок такого места заканчивается, оно уходит в `PAUSED` и дальше живёт по прежним правилам приостановленного места. То есть `PARKED` означает «оплаченный остаток ещё есть», а не «место будет ждать вечно».

Прежние значения и их смысл не менялись, старые запросы работают как работали. Клиенту достаточно трактовать незнакомое значение как «доступа сейчас нет» — ровно так же, как `PAUSED` и `CANCELLED`.

### BC-0917-27: Однозначный выбор записи и страницы при получении CRM-документов

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

**Было**

[GET /v1/crm-documents](/docs/entities/documents/crm-list) принимал повторённый `entityId` с ответом HTTP 200 и возвращал документы записи, указанной последней. Одновременная передача `entityId` и `entityID` также принималась, а исход зависел от порядка имён. Скобочные формы — `entityId[]=7`, `entityId[0]=7`, `entityId[a][b][c]=7` — выглядели как непереданный параметр: фильтр по записи молча исчезал, и ответ приходил по всем записям указанного типа. Параметр `start` вёл себя так же: повтор давал последнее значение, а скобочная форма молча возвращала первую страницу вместо запрошенной.

**Стало**

Повтор параметра, оба имени вместе и скобочные формы возвращают HTTP 400 `INVALID_ENTITY_ID` для выбора записи и HTTP 400 `INVALID_START` для выбора страницы, даже если значения совпадают. Отказ приходит до обращения к Битрикс24. Одиночное положительное целое под именем `entityId` или `entityID`, одиночный `start` и пустое значение `entityId=` работают как прежде. Параметры `select` и `order` этим изменением не затронуты — их поведение не менялось.

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

Передавайте `entityId` (или `entityID`) и `start` не более одного раза, каждый одним значением. Не используйте массивы и объекты для выбора записи и страницы. Кодируйте пользовательский ввод при построении строки запроса: незакодированное значение может внести в запрос любой дополнительный параметр, включая те, что проверку однозначности проходят. Если идентификатор записи может оказаться незаполненным, опускайте параметр целиком вместо подстановки пустого значения — пустое значение означает выборку по всем записям типа.

### BC-0917-28: карточка шаблонов документов объявляет оба права для примера с fileId

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

**Было**

Карточка [POST /v1/doc-templates](/docs/entities/doc-templates/create) показывала пример создания из файла Диска через `fileId`, но в машинном поле `requiredScope` называла только `documentgenerator`. Клиент, который брал права из `api-reference.json`, мог выпустить ключ без `disk` и получить `403 SCOPE_DENIED` при повторении опубликованного примера.

**Стало**

Runtime не меняется: тело с base64-полем `file` по-прежнему требует только `documentgenerator`, а форма `fileId` требует `documentgenerator` и `disk`. Для этой карточки `api-reference.json` больше не публикует неполный `requiredScope`; вместо него приходит `requiredScopes: ["documentgenerator","disk"]`, а badges карточки показывают оба права.

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

Если вы читаете `api-reference.json`, сначала проверяйте `requiredScopes` и запрашивайте весь список прав из него. Используйте `requiredScope` только когда `requiredScopes` отсутствует.

### FIX-0917-29: параметр в скобочной форме получает 400 вместо 500

**Было**

Если параметр пришёл не строкой, а массивом или объектом (`?portalId[]=x` в query или массив в JSON-теле), `GET /v1/keys`, `POST /v1/keys`, `POST /v1/feedback` и `PATCH /v1/feedback/{id}` отвечали `500` с кодом внутренней ошибки. `GET /v1/feedback` под management-ключом отвечал `500` на `?portalId[]=x`.

**Стало**

Такой запрос получает штатный `400` с тем же кодом, что и отсутствующее или неверное поле: `MISSING_PORTAL_ID` для `portalId`, `VALIDATION_ERROR` для `category` и `resolution`. Необязательный фильтр `portalId` у `GET /v1/feedback` в такой форме не применяется: ответ `200`, как без фильтра. Для корректных строковых значений ответ не изменился, успешный HTTP 200 сохраняется.
