Для AI-агентов: markdown этой страницы — /docs-content/changelog/2026-08-12.md индекс документации — /llms.txt
Изменения API: 12 августа 2026
← Журнал изменений · Август 2026
NEW-0812-1: nextPollAfterMs — платформа может попросить опрашивать события реже
GET /v1/bots/{botId}/events отдаёт новое необязательное поле nextPollAfterMs — сколько миллисекунд стоит подождать перед следующим запросом. Поле приходит только боту, которому события доставляются вебхуком (eventMode равен webhook): очередь Event.get у такого бота пуста по устройству Битрикс24, поэтому частый опрос не приносит ничего. Боту в режиме fetch поле не приходит вовсе — опрашивайте с прежней частотой.
Отсутствие поля означает «опрашивайте как раньше», поэтому пустым или нулевым оно не приходит. Поле необязательное: клиент, который его игнорирует, продолжает работать без изменений. Если в ответе есть непрочитанный остаток (hasMore равен true), дочитайте очередь не дожидаясь присланной паузы — она относится к следующему пустому опросу.
FIX-0812-2: `touSavedPct` теперь доля месячного объёма подписки, а не доля расхода
Было
GET /v1/cowork/state считал touSavedPct как долю недисконтированного счёта — сэкономлено / (сэкономлено + израсходовано). У такой дроби числитель и знаменатель обязаны покрывать одно и то же окно, а счётчик экономии начал работать посреди уже идущего расчётного периода. Поэтому поле намеренно возвращало null до конца первого периода — на практике почти у всех подписок.
Стало
Знаменатель — месячный объём подписки, тот же, что у полосы месячной квоты. Значение читается с первого дня и не подавляется: null приходит, только когда за текущий период ещё ничего не сэкономлено. Верхняя граница — 100.
Две доли теперь читаются рядом: «израсходовано N% месячного объёма» и «выгодные часы вернули M% месячного объёма». Тип и диапазон поля не изменились, действий на стороне клиента не требуется.
NEW-0812-3: Порт приложения закрепляется за сервером и переживает пробуждение
Было
GET /v1/infra/servers/:id отдавал только localPort, а он равен 3000 у всех серверов по умолчанию — понять по ответу, ходит агент строго на этот порт или выбирает его сам, было нельзя. Порт, заданный через PATCH /v1/infra/servers/:id/port, жил только в памяти агента: после пробуждения сервера или починки туннеля выбор шёл заново, и публичный адрес мог начать отдавать соседний процесс.
Стало
Успешная выкладка, прошедшая проверку работоспособности, записывает порт в настройки агента на машине и помечает сервер закреплённым. Ответ GET /v1/infra/servers/:id несёт portPinned: при true агент проксирует строго на localPort и не переизбирает порт ни после пробуждения, ни после починки; при false порт по-прежнему определяется автоматически по слушающим сокетам.
У закреплённого сервера PATCH /v1/infra/servers/:id/port применяет порт перезаписью настроек агента и его перезапуском, а не командой на лету. Следствия для клиента: 409 PORT_NOT_APPLIED на таком сервере не приходит никогда; туннель на несколько секунд обрывается; verified: true означает, что настройки несут запрошенный порт и агент вернулся на связь, а verified: false — что порт записан, но возвращения агента дождаться не удалось (это не отказ, порт применится). port: 0 снимает закрепление. Добавлены два кода отказа: 502 AGENT_CONFIG_WRITE_FAILED — машина отклонила запись настроек, 502 GATEWAY_ERROR — команда не дошла до машины.
В data.steps[] ответа POST /v1/infra/servers/:id/deploy появился шаг port_pin — исход записи порта в настройки агента. Он не влияет на успех выкладки: warning на нём означает, что приложение выложено и работает, но порт не закрепился.
BC-0812-4: context в обращениях должен быть JSON-объектом
Поддержка старого формата до: не предусмотрена
Было
POST /v1/feedback принимал context любого JSON-типа, включая строку, массив, число, boolean и null.
Стало
context остался необязательным. Если поле передано, его верхний уровень должен быть JSON-объектом, включая {}. Строка, массив, число, boolean и null получают 400 VALIDATION_ERROR; обращение не создаётся.
Что делать интеграторам
Передавайте объект: {"context":{"endpoint":"/v1/feedback"}}. Отправители, которые передавали строку, массив, число, boolean или null, должны перейти на объект: старый формат перестаёт приниматься с этим релизом.
NEW-0812-5: публикация приложения принимает исходники с указанного сервера
POST /v1/apps/:id/publish принимает необязательное поле sourceServerId (или заголовок X-Source-Server) — идентификатор сервера, исходники которого публикуются. Это закрывает случай, когда автосейв деплоя отвечал autoSaved: true, а следующая публикация всё равно возвращала 409 SNAPSHOT_REQUIRED: если сервер принадлежит не OAuth-ключу публикуемого приложения (например, деплой шёл под личным ключом vibe_api_), снапшот сохраняется у сервера, а не у приложения, и проверка публикации его не видела. Теперь достаточно назвать тот же сервер, по которому вы вызывали деплой; повторно сохранять исходники через POST /v1/apps/:id/sources не нужно.
Сервер платформа не подбирает — идентификатор передаёте вы. Права на сервер проверяются отдельно от прав на приложение: подходит ключ-владелец сервера, личный ключ того же пользователя или администратор портала. Сервера нет, он из другого портала или удалён — 404 SERVER_NOT_FOUND; прав нет — 403 NOT_AUTHORIZED; заголовок X-Source-Server не в форме UUID или передан дважды — 400 VALIDATION_ERROR. Поле тела приоритетнее заголовка: когда sourceServerId пришёл в теле, заголовок не читается и не проверяется. Вместе с sourceServerId поле sourceVersionId означает версию этого сервера: нумерация версий у сервера своя.
Тег published и отметка о публикации ставятся только на версию самого приложения. Версию сервера публикация не помечает: те же поля у неё хранят историю деплоя, которую отдаёт GET /v1/infra/servers/:id/sources, а тег означает бессрочное хранение и запрет на удаление. Отсюда следствие, о котором стоит знать: у опубликованной версии сервера бессрочного хранения нет — она живёт по обычным правилам очистки и может быть удалена, пока приложение остаётся опубликованным. Поэтому в warnings приходит строка с готовым PATCH /v1/infra/servers/:id/sources/vN, которым версию можно пометить самому; если версия уже помечена, строки не будет. Если хранилище исходников для портала отключено, переданный sourceServerId не используется вовсе — публикация проходит, а в warnings приходит строка о том, что селектор не пригодился.
Без sourceServerId поведение прежнее — проверка ищет свежий снапшот приложения. У отказа 409 SNAPSHOT_REQUIRED появилось поле hint.reason с четырьмя значениями (app_snapshot_missing, app_snapshot_stale, server_snapshot_missing, server_snapshot_stale), а на пути без указанного сервера — блок hint.serverKeyedSources, который объясняет, как опубликовать исходники с сервера. Текст сообщения этого отказа переписан: раньше он начинался со слова «Deploy» и предлагал единственный выход через POST /v1/apps/:id/sources.
Подробности — Опубликовать приложение и Хранилище исходников.
NEW-0812-6: товарные позиции сделки: в ответе появился признак усечения
Было
GET /v1/{entity}/{id}/products (сделки, лиды, счёта, предложения, элементы смарт-процессов) отдавал
только первую страницу товарных позиций — Bitrix24 отдаёт их порциями по 50 — и не сообщал об этом
никак. Ответ состоял из success и data, поля meta не было вовсе, поэтому у элемента с 125
позициями и у элемента с 50 позициями ответы выглядели одинаково, и приложение теряло строки, не имея
возможности это заметить. Параметры limit и offset на этом маршруте не действовали.
Стало
Ответ дополнен блоком meta: meta.total — сколько товарных позиций у элемента всего (по данным
Bitrix24), meta.hasMore — есть ли строки за пределами отданной страницы. Поля success и data не
изменились, поэтому клиент, который читает только их, продолжает работать как раньше.
Заодно на GET /v1/deals/:id/products и его аналогах у лидов,
счетов, предложений и элементов смарт-процессов начали работать limit и offset. limit до 5000
отдаёт запрошенное число позиций за один вызов, значения выше 5000 приводятся к 5000. offset
считается по строкам, а не по страницам: offset=7 начинает выдачу с восьмой позиции. meta.hasMore
верна для любого запрошенного окна, а не только для первого. Вызов без limit и offset ведёт себя
ровно как раньше. Просьба limit=0 размером страницы не считается: применяется значение по умолчанию,
а в meta.warnings появляется запись с кодом LIMIT_ZERO_IGNORED — так же, как на списочных
маршрутах. То же поведение описано в knownIssues ответа GET /v1/guide.
NEW-0812-7: машинная схема описывает работающие методы приложений, ИИ и телефонии
Восемнадцать операций, которые работали и были описаны на страницах документации, отсутствовали в машинной схеме GET /v1/openapi.json. Клиент, который сверялся со схемой, не находил метод и делал вывод, что метода не существует. Теперь схема их описывает: семейство приложений (GET /v1/apps, создание, чтение, изменение, удаление, публикация и снятие с публикации, а также перепривязка OAuth), расход и квота ИИ (GET /v1/ai/usage, GET /v1/ai/quota), расписание выгодных часов (GET /v1/off-peak), ИИ-итоги завершённых звонков (POST /v1/calls/followups/list, GET /v1/calls/followups/:callId) и список линий (GET /v1/voximplant-lines).
Сами методы не новые и не менялись — ни адреса, ни параметры, ни формы ответов. Новое только описание, поэтому менять в интеграции нечего.
Четыре метода ИИ, совместимых с OpenAI, отвечают по двум адресам сразу: /v1/models и /v1/ai/models, и так же для завершений чата, векторных представлений и расшифровки аудио. В схеме были только короткие адреса. Теперь описаны оба, и у формы с приставкой /v1/ai/ прямо указано, что она устаревшая: такие ответы несут заголовки Deprecation: true и X-Deprecated-Use с каноническим адресом. Вызовы по ним работают по-прежнему, но переходить стоит на короткий адрес. Исключение — GET /v1/ai/usage и GET /v1/ai/quota: у них короткой формы нет, они сами канонические и заголовков депрекации не несут.
Схема по-прежнему не описывает адреса-подсказки, которые существуют только чтобы вернуть ошибку «неверный путь» со списком настоящих адресов, и карточку одной модели: её идентификатор сам содержит косую черту, и параметр пути в OpenAPI такое значение описать не может.
FIX-0812-8: список планов бесплатного тарифа отдаётся публичным идентификатором
Было
На международной поверхности allowedPlans в теле 402 PLAN_NOT_ALLOWED_ON_TRIAL и в
capabilities.servers.create.limits (GET /v1/me) нёс внутренний идентификатор плана —
не тот, которым тот же план назван в каталоге GET /v1/infra/providers/:providerId/plans. Тот же
идентификатор подставлялся в кавычках в человекочитаемый note, поэтому ИИ-ассистент
показывал его пользователю дословно, а рядом стоящее requestedPlan могло вернуть план,
который выбрала сама платформа (создание агента), а не клиент.
Стало
Оба поля и текст note несут тот же нейтральный идентификатор, который отдаёт каталог
планов, поэтому ответ гейта и каталог наконец согласованы. Идентификаторы прежнего
каталога по-прежнему принимаются на входе создания сервера, так что клиент, отправляющий
ранее полученное значение, продолжает работать без изменений.
FIX-0812-9: Исправлен отбор по пользовательским полям реквизитов
Было
GET /v1/requisites с фильтром filter[ufCrm_1698325419] передавал имя пользовательского поля без изменения. Bitrix24 игнорировал такой ключ, поэтому успешный ответ не сужал список.
Стало
Оба написания одного поля — filter[UF_CRM_1698325419] и filter[ufCrm_1698325419] — принимаются и отбирают одинаково. Написание, которого Битрикс24 не даёт (например filter[ufCrm_taxId]), по-прежнему не применяется. Ошибка UNKNOWN_FILTER_FIELD для пользовательских полей не появилась.
FIX-0812-10: неподтверждённая активация демо Маркетплейса отдаёт статус pending, а не activated
Было
POST /v1/portals/{id}/activate-market-trial присылал два успешных статуса: activated и already_active. Когда Битрикс24 включал демо, но подтверждения по нему не отдавал, вызов всё равно отвечал activated с полем trialEndsAt — то есть сообщал о завершённой активации, которую никто не подтвердил.
Стало
У неподтверждённого исхода появился свой статус — pending, без trialEndsAt. Он означает, что демо у Битрикс24 уже включено и повторно включить его нельзя, но подтверждения мы пока не получили. Статусы activated и already_active теперь приходят только на подтверждённые исходы.
Влияние на интеграторов
Проверьте ветку data.status === 'activated': часть удачных активаций приходит в неё больше не будет. Обработайте pending как выполненный вызов — перечитайте состояние аккаунта через минуту (Самоописание ключа) и не повторяйте запрос, он упрётся в ограничение частоты.
FIX-0812-11: выкладка на отдельную виртуальную машину сохраняет внутренние ссылки сборки и проверяет корень приложения
Было
Перед запуском платформа удаляла из распакованного дерева все символические ссылки. Готовая сборка с серверным рендерингом связывает собственные файлы относительными ссылками внутри своего же каталога, поэтому приложение теряло часть зависимостей и на корневом адресе отвечало ошибкой 500. Проверка работоспособности опрашивала только путь из поля healthPath, так что лёгкая ручка вроде /api/health продолжала отвечать 200 и POST /v1/infra/servers/{id}/deploy сообщал об успехе. В Битрикс24 приложение при этом не открывалось: ошибка приходила с собственным заголовком приложения X-Frame-Options, и вместо страницы ошибки пользователь видел сообщение браузера о невозможности соединения.
Стало
Ссылки внутри каталога выкладки сохраняются. Удаляются только небезопасные: абсолютные, ведущие в никуда и те, чей действительный путь выходит за пределы каталога выкладки.
Кроме того, когда healthPath отличается от /, выкладка после успешной проверки этого пути опрашивает ещё и корень приложения — адрес, который открывает Битрикс24. Ответ 5xx на корне останавливает выкладку ошибкой на шаге healthcheck. Ответ 4xx выкладку не останавливает и приходит отдельным шагом app_root со статусом warning и пояснением в stdout. С healthPath по умолчанию (/) не меняется ничего: дополнительный запрос не выполняется, шага app_root в ответе нет.
Разбор ссылок стоит дороже прежнего удаления, поэтому на очень большом дереве он может не уложиться в отведённое время. Раньше такой шаг всё равно отчитывался успехом — теперь шаги normalize_windows_paths и cleanup_metadata в этом случае приходят со статусом warning и объяснением в stdout, а выкладка продолжается.
Влияние на интеграторов
Менять вызовы не нужно. Приложение, отвечающее на корне, ведёт себя как раньше. Сервер без страницы на корне — например, только с API — по-прежнему выкладывается успешно: его 404 приходит предупреждением, а чтобы предупреждения не было, укажите приложению тот путь, на котором оно действительно отвечает, через PATCH /v1/apps/{id}. Выкладка приложения, чей корень отвечает ошибкой 5xx, теперь завершается ошибкой, а не мнимым успехом — прежний зелёный ответ в этом случае был неверным.
BC-0812-12: числовые поля учёта времени возвращаются числами, а не строками
Поддержка старого формата до: не предусмотрена
Было
GET /v1/task-time и GET /v1/tasks/:taskId/time возвращали id, taskId, userId, seconds и minutes строками JSON. Справочник полей при этом объявлял их числовыми, то есть описание и ответ расходились.
Стало
Эти пять полей возвращаются числами JSON. Поле source остаётся строкой. Схема GET /v1/task-time/fields приведена в соответствие тем же изменением.
Что делать интеграторам
Обновите строгие схемы на своей стороне: там, где ожидалась строка, теперь придёт число. Старый формат не возвращается — сравнение вида seconds === "900" перестанет совпадать, нужно сравнивать с числом.
FIX-0812-13: фильтр с зарезервированным именем поля отклоняется, а не отдаёт всю коллекцию
Было
Фильтр с зарезервированным именем поля — filter[__proto__], filter[constructor], filter[prototype] — принимался и молча отдавал ПОЛНУЮ, неотфильтрованную коллекцию (а на /{entity}/aggregate — счёт по всей коллекции). Клиент, собравший имя поля фильтра из пользовательского ввода, считал выборку отфильтрованной.
Стало
Такой фильтр отклоняется с 400 UNKNOWN_FILTER_FIELD (как и любое другое неизвестное поле), на всех сущностях и на /{entity}/aggregate.
FIX-0812-14: ключ, которому разрешена выкладка, может и поправить приложение на том же сервере
Было
Ключ, привязанный к серверу через приложение, успешно выкладывал код: POST /v1/infra/servers/:id/deploy отвечал успехом. Но команда, загрузка файла и чтение логов тем же ключом отвечали 403 WRONG_KEY, и программного способа получить это право не было — смена управляющего ключа выполняется только в личном кабинете. Мелкая правка рантайма на сервере, который принимает выкладку, оказывалась недостижимой. Отказ на команде приходил без объекта hint, поэтому подсказки о восстановлении в нём тоже не было.
Стало
Права на сервер делятся по тому, чего касается вызов. Операции над содержимым приложения — выкладка, команда, загрузка файла и чтение логов — принимают либо управляющий ключ сервера, либо ключ, приложение которого привязано к этому серверу. Управление самой машиной (запуск, остановка, пробуждение, перезагрузка, сон, удаление, починка, режим, политика доступа, порт, SSH), токены доступа и загрузка значка по-прежнему требуют управляющего ключа.
Отказ 403 WRONG_KEY теперь на всех четырёх маршрутах несёт объект hint и называет оба шага восстановления: перепривязать сервер в кабинете и сменить ключ, которым обращается клиент — перепривязка не меняет секрет, который вы уже отправляете. Код ответа и его смысл не изменились: где отказывали раньше, отказывают и теперь.
Влияние на интеграторов
Менять ничего не нужно. Вызовы, которые работали, продолжают работать; вызовы, которые отвечали 403 WRONG_KEY из-за связи через приложение, теперь проходят. Подробности — восстановление доступа к серверу.
NEW-0812-16: идентификатор операции выкладки и сверка исхода постфактум
Выкладка приложения на сервер теперь выдаёт идентификатор операции ДО начала работы, а не в конце. Он приходит заголовком ответа X-Vibe-Operation-Id в обоих режимах, в потоковом режиме дополнительно первым событием operation (браузерный EventSource заголовков не отдаёт), и дублируется полем operationId в конверте — и при успехе, и при отказе.
По этому идентификатору исход выкладки читается отдельным запросом: GET /v1/infra/operations/:operationId. Он отвечает статусом running, succeeded, failed либо unknown (процесс прервался между стартом и записью исхода), шагом, временами старта и финиша и кодом ошибки. Ручка только на чтение, побочных действий у неё нет.
Зачем это нужно: раньше исход выкладки существовал только внутри самого ответа, поэтому при обрыве соединения узнать, чем всё кончилось, было нечем — оставалось запускать выкладку заново, не зная, сработала ли первая. Теперь идентификатор выдаётся до начала долгой работы, и оборванный вызов можно сверить.
Идентификатор выдаётся только при выкладке на отдельную виртуальную машину (kind: "STANDALONE"). У galaxy-приложения (kind: "GALAXY_APP") его нет ни в заголовке, ни в кадре, ни в теле ответа, и сверка исхода постфактум для таких выкладок пока не поддержана.
Исходы хранятся 7 суток и доступны по идентификатору каждый в отдельности, включая предыдущие попытки на том же сервере. По истёкшему идентификатору ответ отличается от «не найдено» — приходит 410 с кодом OPERATION_OUTCOME_EXPIRED, то есть «операция была, исход больше не хранится». Списка операций пока нет.
Отсутствие заголовка НЕ означает, что выкладка не запускалась: если платформа не смогла завести запись, выкладка продолжается без идентификатора. Читать отсутствие заголовка как отказ и запускать вторую выкладку поверх первой нельзя.
NEW-0812-17: Провалившийся деплой сообщает исход машинно, а не прозой
Деплой отдельной виртуальной машины мог закончиться так: приложение выложено и работает, проверка
работоспособности отвечает ошибкой, а платформа уже вернула приложение с ограниченных прав на
полные. Отличить это от «деплой не состоялся» можно было только по английскому тексту внутри
error.message, а машинный потребитель читает поля.
Теперь шаг healthcheck в data.steps[] несёт три факта о проверке: httpCode — код последней
попытки (поле отсутствует, если кода не было; ноль или null вместо него не приходят),
healthPath — путь, который фактически проверяли, и portOwner — service, foreign либо
unknown. Это факты о попытке, а не вердикт: шаг со статусом error законно может нести
httpCode: 200, потому что исход определяют success и статус шага.
Шаг hardening теперь приходит и при провальном деплое — раньше он был только при успешном.
Он несёт поле hardeningRollback: completed — возврат завершён, приложение работает с правами
администратора; incomplete — возврат начался и не завершился, приложение может по-прежнему
работать с ограниченными правами. То же значение продублировано в конверте ошибки как
error.hardeningRollback. Поле описывает возврат юнита и владения каталогом развёртывания, и
только их; его отсутствие означает «возврат не запускался», а не «ограничение прав не применялось»
— на второй вопрос отвечает шаг service_user.
Все поля необязательные и аддитивные: прежние вызовы работают без изменений.