Для AI-агентов: markdown этой страницы — /docs-content/changelog/2026-07-23.md индекс документации — /llms.txt

Изменения API: 23 июля 2026

← Журнал изменений · Июль 2026

NEW-0723-1: библиотека чертежей приложений — ТЗ по API-ключу

Готовые технические задания популярных приложений теперь доступны по ключу: GET /v1/app/blueprints/:slug?locale=ru|en возвращает сырой markdown ТЗ (Content-Type: text/markdown). Эндпоинт требует Authorization: Bearer <ключ>; неизвестный или скрытый чертёж — 404 BLUEPRINT_NOT_FOUND. Ссылку на ТЗ несёт копируемый «Промт для AI» при создании ключа — AI-агент скачивает ТЗ тем же ключом. Прежний анонимный путь /api/public/blueprints/:slug.md удалён.

NEW-0723-2: исходники удалённого сервера: доступ, уборка и честный ответ на вычищенные байты

Исходники переживают сервер — это давняя гарантия платформы, но добраться до них через API было нельзя: весь набор /v1/infra/servers/:id/sources* отвечал 404 на удалённый сервер, поэтому владелец не мог ни посмотреть свои версии, ни снять с них тег, ни удалить. Для версии с тегом published или manual это был тупик: снятие тега — единственный разрешённый способ обойти 409 PROTECTED_BY_TAG, а именно оно и было недоступно.

Теперь на удалённом сервере работают чтение и уборка: список версий, метаданные версии, скачивание, tag, PATCH, DELETE и cleanup. Сохранение новых версий (POST /sources) по-прежнему отвечает 404 — мёртвый сервер новых депозитов не принимает.

Чтобы удалённый сервер вообще можно было найти, GET /v1/infra/servers принимает ?includeDeleted=true. По умолчанию выдача не меняется. В каждой строке появилось поле deletedAt (null у живых серверов).

Отдельно: версия, чьи байты уже вычищены из хранилища, теперь отвечает 410 с кодом SOURCE_VERSION_BYTES_PURGED вместо 404. Разница существенная — 404 утверждал, что версии нет, тогда как запись о ней жива, а восстановление другое: перезалить архив, а не искать его в другом месте. Код приходит на скачивании и на деплое по {"source": {"versionId": "vN"}}.

Затронутые эндпоинты: GET /v1/infra/servers, POST /v1/infra/servers/:id/deploy, GET /v1/infra/servers/:id/sources, GET /v1/infra/servers/:id/sources/:versionId/download — контракт исходников описан на странице Хранилище исходников

FIX-0723-3: деплой galaxy-приложения: обрыв туннеля при сборке больше не маскируется под «host unreachable»

Было

Если во время сборки на galaxy-хосте моргал туннель и здорового контейнера этого деплоя в итоге не оказывалось (сборку прервало, контейнер не поднялся, канал exec был занят или приложение крашилось), деплой (POST /v1/infra/servers/:id/deploy) возвращал 502 GALAXY_HOST_UNREACHABLE с советом «повторите, когда хост переподключится». Хост при этом часто был доступен — совет вводил в заблуждение, а вызывающая сторона не видела реальную причину (например, что её приложение падает при старте).

Стало

Если после моргания туннеля хост доступен, но деплой не довёл приложение до рабочего состояния, деплой возвращает новый код 502 GALAXY_DEPLOY_INTERRUPTED — «хост доступен, но деплой прервался до старта приложения: отправьте тот же деплой повторно; если приложение раз за разом не стартует, сначала почините его (команду запуска, порт, зависимости, переменные окружения или лимит памяти)». Это по-прежнему повторяемая ошибка — слот цел, удалять и пересоздавать его не нужно. Код GALAXY_HOST_UNREACHABLE теперь остаётся только для действительно недоступного хоста (ни одна попытка проверки до него не достучалась). Если приложение действительно крашится в цикле, это надёжно выявляется уже на повторном деплое обычной проверкой живости (код GALAXY_APP_START_FAILED).

NEW-0723-4: подсказка в ошибке таймаута шага деплоя

Ошибка DEPLOY_TIMEOUT у POST /v1/infra/servers/:id/deploy теперь несёт структурированное поле error.hint (reason, recovery, recoveryAction), привязанное к зависшему шагу (error.step). Для команд пользователя (install, preStart) подсказка объясняет, что команда не завершилась в отведённое время, и советует сделать её неинтерактивной и завершающейся, а долгоживущие сервисы запускать из команды start или в фоне (docker compose up -d). Для шага установки рантайма (runtime — платформенный шаг, а не команда пользователя) и прочих служебных шагов подсказка указывает на возможный обрыв туннеля и на POST /v1/infra/servers/:id/repair. Поле аддитивное: прежние error.code, error.message и error.step не изменились, менять интеграцию не нужно; подсказка приходит и в JSON-режиме, и в SSE-событии error.

FIX-0723-5: ошибка шага деплоя показывает реальную причину, а не безобидное предупреждение

Было

При падении шага деплоя поле data.steps[].stderr (и, как следствие, error.message) могло нести только безобидное предупреждение из одного потока, теряя реальную причину сбоя.

Стало

Оба потока возвращаются вместе, с метками stderr: и stdout:; реальная причина больше не скрывается. Форма ответа и имя поля не изменились.

FIX-0723-6: сортировка списка по неуникальному полю больше не теряет записи на второй странице

Было

Запрос списка или поиск с сортировкой по неуникальному полю (например по датовому begindate) при выборке больше 50 записей мог молча вернуть меньше записей, чем есть: на границе страницы часть записей с одинаковым значением поля сортировки терялась. Ответ приходил с кодом 200, без признака неполноты. Затронуты сущности на основе CRM smart-process — сделки, лиды, контакты, компании, предложения, счета и элементы смарт-процессов /v1/items/{entityTypeId} — на путях GET /v1/{entity}, POST /v1/{entity}/search, в подвызовах POST /v1/batch и per-entity POST /v1/{entity}/batch. Тот же класс нестабильности затрагивал и числовую агрегацию (POST /v1/{entity}/aggregate с sum/avg/min/max/groupBy): выборка записей для агрегата шла без порядка, поэтому на объёмах больше 50 записей часть строк могла теряться и искажать результат.

Стало

К сортировке автоматически добавляется вторичный ключ по id — порядок становится строго определённым, и постраничная выборка не теряет и не дублирует записи независимо от поля сортировки. Пользовательская сортировка остаётся основным ключом; записи с одинаковым значением поля упорядочиваются по id по возрастанию. Менять запросы не нужно.

FIX-0723-7: переоткрытие тикета комментарием больше не оставляет штамп решения

Было

Комментарий команды через POST /v1/feedback/:id/comments, возвращающий тикет из RESOLVED или WITHDRAWN обратно в активный статус (NEW, REVIEWING, AWAITING_USER, NEEDS_REVIEW), не сбрасывал resolvedAt и resolvedBy. Они «зависали» от прошлого закрытия, и на чтении (GET /v1/feedback/:id, GET /v1/feedback) переоткрытый тикет выглядел одновременно активным и решённым.

Стало

Такой комментарий очищает resolvedAt и resolvedBy — на чтении активный тикет больше не несёт даты решения. resolution при этом не очищается: он отражает текст самого комментария. Комментарий, ставящий RESOLVED, по-прежнему проставляет штамп; переход в ARCHIVED и обычный переход между активными статусами штамп не трогают. Поведение согласовано с уже действовавшим сбросом на PATCH /v1/feedback/:id.

FIX-0723-8: фильтр списков statuses / departments / storages / currencies / products больше не игнорируется молча

Было

GET /v1/statuses, /v1/departments, /v1/storages, /v1/currencies, /v1/products работают через legacy-методы Битрикс24 (crm.status.list, department.get, disk.storage.getlist, crm.currency.list, crm.product.list), которые молча игнорируют нефильтруемые ключи и операторы. Неизвестное или неподдерживаемое поле фильтра — например filter[system] у статусов, filter[module] у хранилищ или filter[price] у товаров — а также операторы $gt / $contains / $ne возвращали 200 со всей таблицей. Клиент получал полный набор вместо ожидаемого подмножества — тихий отказ с неверными данными.

Стало

Для этих сущностей фильтр проверяется до вызова Битрикс24: разрешены только те поля, которые метод действительно фильтрует (проверено вживую). Остальные поля, операторы и пустые множества возвращают 400 UNSUPPORTED_FILTER с перечнем фильтруемых полей. Разрешённые поля по сущностям: statuses — id, entityId, statusId, name, sort, semantics, categoryId; departments — id, name, parentId, headId; storages — id, name, code, entityType, entityId; products — id, name, code, xmlId, active, sectionId, sort. crm.currency.list не фильтрует ничего — любой filter у /v1/currencies возвращает 400 с подсказкой отфильтровать на стороне клиента.

BC-0723-9: POST /v1/apps больше не возвращает поля prefix и suffix в ответе на создание

Поддержка старого формата до: 21.07.2026

Было

Ответ POST /v1/apps на создание приложения содержал два значения на vibe_app_ — короткий prefix и полный rawKey. Короткий prefix ошибочно принимали за ключ, и запрос с ним возвращал 401.

Стало

Ответ на создание содержит одно значение vibe_app_ — рабочий rawKey. Поля prefix и suffix остаются в GET /v1/apps и GET /v1/apps/:id для отображения маскированного ключа.

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

Используйте поле rawKey из ответа на создание как X-Api-Key. Маскированный префикс, если он нужен, берите из GET /v1/apps или GET /v1/apps/:id вместо ответа на создание.

FIX-0723-10: POST /v1/batch сохраняет телефон и почту при создании и обновлении лидов и контактов

Было

Через общий POST /v1/batch значения телефона и почты (мультиполя) при создании или обновлении лида либо контакта терялись. Вызов возвращал успех, но поле не сохранялось. Те же данные через одиночный POST /v1/leads или PATCH /v1/contacts/:id и через POST /v1/{entity}/batch сохранялись корректно.

Стало

Общий POST /v1/batch сериализует мультиполя так же, как одиночные вызовы. Телефон и почта сохраняются при создании и обновлении.

FIX-0723-11: Поиск и research больше не отвечают 402 INSUFFICIENT_BALANCE при положительном балансе

Было

POST /v1/search и POST /v1/research с платформенным движком (bitrix-search) на персональном ключе могли вернуть 402 INSUFFICIENT_BALANCE даже при достаточном балансе Vibe на счёте портала. Предварительная проверка баланса искала счёт по владельцу ключа, а счёт с недавних пор один на весь портал — и не находился.

Стало

Проверка и списание баланса всегда идут по счёту портала. При положительном балансе запрос выполняется и списывается корректно; 402 возвращается только при реальной нехватке средств. Форма запроса и ответа не изменилась.

FIX-0723-12: POST /v1/apps честнее сообщает о необходимости подписки Маркетплейса на cloud-shared пути

Было

При создании приложения на портале без активной подписки «BitrixGPT + Маркетплейс» POST /v1/apps возвращал непрозрачный 502 CONNECTOR_APP_INSTALL_FAILED без указания причины.

Стало

Отказ по подписке теперь классифицируется заранее: до обращения к коннектору POST /v1/apps проверяет авторитетное состояние подписки портала и, если оно отсутствует, сразу возвращает 403 с кодом B24_MARKET_SUBSCRIPTION_REQUIRED (или B24_MARKET_TRIAL_USED, если демо уже использован), понятным сообщением и ссылкой на оформление в error.details.upgradeUrl. Если состояние не авторитетно, тот же результат срабатывает при явном отказе коннектора по подписке. Прочие отказы cloud-shared выпуска (модуль не установлен, доступ запрещён портал-админом, прочие ошибки) классифицируются как прежде.

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

Менять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал 502 CONNECTOR_APP_INSTALL_FAILED при создании приложения, стоит дополнительно ловить 403 B24_MARKET_SUBSCRIPTION_REQUIRED / B24_MARKET_TRIAL_USED и подсказывать пользователю оформить подписку на портале.

FIX-0723-13: запуск сервера не отвечает ошибкой, если машина уже работает

Было

POST /v1/infra/servers/:id/start для сервера в состоянии error вызывал запуск машины у облака и любой отказ отдавал как 502 PROVIDER_ERROR. Если машина к этому моменту уже работала — например, её подняло автоматическое восстановление после вытеснения, — облако отвечало отказом «машина уже в состоянии RUNNING», и вызов возвращал ошибку на операции, которая фактически удалась. Клиент видел 502 и не мог отличить это от настоящего сбоя.

Стало

Такой отказ распознаётся как идемпотентный успех: если машина уже работает или находится в переходном состоянии, вызов возвращает 200 и сервер переходит в provisioning, как при обычном запуске. Настоящие отказы — недостаточно прав, исчерпана квота, машина не найдена — по-прежнему возвращают 502 PROVIDER_ERROR.

BC-0723-14: форма deployment.standalone.requiredFields.create в /v1/me стала объектом + документирует slug поля name

Поддержка старого формата до: 22.01.2027

Было

В ответе GET /v1/me per-kind под-блок deployment.standalone.requiredFields.create был массивом ["provider", "name", "plan", "region"] — формат name не указывался; у соседнего deployment.galaxyApp.requiredFields.create поле name говорило лишь «required». Имя с кириллицей или заглавными буквами при POST /v1/infra/servers отклонялось с 400 INVALID_REQUEST, но self-discovery об этом ограничении молчал.

Стало

deployment.standalone.requiredFields.create теперь объект (как соседний deployment.galaxyApp.requiredFields.create), и в обоих под-блоках name несёт формат: slug из строчных латинских букв по маске ^[a-z][a-z0-9-]*$, длина 2–63 символа. Человекочитаемую подпись кладите в необязательное поле displayName.

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

Плоский deployment.requiredFields["POST /v1/infra/servers"] (массив ["provider","name","plan","region"]) НЕ изменился — если вы читаете его, делать ничего не нужно, набор обязательных полей тот же. Если же ваш код парсил per-kind под-блок deployment.standalone.requiredFields.create как массив (.forEach / .includes("name") / .length / [0]), перейдите на чтение объекта: ключи — имена полей (provider/name/plan/region), значения — их описания.

NEW-0723-15: GET /v1/contacts/fields получил label и description для всех полей

В ответе GET /v1/contacts/fields теперь у всех 28 статических полей контакта есть человекочитаемые label и description. Раньше базовые поля (name, lastName, typeId и другие) приходили только с type и readonly, без описания смысла. Метки приходят по-русски. Семантику поля можно получить программно из ответа, без сверки со статической документацией. Кроме того, GET /v1/openapi.json публикует эти метки и описания (по-английски) как title и description в схемах Contact и ContactInput.

NEW-0723-16: smart-processes: поля relations и linkedUserFields во входной схеме

Поля relations (связи с сущностями CRM — parent/child, например привязка смарт-процесса к сделкам) и linkedUserFields теперь объявлены во входной схеме и видны в GET /v1/smart-processes/fields. Их можно передавать в POST /v1/smart-processes и PATCH /v1/smart-processes/:entityTypeId, чтобы связать смарт-процесс с другими сущностями CRM и вывести его в пользовательских полях. Фильтрация и сортировка по этим полям не поддерживаются — это вложенные структуры записи, а не поля выборки.

FIX-0723-17: bizproc-activities и bizproc-robots: тип documentType в /fields исправлен на array

Было

GET /v1/bizproc-activities/fields и GET /v1/bizproc-robots/fields показывали для documentType тип object, тогда как поле — массив из трёх элементов ([moduleId, entity, documentType]), как уже было объявлено у bizproc-templates.

Стало

Тип documentType в /fields теперь array у всех трёх сущностей — согласованно с реальным контрактом.

FIX-0723-18: PATCH /v1/bizproc-templates возвращает id числом

Было

PATCH /v1/bizproc-templates/:id возвращал data.id строкой ("1215"), тогда как POST возвращает число (1215). Клиент, сравнивавший id из ответа создания с ответом обновления, получал ложное несовпадение.

Стало

Ответ PATCH возвращает data.id числом (1215) — так же, как POST.

FIX-0723-19: userfields: тип label в схеме создания исправлен на string

Было

OpenAPI-схема POST /v1/userfields/{entity} объявляла label как object. B24 crm.<entity>.userfield.add принимает LABEL только строкой, поэтому SDK, сгенерированный по спеке (где label — объект), отправлял неверный тип и получал ошибку. OpenAPI-спека — публичный контракт: клиенты генерируют по ней SDK, и у тех, у кого тип был «объект», клиент был сломан.

Стало

label в схеме создания объявлен как string (подпись на языке портала по умолчанию). Мультиязычные подписи задаются через editFormLabel / listColumnLabel / listFilterLabel (PATCH после создания). Рантайм не менялся — правка только генерируемой спеки.

FIX-0723-20: sleep-now для galaxy-приложения теперь отвечает 400 — управляйте им со страницы «Галактики»

Было

POST /v1/infra/servers/:id/sleep-now для приложения, размещённого в галактике (GALAXY_APP), усыплял контейнер и отвечал 200. Это расходилось с сессионным маршрутом, который такое приложение уже отклонял, и могло рассинхронизировать состояние контейнера с хостом.

Стало

Тот же вызов для galaxy-приложения отвечает 400 с error.code = "GALAXY_APP_USE_GALAXY_ROUTE" и не меняет состояние: контейнер остаётся RUNNING. Управляйте жизненным циклом приложения через маршруты галактики. Для обычных (standalone) серверов поведение sleep-now не изменилось.

FIX-0723-21: комментарий задачи больше не отдаётся под чужой задачей

Было

На старых порталах GET /v1/tasks/:taskId/comments/:id возвращал 200 и сам комментарий, даже когда комментарий не принадлежал задаче :taskId: один и тот же комментарий отдавался под любой задачей, а поле taskId в ответе было простым эхом пути.

Стало

Перед выдачей комментарий сверяется с задачей из пути. Если комментарий не принадлежит :taskId, эндпоинт отвечает 404 с кодом NOT_FOUND и сообщением «Comment not found». Поле taskId в ответе теперь совпадает с реальной родительской задачей. Запросы комментария по его настоящей задаче работают как прежде.

FIX-0723-22: тип компании — единое поле typeId, не companyType

Было

Имя поля «тип компании» различалось на слоях. POST /v1/companies с полем companyType молча игнорировал тип — компания создавалась с типом по умолчанию; сохранить тип можно было только полем typeId. Чтение (GET, поиск) всегда возвращало тип в поле typeId. Фильтр же принимал companyType, но не typeId.

Стало

Тип компании — единое поле typeId во всех операциях: создание и изменение, чтение и поиск, фильтр (filter[typeId]) и группировка (groupBy: typeId). Значения прежние — CUSTOMER, SUPPLIER, COMPETITOR (список: GET /v1/statuses?filter[entityId]=COMPANY_TYPE). Поле теперь описано в GET /v1/companies/fields.

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

Указывайте тип полем typeId. Чтение не меняется — тип всегда приходил в typeId. На создании и изменении companyType больше не описан (он и раньше не сохранял значение). В фильтре и группировке теперь работает typeId, а companyType возвращает 400 (UNKNOWN_FILTER_FIELD в фильтре, INVALID_AGGREGATION_FIELD в группировке) — замените имя на typeId.