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

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

### FIX-0828-2: поздняя загрузка по действующей ссылке сохраняет объект

**Было**

Если загрузка по подписанной ссылке начиналась позднее часа после её выдачи, служебная бронь могла быть удалена раньше окончания срока ссылки. Последующее завершение отвечало `404`, а загруженные байты оставались без объекта в API.

**Стало**

Пустая бронь сохраняется до максимального срока подписанной ссылки. Ответ успешного завершения не изменился; поздняя загрузка по ещё действующей ссылке остаётся связанной с объектом API.

### FIX-0828-3: правила листания в GET /v1/guide приходят одним блоком paginationCanon

**Было**

Правила листания и подсчёта записей повторялись внутри каждой сущности ответа. Поля `operations.search.paginationStability` и описание параметра `operations.search.params.withTotal` несли один и тот же текст у всех сущностей.

**Стало**

Тот же текст приходит один раз, блоком `paginationCanon` на верхнем уровне ответа. Формулировки перенесены дословно, ни одно правило не переписано и не сокращено. Одноимённые поля внутри сущностей сохранены и содержат указатель на соответствующий пункт `paginationCanon`.

Исключение — `operations.search.paginationStability.counting`. Он по-прежнему приходит у каждой сущности со своим текстом, потому что способ узнать точное количество зависит от того, доступна ли этой сущности операция агрегации.

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

Вызовы работают как прежде, ни одно поле ответа не удалено. Если ваш код читал текст правил из полей сущности, возьмите его из блока `paginationCanon`. Ответ на полном наборе скоупов стал меньше примерно на 37 процентов.

### FIX-0828-4: filter и sort комментариев задач принимают camelCase-имена полей

**Было**

[GET /v1/tasks/:taskId/comments](/docs/entities/task-comments/list) принимал в `filter` и `sort` только сырые имена полей Битрикс24. Имена `authorId` и `createdAt`, в которых те же поля приходят в ответе, давали `400 UNKNOWN_FILTER_FIELD` или `400 INVALID_SORT_FIELD`.

**Стало**

Параметры `filter` и `sort` принимают camelCase-имена наравне с сырыми: `id`, `authorId`, `authorName`, `createdAt`, а `sort` дополнительно `authorEmail`. Ограничения по типу карточки не изменились: фильтр по `authorName` и сортировка по `authorName` или `authorEmail` по-прежнему принимаются только на старой карточке, на новой отвечают `400`. Если `filter` называет одно поле дважды в разных написаниях имени или знака равенства, приходит `400 INVALID_FILTER`. Неизвестные имена по-прежнему возвращают `400`.

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

Для фильтрации и сортировки можно использовать `authorId` и `createdAt` — те же имена, в которых эти поля приходят в ответе. Менять существующие запросы с сырыми именами не требуется.

### BC-0828-5: Исход deploy можно подтвердить после обрыва связи

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

**Было**

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

**Стало**

`POST /v1/infra/servers/:id/deploy` сохраняет наблюдаемый исход standalone- и galaxy-выкладки, а `GET /v1/infra/servers/:id/operations` возвращает последние операции текущего API-ключа даже когда терминальный ответ с `operationId` был потерян. При `GATEWAY_UNREACHABLE`, `GATEWAY_CONNECTION_TERMINATED`, `GATEWAY_STREAM_ERROR*`, `GATEWAY_TIMEOUT*`, `TUNNEL_NOT_FOUND` и post-drop обрыве Galaxy исход сохраняется как `unknown`, а `error.hint` требует сначала сверить операцию, сервер и логи. Для standalone это одинаково работает и когда транспорт бросил исключение, и когда шлюз прислал timeout/error кадром внутри exec или upload/download; такой неподтверждённый исход несёт `error.retryable: false` и не запускает внутренний повтор. При подтверждённом `EXEC_BUSY` standalone-сервер можно восстановить через `/unstick` только когда выполняющейся операции уже нет; для общего Galaxy-хоста tenant recovery не поддерживается — нужно подождать и при устойчивом отказе обратиться в поддержку.

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

При `error.retryable: false` не отправляйте тот же deploy повторно: сначала прочитайте `GET /v1/infra/servers/:id/operations`, состояние сервера и логи. Повтор допустим только после устойчивого исхода `failed`, когда подсказка ответа явно разрешает тот же запрос. Не удаляйте и не пересоздавайте слот при исходе `unknown`.

### FIX-0828-6: создание комментария задачи возвращает правильный числовой ID

**Было**

На новой карточке задачи [POST /v1/tasks/:taskId/comments](/docs/entities/task-comments/create) и [POST /v1/tasks/:taskId/comments/batch](/docs/entities/task-comments/comments-batch) с `action: create` могли вернуть `id: null` для найденного сообщения, если его числовые идентификаторы пришли строками. При нескольких совпадениях строковое сравнение могло выбрать неверный ID.

**Стало**

Оба эндпоинта возвращают числовой `id` найденного сообщения и сравнивают совпавшие ID как числа. Ответ одиночного POST по-прежнему остаётся HTTP 201. Ответ batch остаётся HTTP 200, а успешно созданный элемент — `success: true`.

Если поиск не находит сообщение или не может определить его однозначно, `id: null` остаётся допустимым успешным результатом: комментарий уже создан, и повторный запрос может создать дубликат.

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

Менять клиент не требуется. Продолжайте поддерживать `number | null` и не считайте `null` признаком неудачного создания.

### BC-0828-7: незавершённые публичные загрузки больше не доступны анонимно

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

**Было**

Для объекта `PUBLIC` в состоянии `PENDING` запрос [GET /v1/public-storage/{portalId}/{objectId}](/docs/storage/objects/public-get) отвечал `302`, а [HEAD /v1/public-storage/{portalId}/{objectId}](/docs/storage/objects/public-head) — `200`. Файл становился доступен без аутентификации до завершения загрузки.

**Стало**

Оба запроса отвечают `404`, пока загрузка остаётся в состоянии `PENDING`. Анонимный доступ открывается только для объекта `PUBLIC` с завершённой загрузкой.

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

После загрузки файла вызовите [POST /v1/storage/objects/complete](/docs/storage/upload/complete) и публикуйте анонимную ссылку только после успешного ответа.

### FIX-0828-8: MCP отдаёт содержимое файла Диска вместо ошибки сети

**Было**

Действие `download` инструмента `manage_file` разбирало ответ [GET /v1/files/:fileId/download](/docs/entities/files/download) как служебный JSON, хотя этот запрос отдаёт байты файла. На любом реальном файле агент получал `success: false` с кодом `NETWORK_ERROR` и текстом `Unexpected token …` — то есть первые байты уже скачанного файла выдавались за сбой связи. Рабочего ответа у действия не было.

**Стало**

Действие возвращает `content` с содержимым файла в base64, а также `contentType`, `size` и `filename` — симметрично тому, как `upload` принимает `content`. Файлы больше 10 МиБ отклоняются кодом `FILE_TOO_LARGE`, и текст ошибки называет способ получить такой файл: запрос к тому же адресу с тем же ключом API. Ответы с ошибкой остаются обычным конвертом платформы: отсутствующий файл — это `ENTITY_NOT_FOUND`, а не `NETWORK_ERROR`. Универсальный `call_api`, который умеет читать только JSON-конверты, теперь заранее отклоняет известные не-JSON маршруты кодом `NON_JSON_RESPONSE_UNSUPPORTED`, не выполняя заведомо неверный запрос. Так же заранее он отклоняет кодом `REDIRECT_ROUTE_UNSUPPORTED` браузерные точки входа потоков OAuth и подключения: они отвечают перенаправлением, и запрос к ним уходил вместе с ключом API, а затем следовал за перенаправлением на адрес, который выбирает не платформа. Путь приводится к каноническому виду до проверки, поэтому его написание через `..` больше не обходит отказ, а полный адрес вместо пути отклоняется кодом `INVALID_PATH`. У действия `download` идентификатор файла теперь обязан быть целым положительным числом: дробное значение приводило к содержимому другого файла, выданному как успешный ответ. Сам REST-запрос по-прежнему отдаёт бинарные данные, поэтому обычные клиенты работают как раньше.

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

**Было**

Одновременные вызовы [POST /v1/storage/objects/upload](/docs/storage/upload/direct) для одного физического адреса приложения могли перемешать содержимое файла и его метаданные.

**Стало**

Прямые загрузки пути A с ключом приложения выполняются по очереди. После получения очереди ответ по-прежнему остаётся HTTP 200. Если безопасное право записи не подтверждено в пределах 60 секунд или лимита конкурентности, запрос получает `409 STORAGE_KEY_CONFLICT` до записи в объектное хранилище. Повторите весь запрос.

Гарантия относится только к пути A с ключом приложения. Она не расширяет поведение личных ключей (`appId = null`) и путей B/C.

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

Формат успешного ответа не изменился. При `STORAGE_KEY_CONFLICT` повторяйте исходный запрос целиком.

### FIX-0828-10: спецификация ответов создания и обновления отражает идентификатор

**Было**

Спецификация OpenAPI и примеры ответов для операций создания и обновления обещали полную сущность там, где API возвращает только `data.id`.

**Стало**

OpenAPI и документация описывают фактический ответ с `data.id`. Статусы HTTP 201 для создания и HTTP 200 для обновления, а также поведение API при выполнении запросов не изменились.

**Затронутые эндпоинты:** [POST /v1/bizproc-activities](/docs/entities/bizproc-activities/create), [PATCH /v1/bizproc-activities/:code](/docs/entities/bizproc-activities/update), [POST /v1/bizproc-robots](/docs/entities/bizproc-robots/create), [PATCH /v1/bizproc-robots/:code](/docs/entities/bizproc-robots/update), [POST /v1/bizproc-templates](/docs/entities/bizproc-templates/create), [PATCH /v1/bizproc-templates/:id](/docs/entities/bizproc-templates/update), [POST /v1/calendar-sections](/docs/entities/calendar-sections/create), [PATCH /v1/calendar-sections/:id](/docs/entities/calendar-sections/update), [POST /v1/telephony-lines](/docs/telephony/lines/create), [PATCH /v1/telephony-lines/:number](/docs/telephony/lines/update).

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

Существующие запросы менять не нужно. Повторно сгенерированные клиенты теперь видят фактическую форму успешного ответа.

### FIX-0828-11: выполнение команд в galaxy-приложении больше не отклоняется до отправки

**Было**

`POST /v1/infra/servers/{id}/exec` для galaxy-приложения отвечал `502` с кодом `EXEC_FAILED` и сообщением о неподдерживаемом безопасном выполнении в контейнере. Отказ приходил на любом приложении и не зависел ни от команды, ни от состояния контейнера: до контейнера запрос не доходил вовсе.

**Стало**

Команда выполняется в контейнере приложения, и успешный ответ остаётся `HTTP 200` с полями `exitCode`, `stdout`, `stderr` и `duration` — той же формы, что на остальных серверах. Код `CONTAINER_NOT_READY` для остановленного или отсутствующего контейнера платформа возвращает там, где может подтвердить это состояние; где не может — причина приезжает в `stderr` результата, а сам ответ по-прежнему `HTTP 200`.

### FIX-0828-12: Устойчивее установка Node.js 20 в шаблонах рантайма

Node.js 20 в шаблонах `node20*` теперь устанавливается устойчивее при проблемах с внешней сетью.

**Было**

Шаблон без ограничения выполнял установку через NodeSource. При сетевой ошибке мог быть установлен Node.js 18, после чего деплой завершался только общей ошибкой проверки версии.

**Стало**

Установка NodeSource выполняется с ограниченными сетевыми попытками и проверкой целостности. При неуспехе его источник временно изолируется, существовавшая конфигурация сохраняется, и используется проверенный резервный архив Node.js 20; идентификаторы рантаймов и API деплоя не меняются.

### FIX-0828-13: ZIP-выкладка больше не просит сменить формат архива, если сервер занят другой командой

**Было**

Пока на сервере выполнялась другая команда, [POST /v1/infra/servers/:id/upload](/docs/infra/deploy/upload) с zip и `extract: true` и выкладка galaxy-приложения из zip отвечали отказом установки `unzip` (`UNZIP_PREFLIGHT_FAILED` / ошибка сборки) и советовали пересобрать архив в `.tar.gz`.

**Стало**

Кратковременная занятость канала команд сервер повторяет сам. Если канал так и не освободился, ответ — `409` с кодом `EXEC_BUSY` (на выкладке galaxy-приложения — `GALAXY_APP_BUSY`), `retryable: true` и заголовком `Retry-After`. Формат архива менять не нужно. Настоящий отказ установить `unzip` по-прежнему приходит как `502 UNZIP_PREFLIGHT_FAILED` и совет использовать `.tar.gz`.

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

Повторяйте запрос по `Retry-After`. Не пересобирайте архив из-за `EXEC_BUSY` / `GALAXY_APP_BUSY`. Ветка `UNZIP_PREFLIGHT_FAILED` не менялась.

### FIX-0828-15: ключ владельца в самоудалении больше не вызывает методы Битрикс24 через V1

**Было**

Ключ владельца портала с запланированным самоудалением мог продолжать вызывать часть методов Битрикс24 через V1. Ограничение действовало не для всех сгенерированных и рукописных обёрток, методов REST 3.0, прямых вызовов и пакетных запросов.

**Стало**

Все V1-методы, которые по APP/OAuth-ключу портала отправляют запросы в Битрикс24, возвращают `503 user_self_deletion_pending` и `Retry-After` до отправки запроса. Это относится, в частности, к [GET /v1/lists](/docs/lists/lists/list), [GET /v1/calendar/settings](/docs/calendar/settings), [GET /v1/chats/recent](/docs/chats/discovery/recent), [GET /v1/workday/status](/docs/workday/status), `GET /v1/applications`, `POST /v1/apps`, методам обновления тарифа и активации trial, а также `POST /v1/batch`.

Для READONLY-ключа владельца в самоудалении этот 503 имеет приоритет над `WRITE_BLOCKED_READONLY_KEY`. Полностью аутентифицированный ключ активного владельца по-прежнему получает READONLY-запрет; более ранние ошибки аутентификации и состояния аккаунта сохраняют свои коды.

Мета-методы V1, включая обычный `GET /v1/me` без `refresh=tariff`, MANAGEMENT control-plane и методы, работающие только с данными платформы Вайбкод, не замораживаются этим изменением.

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

Менять интеграцию не требуется. При ответе 503 повторяйте запрос не раньше значения `Retry-After`.

### BC-0828-16: include требует право на связанную сущность

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

**Было**

Параметр `include` не проверял, есть ли у ключа право на ту сущность, которую он достаёт. Право проверялось только у сущности, которую вы читаете. Поэтому ключ с одним правом `tasks` через `GET /v1/tasks/{id}?include=responsible` получал полную карточку сотрудника, хотя на `/v1/users` тот же ключ отвечал отказом.

**Стало**

Право проверяется у обеих сторон связи. Не хватает права на связанную сущность — запрос отклоняется с кодом `SCOPE_DENIED` и HTTP 403, в тексте названо недостающее право и связь, из-за которой оно понадобилось.

Затронуты связи, у которых стороны требуют разных прав: `responsible` и `creator` у задач и `owner` у рабочих групп требуют `user`, `catalog` у разделов товаров требует `catalog`. Связи внутри одного права — например `preset` у реквизитов или `parentSection` у разделов товаров — работают как раньше.

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

**Затронутые эндпоинты:** любой эндпоинт, принимающий `include` — чтение по идентификатору, выдача списка и `POST /search`. Общее описание механизма — [включение связанных записей](/docs/includes).

### NEW-0828-17: include для реквизитов, рабочих групп и разделов товаров

Три справочные сущности получили связи, поэтому параметр `include` на них теперь объявлен и работает. У реквизитов появилась связь `preset` — шаблон реквизитов, определяющий набор полей. У рабочих групп — `owner`, карточка сотрудника-владельца группы. У разделов товаров сразу две: `catalog` — торговый каталог, к которому относится раздел, и `parentSection` — родительский раздел, он приходит пустым у раздела верхнего уровня.

Связанная запись, как и раньше, лежит в поле `_included` рядом с самой записью, работает на выдаче списка, на чтении по идентификатору и в теле `POST /search`. Ограничения общие для механизма: не больше трёх связей на запрос, а на выборке больше 200 записей связи не разрешаются, и ответ помечается `includeSkipped`.

Остальные справочники по-прежнему `include` не объявляют, и это не упущение. Стадии направления сделок Битрикс24 отдаёт строковым кодом, а не идентификатором записи справочника, поэтому связать их с элементом справочника нечем. У элемента справочника номер воронки имеет смысл только в паре с типом объекта CRM, и один и тот же номер принадлежит одновременно направлению сделок и воронке смарт-процесса. У шаблона документа идентификатор файла не адресуется методами Диска, а нумератор отдельной сущностью в API не представлен. Список участников рабочей группы доступен отдельной ручкой, а не через `include`.

**Затронутые эндпоинты:** [GET /v1/requisites](/docs/entities/requisites), [GET /v1/workgroups](/docs/entities/workgroups), [GET /v1/product-sections](/docs/entities/product-sections), их чтение по идентификатору и `POST /search`. Общее описание механизма — [включение связанных записей](/docs/includes).

### FIX-0828-18: select вместе с include больше не обнуляет связь на списке и в поиске

**Было**

Если в одном запросе к списку или к `POST /search` стояли и `select`, и `include`, а поле-ссылка в `select` названо не было, связанная запись приходила пустой. Например, `GET /v1/deals?select=id,title&include=company` отдавал `_included.company` пустым, хотя без `select` та же связь разрешалась. На чтении по идентификатору проблемы не было.

**Стало**

Связь разрешается до того, как ответ урезается до запрошенных полей, а поле-ссылка добирается во внутренний запрос само. Публичный ответ при этом не расширяется: в нём остаются только названные вами поля плюс блок `_included`.

**Затронутые эндпоинты:** выдача списка и `POST /search` у любой сущности со связями. Общее описание механизма — [включение связанных записей](/docs/includes).

### NEW-0828-19: профиль текущего сотрудника появился в спецификации и справочнике

Метод [GET /v1/users/me](/docs/entities/users/me) работал и раньше, но не был объявлен в `GET /v1/openapi.json`, поэтому не попадал ни в справочник API, ни в карту эндпоинтов. Клиент, сверявшийся со спецификацией, не находил метод и делал вывод, что его нет.

Теперь операция описана: скоуп `user`, форма ответа как у `GET /v1/users/:id` плюс трёхзначное поле `isAdmin`. Само поведение метода не менялось.

Значение `isAdmin` равно `null`, когда проверку выполнить не удалось. Профиль при этом возвращается целиком, поэтому права проверяйте условием `isAdmin === true`, а не отрицанием.

### FIX-0828-20: `topUpAvailable` у коробочного аккаунта отражает доступность именно коробочной кассы

**Было**

`GET /v1/cowork/subscription/preview` отвечал коробочному аккаунту `topUpAvailable: true`, если в его регионе была открыта продажа вообще. Продажа для коробочных аккаунтов идёт через отдельную кассу и открывается отдельно, поэтому признак мог обещать пополнение там, где заказ следующим шагом отклонялся.

**Стало**

Признак считается по доступности коробочной продажи в регионе аккаунта. Ответ остаётся HTTP 200, набор полей прежний, у облачных аккаунтов значение не изменилось. Пока коробочная продажа закрыта, приходит `topUpAvailable: false` и `currency: null`, а попытка начать пополнение отвечает кодом `BOX_TOPUP_NOT_AVAILABLE`.

### FIX-0828-21: ключ авторизации получает права, которые портал реально выдаёт

**Было**

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

**Стало**

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

### FIX-0828-22: фотографию сотрудника снова можно записать через API

**Было**
Поле `personalPhoto` объявлено строкой, поэтому любой массив или объект в нём отклонялся с
`400 INVALID_PARAMS` — а массив `[имя файла, base64]` и есть единственная форма, которую
принимает Битрикс24. На сущностных маршрутах записать фотографию было нельзя ни одной формой:
строка с base64 и ссылка возвращали `422`, пакетные маршруты — `400`. Проходила она только через
обёртку `POST /v1/users/invite`.

**Стало**
`personalPhoto` принимает на записи файл прямо в теле: массив ровно из двух непустых строк —
имя файла и содержимое в base64 — на `POST /v1/users` и `PATCH /v1/users/:id`. Чтение поля не
изменилось, оно по-прежнему отдаёт URL фотографии. Прочие формы отклоняются как раньше, и это
намеренно: вложенная `{"personalPhoto": {"fileData": [...]}}` принимается порталом с успехом,
но при этом снимает фотографию. Пакетные маршруты фотографию отклоняют: подзапрос пакета едет
строкой запроса под общим потолком тела, и за пределом длины значение обрезалось бы,
отвечая при этом успехом. Обёртка `POST /v1/users/invite` эту же форму принимает — она переводит имя
поля и отдельно проверяет пару, — но её потолок тела остался общим (1 МиБ против 40 МиБ у
одиночных маршрутов), поэтому настоящую фотографию отправляйте через `POST /v1/users` или
`PATCH /v1/users/:id`.
