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

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

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

NEW-0714-1: OpenAPI-спека: валидность 3.1, семантика полей и срез по скоупу

Было

Машинная спека GET /v1/openapi.json содержала 3.0-ключ nullable (невалиден в 3.1), не объявляла path-параметр {entityTypeId} на batch/aggregate/fields/products, не несла описаний/допустимых значений/примеров полей, описывала только ключи vibe_app_ и отдавалась одним монолитом.

Стало

Спека валидна по OpenAPI 3.1: nullable-поля используют union-тип ["<тип>","null"], все path-параметры объявлены. У свойств появились title/description, допустимые значения (x-enumValues + расшифровка в описании) и примеры. Секьюрити-схемы называют все три вида ключей (vibe_api_/vibe_app_/vibe_live_), у каждой операции есть x-required-scope, а в корне — каталог x-scopes. Добавлены корневые tags/externalDocs, блок webhooks для бот-событий и anyOf для мультиполей. GET /v1/openapi.json?scope=<scope> (например ?scope=crm) отдаёт срез спеки по одному скоупу, чтобы он помещался в контекст агента. Указатели на спеку добавлены в /v1/me и /v1/guide.

FIX-0714-2: Общая 5xx-ошибка на AI-эндпоинтах теперь в OpenAI-совместимом конверте

AI-эндпоинты (/v1/ai/*, /v1/models, /v1/chat/*, /v1/audio/*) документированы с OpenAI-совместимым форматом ошибок. При исчерпании пула соединений это уже соблюдалось, но общая (непредвиденная) 5xx-ошибка на этих эндпоинтах отдавала обычный V1-конверт.

Было

Общая 5xx-ошибка на AI-эндпоинте: {"success": false, "error": {"code": "...", "message": "..."}} — не тот формат, что SDK-клиенты OpenAI ожидают для этих путей.

Стало

Тот же случай теперь отдаёт {"error": {"message": "...", "type": "...", "code": "..."}} — единый конверт для всех ошибок AI-эндпоинтов, включая общие 5xx.

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

Клиенты на OpenAI SDK, уже читающие error.type/error.code (штатный путь для этих эндпоинтов), не заметят изменения. Код, который на AI-эндпоинтах ожидал success/error.code в верхнем уровне именно на общих 5xx, должен переключиться на error.type/error.code.

FIX-0714-3: `versions` в списке версий исходников приложения ограничен 500 записями

GET /v1/apps/:id/sources возвращал весь список версий без ограничения — для приложений с очень длинной историей это была неограниченная выборка.

Было

data.versions — весь список версий без ограничения по размеру; data.totalVersions всегда равнялся data.versions.length.

Стало

data.versions содержит не более 500 последних версий (по savedAt, убывание). data.totalVersions и data.totalSizeBytes по-прежнему считаются по полной выборке — точность агрегатов не зависит от кэпа.

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

Для приложений с историей до 500 версий поведение не меняется. Для приложений с историей больше 500 версий data.versions.length теперь может быть меньше data.totalVersions — код, полагавшийся на их равенство, должен ориентироваться на data.totalVersions/data.totalSizeBytes для агрегатов и не считать data.versions полным списком.

FIX-0714-4: Телефония: `userId`/`duration` теперь по-настоящему валидируются, а не только на truthy

POST /v1/calls/register, /v1/calls/:callId/show, /v1/calls/:callId/hide, /v1/calls/:callId/finish принимали userId (и duration у finish) без проверки типа/формы — любое truthy-значение (например, строка "abc" или объект) проходило в Битрикс24 и падало там уже непрозрачной ошибкой апстрима.

Было

{"userId": "abc"} (или любое другое truthy, не являющееся положительным целым) проходил валидацию и уходил в Битрикс24; ошибка Required: userId (number) появлялась только на полностью пустом/falsy значении.

Стало

userId принимается как положительное целое число либо как числовая строка ("42"), иначе — чистый 400 MISSING_PARAMS с уточнённым текстом Required: userId (positive integer) (для register — плюс phoneNumber). duration у finish аналогично — неотрицательное число либо числовая строка, иначе 400.

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

Корректные вызовы (userId — число или числовая строка) не меняются. Вызовы с ранее «проходившим» некорректным userId/duration (не число, не числовая строка) теперь получают явный 400 вместо непрозрачной ошибки со стороны Битрикс24.

FIX-0714-5: Создание комментария к несуществующей задаче — понятная ошибка вместо ложного успеха

POST /v1/tasks/:taskId/comments и его пакетный вариант POST /v1/tasks/:taskId/comments/batch (action: create) обращаются к Битрикс24 для создания комментария на старой карточке задачи. Если задача не существует или недоступна ключу, Битрикс24 не создаёт комментарий и не возвращает идентификатор.

Было

Оба эндпоинта отвечали успехом с пустым идентификатором — одиночный вызов 201 {"success": true, "data": {"id": null}}, пакетный — элементом {"success": true, "id": null}. Комментарий не создавался, но интегратор не мог отличить это от штатного случая.

Стало

Одиночный вызов возвращает 404 TASK_NOT_FOUND. Пакетный вариант помечает соответствующий элемент как {"success": false, "error": "TASK_NOT_FOUND"}, не отменяя обработку остальных элементов пакета. Отдельный, не связанный с этой ошибкой случай сохраняется: на новой карточке задачи комментарий уходит в чат, и если система не смогла восстановить его id отдельным поиском, ответ по-прежнему success: true с id: null — комментарий в этом случае реально создан.

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

Код, проверяющий data.id / data[i].id на null как признак ошибки, продолжит работать без изменений и получит более точный код ошибки. Код, полагавшийся на молчаливый успех с id: null при недоступной задаче, должен обрабатывать 404 / TASK_NOT_FOUND явно.

FIX-0714-6: Лимит запросов `/v1/search`, `/v1/research`, `/v1/batch` теперь считается на портал

Было

Лимиты (/v1/search 60/мин, /v1/research 20/мин, /v1/batch 30/мин) фактически считались по IP-адресу, а не по порталу. Портал с несколькими API-ключами (или несколько порталов за одним общим egress-IP) мог получить больше документированного лимита, а само ограничение обходилось ротацией IP.

Стало

Лимит считается на портал: все API-ключи одного портала делят единый бакет (60 / 20 / 30 запросов в минуту соответственно). Документированный лимит на тенант теперь применяется корректно и не обходится ни числом ключей, ни сменой IP.

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

Если ваш портал распределял нагрузку на /v1/search / /v1/research / /v1/batch между несколькими API-ключами, суммарный предел теперь — единый лимит портала, а не сумма по ключам. При достижении предела возвращается 429 с заголовком Retry-After (как и раньше).

FIX-0714-7: Календарь: рабочий batch-delete секций, чистые ошибки update и без утечки sync-полей

Было

  • POST /v1/calendar-sections/batch с action: "delete" не имел канала для type/ownerId, которые требует calendar.section.delete — каждый элемент падал на обеих платформах.
  • PATCH /v1/calendar-sections/{id} без type/ownerId/name уходил в Битрикс24 и возвращал сырой 422 с именем внутреннего метода.
  • GET /v1/calendar-sections отдавал недокументированные сырые поля Битрикс24 GAPI_CALENDAR_ID, CAL_DAV_CON, SYNC_TOKEN, PAGE_TOKEN, EXTERNAL_TYPE (три из них — токены синхронизации).
  • GET /v1/calendar-events и GET /v1/calendar-events/{id} отдавали внутреннее поле attendeesEntityList — схема пыталась его вырезать, но не срабатывала из-за несовпадения регистра ключа.

Стало

  • Batch-delete секций читает type/ownerId из тела рядом с ids и прокидывает их в каждую команду удаления. Отсутствие любого — чистый 400 MISSING_REQUIRED_PARAMS до вызова Битрикс24.
  • Partial-update секций требует якорные type/ownerId/name (у секций нет get-by-id для подстановки) — чистый 400, без сырого 422 и без утечки имени метода.
  • Оба календарных read-пути убирают перечисленные внутренние/sync-поля из ответа.

Затронутые эндпоинты:

FIX-0714-8: PATCH catalog-product-properties снова работает (был неустранимый catch-22)

Было

Обновить свойство товара было невозможно ни при каком теле: PATCH без iblockId → 422 («Required fields: iblockId» — B24 требует его на каждом update), а PATCH с iblockId → 400 READONLY_FIELD (поле только для создания). Итог — весь глагол UPDATE мёртв: ни одно поле нельзя было изменить после создания.

Стало

iblockId теперь подставляется автоматически из существующей записи (pre-fetch, как у catalog-sections), так что PATCH {name:"…"} доходит до B24 с нужным iblockId и возвращает 200. iblockId в теле по-прежнему не нужен (и по-прежнему отклоняется как read-only, если его прислать) — его сохраняет сам сервис.

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

Если раньше ваш PATCH свойства товара всегда падал 422/400 — теперь шлите только изменяемые поля (PATCH {name:"…"}), iblockId передавать не надо.

FIX-0714-9: Валидация entityTypeId: мусорные формы → 400 вместо тихого усечения

Было

Пять поверхностей парсили entityTypeId (селектор типа smart-process / динамической сущности) снисходительно — неякорным parseInt или коэрсирующим Number(), — и мусорная форма молча превращалась в ДРУГОЙ (в рамках портала) тип сущности:

  • Путевой entityTypeId (/v1/items/:entityTypeId/..., /v1/categories/:entityTypeId/... — CRUD и /aggregate): GET /v1/items/1058abc усекался до 1058, 1e3 → 1, 1.5 → 1.
  • Глобальный POST /v1/batch: params.entityTypeId через Number() принимал дробные (1.5), hex ('0x10' → 16), переполнение до Infinity, а также массив [1058] → 1058.
  • /v1/items/:entityTypeId/userfields/*: собственный парсер — 2abc резолвил пользовательские поля типа 2.
  • POST /v1/smart-processes/batch: ids: ['1030abc'] усекался до 1030 — delete/update молча выполнялся против ЧУЖОГО реального типа; дробное число (1030.5) тоже проходило.
  • POST /v1/triggers/fire (entityType="item"): entityTypeId: '1038abc' → триггер автоматизации выстреливал по типу 1038.

Стало

Все пять поверхностей требуют каноничную форму положительного целого (/^[1-9]\d*$/ для строк, Number.isInteger для чисел): любая иная форма → 400 с прежним кодом ошибки поверхности (INVALID_DYNAMIC_PARAM / INVALID_ENTITY_TYPE_ID / BATCH_ITEM_VALIDATION / MISSING_PARAMS) ДО вызова Битрикс24. Aggregate использует общий с CRUD-маршрутами валидатор вместо инлайн-копии.

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

Формы, которые раньше коэрсились в корректное значение и обслуживались — 007 → 7, %20-пробелы, +2 → 2, массив [1058] в batch — теперь отклоняются с 400: значение должно быть каноничным целым без префиксов, хвостов и ведущих нулей. Boolean в batch отклонялся и раньше (коэрсился в reserved-тип); меняется только код ошибки — теперь INVALID_DYNAMIC_PARAM. Корректные вызовы не меняются.

FIX-0714-10: Переоткрытие фидбека очищает поля решения

Было

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

Стало

Возврат тикета из RESOLVED/WITHDRAWN в активный статус очищает resolvedAt, resolvedBy и resolution. RESOLVED/WITHDRAWN по-прежнему проставляют штамп решения; ARCHIVED не трогает поля (архивирование сохраняет историю решения). Обычный переход между активными статусами (например AWAITING_USER → REVIEWING) поля НЕ трогает — на активных тикетах resolution отражает последний комментарий команды. Явно переданный в том же запросе resolution имеет приоритет над очисткой.

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

Если вы читали resolvedAt/resolution переоткрытого тикета и получали значения от прошлого закрытия — теперь они null для активного тикета.

FIX-0714-11: `INVALID_JSON_BODY` больше не цитирует внутренний текст парсера

Было

Пять маршрутных групп (/v1/apps*, /v1/bots*, /v1/keys*, /v1/note*, /v1/infra/servers/* deploy/exec/upload) на битый JSON отвечали 400 с сообщением вида Invalid JSON: Unexpected token } in JSON at position 41 — сырой текст движка V8 (отпечаток рантайма, деталь реализации). Группа deploy/exec/upload при этом не ставила и код ошибки.

Стало

Все пять отвечают единым статическим сообщением Request body is not valid JSON. — как /v1/<entities> (тот же класс закрывается там отдельным исправлением). На V1-поверхностях код — INVALID_JSON_BODY (deploy/exec/upload теперь тоже его ставит); статус 400 не изменился.

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

Если ваш код парсил текст сообщения (например, вытаскивал позицию ошибки) — опирайтесь на код INVALID_JSON_BODY; позиция ошибки больше не сообщается.

FIX-0714-12: Смета: amount, currency и даты в ответе GET снова заполнены (были null)

Было

Чтение сметы (GET/list/search /v1/quotes) отдавало null для amount, currency, beginDate, closeDate — значения «протекали» только под сырыми ключами Битрикс24 (opportunity, currencyId, begindate, closedate). Запись работала правильно, но READ-проекция теряла все поля-алиасы: сумма сметы и валюта были 100% невидимы через задокументированный API.

Стало

READ-ветка теперь реверс-мапит объявленные алиасы (зеркало write-маппинга): opportunity → amount, currencyId → currency, begindate → beginDate, closedate → closeDate — с приведением типов. Сырые ключи Битрикс24 в ответе больше не появляются.

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

Если вы читали amount/currency смет и получали null — теперь они заполнены. Код, читавший обходным путём сырой opportunity/currencyId из ответа, их там больше не найдёт — переключитесь на документированные amount/currency.

FIX-0714-13: Валидация записи: фантомный чек-лист → 404, мусорные типы и `:id` → 400

Было

  • POST /v1/tasks/:taskId/checklist на несуществующую задачу возвращал 201 с правдоподобным id, хотя пункт не создавался (его нельзя было получить GET-ом).
  • POST /v1/warehouses принимал нестроковые title/address (число, объект) и пересылал их в Битрикс24 с непредсказуемым результатом; POST /v1/doc-templates так же пропускал нестроковые name/region и нечисловой numeratorId.
  • Нечисловой :id в путях сущностей с типизированным числовым id (GET/PATCH/DELETE /v1/quotes/abc, /v1/deals/1.5, /v1/leads/1e3) уходил в Битрикс24 как есть — в ответ прилетала непрозрачная ошибка B24 вместо внятного кода. У smart-processes 12abc усекался parseInt-ом до 12 и попадал в ЧУЖОЙ тип.

Стало

  • Чек-лист: родительская задача проверяется до создания пункта; несуществующая (или недоступная ключу) задача → 404 TASK_NOT_FOUND.
  • Склады и шаблоны документов: значение не того типа → 400 INVALID_PARAMS без вызова Битрикс24 (числовая строка в numeratorId по-прежнему принимается).
  • Сущности с явно типизированным числовым id: не-канонично-целый :id → 400 INVALID_PARAMS до вызова Битрикс24 (id 0 — основная воронка сделок categories — остаётся валидным). Smart-processes сохраняют INVALID_ENTITY_TYPE_ID и теперь отклоняют 12abc на GET/PATCH/DELETE, а не усекают до 12. Сущности, у которых тип id не задекларирован в схеме, сохраняют прежнее сквозное поведение.

Затронутые эндпоинты: POST /v1/tasks/:taskId/checklist, POST /v1/warehouses, POST /v1/doc-templates + GET/PATCH/DELETE по сущностям с типизированным числовым id.

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

Если ваш код опирался на фантомный 201 от чек-листа или отправлял мусорные значения «на авось» — теперь придёт явный 4xx с кодом. Корректные вызовы не меняются.

FIX-0714-14: Пять тихих false-success/hint-дефектов: честный ответ вместо мнимого успеха

Было

  • PATCH /v1/userfields/{entity}/{id} c label — тихий no-op на обновлении: ответ 200, но подпись поля не менялась (Битрикс24 crm.*.userfield.update игнорирует LABEL).
  • POST /v1/humanresources/nodes/{id} c parentId — ложный «перенос удался»: name применялся, parentId молча игнорировался, а в ответе эхом возвращался старый родитель.
  • POST /v1/chats/messages/bulk — псевдоним dialogId: "me" не разрешался в bulk-цикле (одиночные роуты его разрешают) → сообщение уходило не туда.
  • POST /v1/bots/{botId}/chats/{dialogId}/users — предохранитель USERS_NOT_ADDED был мёртвым кодом (не совпадал с формой ответа метода v2) → неудачное добавление проходило как успех.
  • Ошибки crm.item.list получали нерелевантную подсказку «Maximum 50 records…» даже когда причина была иной (например «entity type does not exist»).

Стало

  • label на обновлении разворачивается в реальные параметры EDIT_FORM_LABEL/LIST_COLUMN_LABEL/LIST_FILTER_LABEL — подпись действительно меняется.
  • parentId и type теперь только для создания: на PATCH они отклоняются с 400 (перенос — через POST /v1/humanresources/nodes/{id}/move), а не имитируют успех.
  • Bulk-чтение сообщений разрешает dialogId: "me" для каждого элемента, как и одиночные роуты.
  • Предохранитель добавления в бот-чат снова срабатывает: непринятые пользователи возвращаются в warning.USERS_NOT_ADDED.
  • Подсказки об известных ограничениях привязаны к релевантности сообщения ошибки, а не только к имени метода.

Затронутые эндпоинты:

FIX-0714-15: GET /v1/task-time теперь честно возвращает больше 50 записей при limit>50

Было

GET /v1/task-time с limit больше 50 возвращал только 50 записей, хотя meta.limit повторял запрошенное значение, а meta.hasMore мог вводить в заблуждение. Клиент, обходивший данные постранично с шагом больше 50, молча терял часть записей.

Стало

Запрос отдаёт до limit записей (максимум 500), собирая их постранично на стороне бэкенда; meta.total и meta.hasMore соответствуют фактически возвращённому окну. При limit больше 50 значение offset теперь указывает на правильную позицию, а не сдвигается на первые страницы.

NEW-0714-16: GET /v1/companies/fields и системные поля catalog-prices получили label и description

В ответе GET /v1/companies/fields теперь у всех полей компании есть человекочитаемые label и description. В GET /v1/catalog-prices/fields те же метки добавлены системным полям extraId, priceScale и timestampX. Метки приходят по-русски. Семантику поля можно получить программно из ответа, без сверки со статической документацией.

FIX-0714-17: /stop и /reboot различают отсутствующий сервер и неподходящий статус

Было

POST /v1/infra/servers/:id/stop и POST /v1/infra/servers/:id/reboot на сервере не в статусе running (например, спящем) отвечали плоским 404 NOT_FOUND с текстом «Running server not found» — по нему нельзя было понять, что сервер существует, и агент решал, что он удалён.

Стало

Оба маршрута ведут себя как /start и /wake: 404 SERVER_NOT_FOUND — только когда сервера с таким id действительно нет; 422 SERVER_WRONG_STATE — когда сервер есть, но не в статусе running. В error.currentState возвращается текущее состояние, в error.availableActions — доступные действия (wake/start/repair/delete).

FIX-0714-18: понятная ошибка на /fields у комментариев и учёта времени задач

Было

Запрос GET /v1/tasks/:taskId/comments/fields отвечал сбивающим с толку 400 INVALID_PARAMS с текстом «taskId and id must be positive integers» (спрашивали про поля — ответ про идентификатор), а GET /v1/tasks/:taskId/time/fields утекал сырой ошибкой Bitrix24 с внутренним PHP-методом и HTML-тегом.

Стало

Обе сущности распознают сегмент fields и возвращают понятный 400 WRONG_PATH: у них нет метода /fields (состав полей описан в справочнике /v1/guide), в тексте перечислены доступные маршруты. Нечисловой или дробный идентификатор в маршрутах по id теперь отклоняется как 400 INVALID_PARAMS до обращения к Bitrix24 — без утечки внутренней ошибки.

FIX-0714-19: Лимиты `/v1/ai/credentials*` считаются на портал; `CREDENTIAL_NOT_FOUND` подсказывает решение

Было

  • Лимиты BYOK-маршрутов (POST /v1/ai/credentials, /:id/test, /:id/fetch-models — 10/мин; /:id/models добавление/удаление — 30/мин) фактически считались по IP-адресу: перебор чужих ключей обходился ротацией IP, а арендаторы за одним общим egress-IP делили один бакет. Маршрут PATCH /:id (при передаче credentials он проверяет ключ у апстрима — тот же оракул, что /:id/test) не имел лимита вовсе.
  • 404 CREDENTIAL_NOT_FOUND от /v1/search и /v1/research содержал только слаг провайдера — без указания, как настроить ключ.

Стало

  • Лимит считается на портал: все API-ключи одного портала делят единый бакет; ротация IP и число ключей на предел не влияют. При превышении — 429 с Retry-After. Дополнительно PATCH /:id (проверяет ключ у апстрима, как /:id/test) раньше вовсе не имел лимита — теперь тоже 10/мин на портал.
  • В ответ CREDENTIAL_NOT_FOUND добавлено поле hint с точным рецептом: POST /v1/search/credentials {provider, apiKey}, список провайдеров — GET /v1/search/providers.

FIX-0714-20: PATCH bizproc-шаблонов, роботов и активностей через /v1 применяет изменения

Было

PATCH /v1/bizproc-templates/:id, /v1/bizproc-robots/:code и /v1/bizproc-activities/:code с полями метаданных (name, description, autoExecute) возвращали 422 BITRIX_ERROR "No fields to update." — обновить сущность было нельзя (то же и в пакетных запросах). Поле autoExecute, переданное числом, дополнительно отвергалось как Incorrect field AUTO_EXECUTE!.

Стало

Поля корректно применяются (в том числе autoExecute, переданное числом); эндпоинт подтверждает успех и возвращает id обновлённой сущности. Работают и одиночный PATCH, и пакетные запросы. Создание (POST) не изменилось.

FIX-0714-21: Транскрипция: проверка кошелька до вызова распознавания

Было

POST /v1/audio/transcriptions (и /v1/ai/audio/transcriptions) не проверял состояние кошелька перед вызовом: PREPAY-счёт, ушедший за овердрафт (но ещё не замороженный фоновой проверкой), всё равно запускал распознавание и уводил баланс глубже в минус. Чат и эмбеддинги отклоняли такой вызов заранее; полностью замороженный счёт и раньше блокировался глобально (ACCOUNT_FROZEN).

Стало

Как у чата и эмбеддингов: денежная проверка выполняется до обращения к Whisper. Превышенный овердрафт → 402 insufficient_balance, распознавание не запускается. BYOK-ключи (USER-scope) бесплатны — для них проверки нет и поведение не меняется.

FIX-0714-22: удаление приложения больше не блокируется галактическим хостом на его ключе

Было

DELETE /v1/apps/:id возвращал 409 APP_HAS_ACTIVE_SERVERS, если на ключе приложения оказывался галактический хост (общая инфраструктура портала). Снять хост через удаление приложения было нельзя, а объяснения в ответе не было.

Стало

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

FIX-0714-23: OpenAPI: тело per-entity батч-эндпоинтов теперь описано верно (action + items/ids/calls)

Было

Спека (GET /v1/openapi.json) описывала тело каждого пер-сущностного батча как {create:[], update:[], delete:[]}. Рантайм же (общий обработчик батчей) требует {action, items|ids|calls} и отвечает 400 INVALID_BATCH_ACTION на документированную форму. Клиент, сгенерированный из спеки (codegen / AI-агент), получал 100% отказ batch-write на всех ~45 пер-сущностных батч-эндпоинтах. Сама фича работает — врала только спека (глобальный POST /v1/batch, /v1/tasks/{taskId}/comments/batch и /v1/guide уже описывали правильную форму).

Стало

Генератор спеки выдаёт правильную форму: один action (create/update/delete/list/get/fields); create/update шлют items, delete — ids, чтения — calls. Совпадает с рантаймом и с глобальным /v1/batch.

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

Если вы генерировали клиент из openapi.json и batch-write падал INVALID_BATCH_ACTION — перегенерируйте: тело теперь {action:"create", items:[…]} вместо {create:[…]}. Handwritten-клиенты, уже славшие {action,…}, не затронуты.

FIX-0714-24: Валюты: fullName, формат и число знаков теперь сохраняются при плоской записи

Было

POST/PATCH /v1/currencies с плоскими fullName, formatString, decimals, decPoint, thousandsSep возвращал успех, но значения молча терялись — Битрикс24 хранит их в структуре локализации по языкам (LANG), а API слал их плоско. Задокументированный обходной путь «передавайте сырой LANG» тоже не работал: LANG — поле только для чтения, запрос отклонялся.

Стало

API упаковывает плоские локализуемые поля в локализацию вашего языка (языка API-ключа) перед вызовом Битрикс24, поэтому плоская запись сохраняется и читается обратно (POST {fullName:"…",decimals:3} → GET вернёт их). Работает на всех путях записи: одиночном, POST /v1/currencies/batch и глобальном POST /v1/batch. Сырой LANG в теле по-прежнему отклоняется как read-only. Разные значения сразу для нескольких языков через API пока не задаются. Язык записи определяется локалью API-ключа (ru или en) и может не совпадать с языком отображения валюты на портале — на порталах с иной локалью (de/pl/ua…) правка ляжет под en.

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

Если вы обходили баг сырым LANG (и упирались в 400 READONLY_FIELD) — уберите его и шлите плоские поля. Уже работавшие плоские запросы теперь ещё и сохраняют значения.

FIX-0714-25: Aggregate уважает обязательные фильтры; infra-валидация не течёт сырым Zod

Было

  • POST /v1/<entity>/aggregate с сущностью, требующей фильтр (напр. catalog-products требует iblockId), пропускал пустой запрос в Битрикс24 и возвращал сырой 422, тогда как GET-list/search на том же условии отдают чистый 400.
  • POST /v1/infra/servers/:id/{deploy,exec,upload,logs} при ошибке валидации тела клал в error.message многострочный JSON-сериализованный массив Zod-issues (сырой отпечаток валидатора).

Стало

  • Aggregate проверяет обязательные фильтры/параметры до вызова Битрикс24: нет обязательного фильтра — 400 MISSING_REQUIRED_FILTER (напр. catalog-products→iblockId); нет обязательного list-параметра — 400 MISSING_REQUIRED_PARAMS (напр. calendar-events→type,ownerId; humanresources-nodes→type). Как у list/search.
  • Infra-валидация форматирует ошибку компактно (поле: сообщение; …). Код (VALIDATION_ERROR) и статус 400 не изменились.

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

Если вы ловили сырой 422 от aggregate без фильтра — теперь придёт 400 MISSING_REQUIRED_FILTER. Если парсили infra-error.message как JSON — теперь это плоская строка поле: сообщение.

FIX-0714-26: Batch: работает per-entity batch для items и создание папок через batch

Было

  • POST /v1/items/{entityTypeId}/batch возвращал 404 — per-entity batch-роут для сущностей с динамическим параметром (items, categories) монтировался по адресу без сегмента параметра (/v1/items/batch), поэтому документированный путь не находился, а entityTypeId не доходил до команды Битрикс24. Обходной путь — глобальный POST /v1/batch — работал.
  • POST /v1/folders/batch с action: "create" падал на каждом элементе с ERROR_ARGUMENT: batch-создание слало fields[...], тогда как disk.folder.addsubfolder ожидает родительскую папку верхним параметром id, а остальные поля — под data[...].

Стало

  • Per-entity batch-роут для items/categories монтируется с сегментом :{entityTypeId} и прокидывает валидированный entityTypeId (положительное целое; id выделенных API вроде deals=2 отклоняются с подсказкой — как в одиночных роутах) в каждую команду — для всех действий: create/update/delete и read-действий list/get/fields.
  • Batch-создание папок повторяет форму одиночного роута: id=<родитель>&data[...]. Пропущенный parentId — чистый per-item 400 до вызова Битрикс24.

Затронутые эндпоинты:

NEW-0714-27: восстановление зависшего exec-канала сервера

Новый эндпоинт POST /v1/infra/servers/:id/unstick принудительно освобождает залипший канал команд Black Hole-сервера, когда деплой или exec продолжает отвечать EXEC_BUSY («Another command is running») даже после DELETE /v1/infra/servers/:id/lock. Эндпоинт снимает блокировку на стороне платформы и разрывает туннель агента — при переподключении агент завершает зависшую команду и освобождает свой мьютекс. Перезагрузка сервера не выполняется.

Если в момент вызова на сервере ещё выполняется легитимная операция (деплой, exec, харден), эндпоинт по умолчанию отвечает 409 OPERATION_IN_PROGRESS и НЕ трогает её — расклинивать нужно только зависший канал. Повторите с ?force=true, если уверены, что канал команд действительно завис.

Ответ: { success: true, data: { backendLockReleased, agentBounced, reconnected } }. Коды ошибок: 404 SERVER_NOT_FOUND, 409 CONFLICT (восстановление уже идёт), 409 OPERATION_IN_PROGRESS (на сервере выполняется операция — повторите с ?force=true), 409 GALAXY_UNSTICK_UNSUPPORTED (для galaxy-хостов и galaxy-приложений не поддерживается), 502 GATEWAY_ERROR. Ошибка /exec (EXEC_BUSY) и провал /deploy (код DEPLOY_FAILED, сообщение «Another command is running») теперь несут подсказку hint с этим эндпоинтом.

NEW-0714-28: описание сервера в `PATCH /v1/infra/servers/:id`

Было

PATCH /v1/infra/servers/:id принимал только displayName. Поле description в контракте отсутствовало, а GET-ответы его не отдавали.

Стало

PATCH /v1/infra/servers/:id принимает опциональное поле description (строка, до 500 символов; пустая строка или null очищает описание; отсутствие поля оставляет текущее значение без изменений). Значение синхронизируется с карточкой приложения в каталоге. Поле description теперь возвращается в GET /v1/infra/servers, GET /v1/infra/servers/:id и в ответе PATCH. Старые запросы без description продолжают работать без изменений.

FIX-0714-29: деплой galaxy-приложения при обрыве связи во время сборки возвращает честную ошибку, а не ложный успех

Было

Если соединение с хостом обрывалось прямо во время сборки galaxy-приложения (частое явление под нагрузкой тяжёлой сборки), POST /v1/infra/servers/:id/deploy мог вернуть 200 со статусом running и пометкой [recovered] в buildLog, хотя новая версия так и не собралась и не поднялась — на хосте продолжал работать прежний контейнер. Повторный деплой упирался в тот же обрыв и снова рапортовал ложный успех.

Стало

Деплой считается восстановленным, только если он полностью завершился: под именем приложения запущен контейнер именно этой попытки, и деплой прошёл до конца. Если связь оборвалась во время сборки — или в любой момент до завершения деплоя — и новая версия не поднялась полностью, эндпоинт возвращает retryable-ошибку 502 с кодом GALAXY_HOST_UNREACHABLE и подсказкой «повторите тот же деплой, не удаляйте сервер» — вместо ложного 200. Только если деплой завершился полностью и потерялся лишь финальный ответ, восстановление в 200 работает как прежде.

BC-0714-30: переименование приложения в каталоге доходит до Битрикс24, publish потерял menuTitle

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

Было

PATCH /v1/apps/:id с полем title у приложения, добавленного в каталог, возвращал 200, но не менял ничего из того, что видит пользователь: карточка каталога и привязки мест встраивания на портале (пункт левого меню, вкладки CRM) оставались со старым именем. Отказа не было никогда — вызов всегда успешен.

У POST /v1/apps/:id/publish тело не проверялось, а заголовок места встраивания задавался отдельным полем menuTitle.

Стало

Для приложения в каталоге title — это одна операция над отображаемым именем: имя синхронизируется в карточку каталога (вместе с title пишется catalogTitle) и перепривязывается в места встраивания на портале. Поэтому вызов, который раньше всегда отдавал 200, теперь может честно отказать:

  • 400 NO_USER_TOKEN — приложение не авторизовано на портале, перепривязать места встраивания нечем;
  • 400 TITLE_TOO_LONG_FOR_CATALOG — имя приложения в каталоге ограничено 100 символами, тогда как title допускает 255;
  • 502 BITRIX_PARTIAL_REBIND — Битрикс24 отклонил привязку. Имя в этом случае не записывается, повтор того же запроса чинит состояние.

Тело POST /v1/apps/:id/publish теперь валидируется, а параметр menuTitle удалён: заголовок места встраивания всегда равен отображаемому имени приложения. Пустое тело по-прежнему работает — публикация берёт значения из записи приложения.

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

  • Убрать menuTitle из тела публикации: поле игнорируется, имя пункта меню задаётся через title и catalogTitle.
  • Держать имя приложения в каталоге в пределах 100 символов.
  • Обработать отказы при переименовании: на NO_USER_TOKEN авторизовать приложение на портале, на BITRIX_PARTIAL_REBIND повторить запрос.
  • Учесть побочный эффект: переименование через title теперь пишет и catalogTitle — у приложения в каталоге эти поля держатся синхронными.

NEW-0714-31: GET /v1/tasks/:taskId/comments/fields — схема полей комментариев задачи

У комментариев к задаче появился метод /fields, как у остальных сущностей: GET /v1/tasks/:taskId/comments/fields возвращает статическую схему из 5 полей (id, taskId, authorId, message, createdAt) с типом, признаком «только для чтения», названием и описанием по-русски. Метод не обращается к Битрикс24. Раньше этот путь возвращал 400 WRONG_PATH — состав полей был доступен только из статических доков.

FIX-0714-32: пер-сущностный batch с action list теперь применяет filter

Было

POST /v1/{entity}/batch с action: "list" игнорировал filter: имена полей не приводились к именам Битрикс24 (например statusId у лидов не превращался в stageId), а операторы $gt / $contains / $in и другие не работали. Вызов возвращал 200 со всей таблицей — тихий отказ с неверными данными. Глобальный POST /v1/batch и POST /v1/{entity}/search при этом фильтровали корректно.

Стало

Пер-сущностный batch пропускает filter через тот же транслятор, что search и глобальный /v1/batch. Псевдонимы полей и операторы ($gt, $gte, $lt, $lte, $ne, $contains, $in, $nin, префиксные >=, <=, ! и т.д.) применяются. Некорректный filter (неизвестное поле у сущности с полной схемой, неподдерживаемый оператор, префиксы @ / !@, логические токены $or / $and) теперь возвращает 400 с указанием индекса вызова, а не молча всю выборку — так же, как на одиночных эндпоинтах.

FIX-0714-33: авто-пагинация больше не дублирует записи на границах страниц

Было

Для методов Битрикс24 без поддержки сортировки (например список объектов хранилища) запись на границе страниц могла сместиться между запросами соседних страниц и попасть в обе — при limit > 50 в выдаче появлялся дубль, занимавший слот, и клиент обрабатывал одну и ту же запись дважды.

Стало

После склейки всех страниц выдача дедуплицируется по id (сохраняется первое вхождение). Для методов со стабильной сортировкой ничего не меняется (дублей нет — операция вхолостую).

FIX-0714-34: список полей шаблона реквизитов больше не пустеет; создание не возвращает чужую запись

Было

GET /v1/requisite-presets/:presetId/fields мог вернуть 200 с пустым data: [], хотя в шаблоне есть поля: метод Битрикс24 отдаёт result то массивом [{…}], то объектом-картой {"0":{…},"1":{…}}, а обработчик принимал только массив. При создании поля (POST …/fields) ответ мог содержать ЧУЖУЮ существующую запись — эхо созданной строки читалось по id из ответа add, а на некоторых порталах чтение по этому id возвращало другое поле.

Стало

Список нормализует обе формы ответа Битрикс24 (массив и объект-карту) — поля больше не теряются. Эхо созданной записи возвращается только если её fieldName совпадает с тем, что создавали; при любом расхождении ответ содержит { id } (сам объект не подставляется), чтобы клиент не получил постороннюю строку.

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

Идентификатор поля шаблона (id) — это позиционный идентификатор Битрикс24: он может меняться после операций записи в шаблоне и на части порталов не является устойчивым ключом. Не кэшируйте id между изменениями шаблона — перечитывайте список полей перед get/update/delete по конкретному полю.

FIX-0714-35: привязка плейсмента для ранее созданных приложений больше не отклоняется по обработчику

Было

POST /v1/placements/bind мог вернуть 400 с кодом PLATFORM_HANDLER_UNRESOLVABLE для приложения, созданного до перехода на единый платформенный обработчик (у такого приложения в качестве обработчика сохранился его собственный технический адрес). Привязка отклонялась даже тогда, когда платформенный обработчик /v1/bitrix-handler был доступен, — приложение нельзя было опубликовать заново через API.

Стало

Привязка проходит: обработчик плейсмента регистрируется на платформенный /v1/bitrix-handler, а в ответе появляются handlerRewritten: true и requestedHandler с исходным значением. Код PLATFORM_HANDLER_UNRESOLVABLE теперь возвращается только когда платформенный обработчик действительно недоступен. POST /v1/placements/unbind снимает такой плейсмент по тому же адресу.