[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-changelog":3,"docs-tabs-changelog":6},{"content":4,"lastmod":5},"# Журнал изменений API Вайбкод\n\nИстория изменений API Вайбкод: новые возможности, исправления и изменения с потерей обратной совместимости. Записи расположены от новых к старым.\n\n## Префиксы записей\n\n- **NEW** — новая возможность: новый эндпоинт, новое необязательное поле или параметр, новый код ошибки в новом сценарии. Прежние запросы клиентов продолжают работать.\n- **FIX** — исправление поведения. Ответ меняется на корректный, действий со стороны клиента не требуется.\n- **BC** — изменение с потерей обратной совместимости. Требует действий со стороны клиента. Старый формат поддерживается указанный срок, затем прекращается.\n\nФормат кода записи: `{ТИП}-{ММДД}-{N}`, где `ММДД` — дата публикации, `N` — сквозной номер в рамках даты.\n\nЧасть записей `NEW` помечена **в процессе раскатки**: метод вышел в конкретном обновлении Битрикс24 и доступен не на всех порталах. Пока обновление не приехало на портал, вызов возвращает `422 METHOD_NOT_YET_AVAILABLE` с целевой версией — это признак раскатки, а не ошибка интеграции.\n\n## 2026-07-27\n\n### NEW-0727-1: фавикон приложения одной строкой — \u002F_gw\u002Ficon\n\nФавикон во вкладке браузера теперь ставится одной статической строкой без id сервера:\n\n```html\n\u003Clink rel=\"icon\" href=\"\u002F_gw\u002Ficon\">\n```\n\n`\u002F_gw\u002Ficon` — платформенный путь на домене приложения; он всегда отдаёт текущую загруженную иконку. Одна загрузка [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Ficon](\u002Fdocs\u002Finfra\u002Fapp-icon) управляет и карточкой в каталоге Bitrix24, и фавиконом: перезалили иконку — фавикон обновится сам (~5 минут), пересобирать приложение не нужно. Свой статический файл иконки в приложение класть больше не нужно. Строка совместима с созданием приложения одним запросом (id не требуется).\n\nДополнительно ответ [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) и создания приложения одним запросом [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers) теперь возвращает запись в `warnings[]`, если иконка ещё не загружена — с точным эндпоинтом для загрузки (иконка грузится отдельным запросом, id известен только после создания). Тот же `warnings[]` по-прежнему подсказывает, если не заданы `displayName`\u002F`description`. Успешный деплой без иконки или названия больше не выглядит завершённым молча.\n\n### NEW-0727-2: source-at-create: ошибка SOURCE_AT_CREATE_GALAXY_ONLY теперь несёт подсказку с путём к галактике\n\nОтказ [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) с `source`, который не удалось разместить в галактике (на портале в режиме «обе стратегии» без открытого galaxy-хоста, либо на портале только со standalone), теперь дополнительно несёт `error.hint` — с понятным путём: как получить galaxy-хост и\u002Fили как задеплоить в два шага на выделенный сервер. Код и текст ошибки не изменились.\n\n### FIX-0727-3: galaxy-приложения: PATCH \u002Fsleep и PATCH \u002Fport теперь отвечают 400 — управляйте ими со страницы «Галактики»\n\n**Было**\n\nДля приложения, размещённого в галактике (`GALAXY_APP`), [PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsleep](\u002Fdocs\u002Finfra\u002Flifecycle\u002Fsleep) возвращал `200` и записывал `sleepAfterMinutes`, а [PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fport](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fport) отвечал 404\u002F409 — расходясь с задокументированным контрактом `\u002Fv1\u002Fme` (`deployment.galaxyApp`), где ни одно V1-действие жизненного цикла к galaxy-приложению не применяется.\n\n**Стало**\n\nОба вызова для galaxy-приложения отвечают `400` с `error.code = \"GALAXY_APP_USE_GALAXY_ROUTE\"` и ничего не меняют. Настраивайте авто-сон приложения через маршрут галактики; порт у galaxy-приложения закреплён за хостом и не задаётся. Для обычных (standalone) серверов поведение `\u002Fsleep` и `\u002Fport` не изменилось.\n\n### FIX-0727-4: galaxy-приложение восстанавливается после обрыва туннеля хоста\n\n**Было**\n\nЕсли у galaxy-хоста обрывался защищённый туннель (сервер в статусе `RUNNING`, но связь потеряна), развёртывание и выполнение команд galaxy-приложения возвращали `502 GALAXY_HOST_UNREACHABLE`, а запрос логов — пустой ответ с подсказкой. Хост оставался недостижим до ручного ремонта: повтор того же запроса упирался в ту же ошибку сколь угодно долго.\n\n**Стало**\n\nПлатформа теперь сама восстанавливает туннель хоста в фоне, не задерживая ответ. Повтор того же запроса проходит, как только хост переподключается (обычно в течение минуты). Параллельные развёртывания\u002Fкоманды на один общий хост не запускают дублирующее восстановление.\n\n**Влияние на интеграторов**\n\nКод ошибки и форма ответа не изменились — `502 GALAXY_HOST_UNREACHABLE` (для развёртывания и выполнения команд) по-прежнему помечен как повторяемый, а логи по-прежнему отдают пустой список с подсказкой. Изменилось только то, что теперь повтор приводит к успеху, а не к вечной ошибке. Продолжайте повторять запрос по своей обычной политике на этот код и на пустой ответ логов.\n\n**Затронутые эндпоинты:** [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy), [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fexec](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fexec), [GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Flogs](\u002Fdocs\u002Finfra\u002Fdeploy\u002Flogs)\n\n### NEW-0727-5: предупреждение, когда changelog деплоя некуда опубликовать\n\nОтвет [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) теперь возвращает запись в `warnings[]`, если в запросе передан `changelog`, а деплой не создал новую версию исходников. Заметка о релизе привязана к версии, поэтому без неё текст никуда не сохраняется и в ленту канала приложения в мессенджере Bitrix24 не уходит.\n\nПричина видна в поле `source` того же ответа: хранилище исходников выключено (`feature-disabled-platform` или `feature-disabled-portal`), сохранение не удалось (`save-failed`) либо загруженные байты совпали с предыдущей версией. Раньше такой деплой отвечал обычным успехом, и узнать, что заметка потерялась, было нечем. Предупреждение приходит и в JSON-режиме, и в событии `done` при `?stream=true`.\n\n### FIX-0727-6: Список bizproc-templates без явного select возвращает все поля, включая id\n\n**Было**\n\n`GET \u002Fv1\u002Fbizproc-templates` и `POST \u002Fv1\u002Fbizproc-templates\u002Fsearch` без явного `select` возвращали только поле `documentType`. Без `id` клиент не мог выполнить последующие `update`\u002F`delete` — список был бесполезен без второго запроса с явным `select`.\n\n**Стало**\n\nОба вызова без `select` возвращают полный набор объявленных полей записи (`id`, `moduleId`, `entity`, `documentType`, `autoExecute`, `name`, `description`, `modified`, `isModified`, `userId`). Явный `select` работает как прежде. Форма запроса не изменилась.\n\n### NEW-0727-7: загрузка файла в документ Базы знаний возвращает assetMarkdown сразу\n\nЗагрузка файла через [POST \u002Fv1\u002Fnote\u002Fdocuments\u002F{documentId}\u002Ffiles](\u002Fdocs\u002Fnote\u002Ffiles\u002Fupload) раньше отдавала только `{ id }`, поэтому за готовым блоком для вставки в документ приходилось идти вторым запросом в `GET \u002Fv1\u002Fnote\u002Fdocuments\u002F{documentId}\u002Ffiles\u002F{id}` либо собирать разметку `[[image fileId=N]]` руками.\n\nТеперь ответ несёт весь объект файла — `id`, `documentId`, `name`, `size`, `mimeType`, `assetType`, `assetMarkdown` — то есть ту же форму, что отдаёт GET. Загрузили картинку, взяли `assetMarkdown` из ответа, вставили в текст документа и вызвали PATCH — второй запрос больше не нужен.\n\nИзменение аддитивное: поле `id` осталось на месте и с тем же значением, поэтому клиент, который читает только его, продолжает работать без правок. Если конкретный портал вернёт объект без `assetMarkdown`, лишних полей платформа не придумывает — в ответе будет то, что пришло от Битрикс24.\n\n### FIX-0727-8: границы `ttlSeconds` у токенов доступа в машинной схеме совпали с поведением\n\n**Было**\n\nСхема OpenAPI для [POST \u002Fv1\u002Finfra\u002Fservers\u002F{id}\u002Faccess-tokens](\u002Fdocs\u002Finfra\u002Faccess-tokens\u002Fcreate) обещала `ttlSeconds` в диапазоне от 60 секунд до 30 суток. Платформа же с самого начала принимала от 300 секунд до 315 360 000 (десять лет) и отклоняла всё за этими пределами кодом `400 INVALID_TTL`. Поэтому клиент или генератор клиентских библиотек, взявший минимум прямо из схемы, получал жёсткий отказ на значении, которое схема сама и предлагала, а вариант «Бессрочно» из интерфейса выглядел недоступным через API. Текстовая документация всё это время была верна — расходилась только машинная схема.\n\n**Стало**\n\nСхема берёт границы и значение по умолчанию из тех же констант, которыми проверяется запрос, поэтому разойтись им больше нечем: минимум 300, максимум 315 360 000, по умолчанию 86 400.\n\n**Влияние на интеграторов**\n\nПоведение эндпоинта не менялось — менялось только то, что о нём написано в машинной схеме. Если вы генерировали клиента по OpenAPI и он валидировал `ttlSeconds` на своей стороне, перегенерируйте его: прежний клиент отклонял бы корректные значения больше 30 суток и разрешал бы заведомо отказные меньше 300 секунд.\n\n### NEW-0727-9: Приложение в плейсменте может авто-ресайзить свой iframe\n\nПриложение, встроенное в плейсмент, теперь может сообщать платформе высоту своего контента, и платформа растит iframe под неё — раньше высоту фиксировал Битрикс24 и высокий контент обрезался. Приложение постит сообщение родительскому окну: `window.parent.postMessage({ type: 'vibe:resize', height: \u003Cпиксели> }, '*')`. Принимается тип `vibe:resize` или `vibe:setHeight` с числовым полем `height`; `targetOrigin` должен быть `'*'` — браузер сверяет его с непосредственным родителем окна. Пересчитывайте высоту при изменении контента, например через `ResizeObserver`. Полное описание и рекомендации — раздел «Авто-высота iframe» руководства по рантайму приложения.\n\nФункция активируется на стороне платформы по аккаунтам Битрикс24; если ресайз пока не срабатывает, для вашего аккаунта она ещё не активна.\n\n### FIX-0727-10: offset в списках считается по записям, а не по страницам\n\n**Было**\n\nБитрикс24 отдаёт списки страницами по 50 и трактует смещение как номер страницы, а не как число записей. Мы передавали `offset` как есть, поэтому он молча округлялся вниз до кратного 50: `offset=0`, `offset=7` и `offset=49` возвращали одну и ту же первую страницу — без ошибки и без предупреждения. Обход выборки шагом меньше 50 записей зацикливался на первой странице, а шаг ровно в 50 работал и создавал впечатление, что параметр исправен.\n\n**Стало**\n\n`offset` считается по записям на generic-списках: `GET \u002Fv1\u002F{entity}`, `POST \u002Fv1\u002F{entity}\u002Fsearch`, `POST \u002Fv1\u002Fbatch` (действие `list`) и `GET \u002Fv1\u002F{entity}\u002F{id}\u002Factivities`. `offset=7` начинает выборку с 8-й записи. Смещение и `limit` независимы: `?limit=2&offset=51` вернёт ровно две записи, начиная с 52-й. Битрикс24 по-прежнему отдаёт страницами по 50 — Вайбкод запрашивает страницу, покрывающую нужную позицию, и отбрасывает лишнее начало; цена — не более одной дополнительной страницы у Битрикс24 на запрос.\n\nЗаодно исправлены два следствия. `meta.hasMore` учитывает смещение: раньше у сущностей, чей список Битрикс24 отдаёт под именованным ключом — сделки, контакты, компании, лиды, задачи, заказы, товары, счета и другие, всего два десятка, — он сравнивал только длину страницы с общим количеством и оставался `true` на последней странице при ненулевом `offset`. `GET \u002Fv1\u002F{entity}\u002F{id}\u002Factivities` теперь уважает `limit`: метод Битрикс24 не принимает ограничение выборки, поэтому раньше приходила вся страница целиком независимо от запрошенного значения.\n\nЕсли выборка на запрошенной позиции оказалась пустой из-за фильтрации на стороне Битрикс24, ответ несёт `meta.warnings` с кодом `OFFSET_BEYOND_FETCHED_PAGE` — раньше это выглядело как пустой список без объяснения.\n\nДля глубокой навигации по большим выборкам курсор по ключу (`filter[>id]` с `order[id]=asc`) по-прежнему надёжнее смещения: он не зависит от глубины и устойчив к параллельным изменениям.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно: смещение, кратное 50, работает как раньше, и код, который его так и использовал, продолжит работать без правок.\n\nДва момента стоит проверить. Первый — `GET \u002Fv1\u002F{entity}\u002F{id}\u002Factivities` с явным `limit`: раньше приходила вся страница (до 50 записей) независимо от значения, теперь придёт ровно запрошенное количество. Если код полагался на то, что за один вызов вернётся больше запрошенного, увеличьте `limit` или пройдите выборку постранично. Второй — обход выборки шагом меньше 50: раньше он зацикливался на первой странице, теперь идёт вперёд, поэтому цикл, который «выручал» лишний выход по счётчику, начнёт возвращать новые записи.\n\nОстаточные исключения, где смещение НЕ построчное. Во-первых, четыре сущности generic-слоя, у которых метод Битрикс24 округляет смещение до границы страницы, а расширить выборку сверх запрошенного размера нельзя, не рискуя потерять записи: `calendar-events`, `calendar-sections`, `telephony-lines`, `workgroups`. Смещение там осталось прежним. Во-вторых, отдельные эндпоинты с собственными обработчиками, которые в это изменение не входили: `\u002Fv1\u002Fwarehouses`, `\u002Fv1\u002Fbookings`, `\u002Fv1\u002Fposts`, `\u002Fv1\u002Frequisite-links`, `\u002Fv1\u002Flists`, `\u002Fv1\u002Ftimeline-logs` и история стадий в `\u002Fv1\u002Fcrm-extras`. Их поведение не изменилось.\n\nУ четырёх сущностей generic-слоя выше смещение осталось прежним, но `meta.hasMore` у них стало точнее: раньше на последней странице при ненулевом смещении он мог сказать «больше нет», хотя записи оставались.\n\n### FIX-0727-11: статус закрытого тикета обратной связи больше не показывается как «в работе»\n\n**Было**\n\nТикет, попавший под детекцию probe-кампании, показывался автору со статусом `NEW` независимо от того, что с ним реально происходило. Фильтр списка работает по настоящему статусу, поэтому закрытый тикет одновременно попадал во вкладку «Решено» и рисовался бейджем «в работе» — один и тот же тикет противоречил сам себе. Затронуты [GET \u002Fv1\u002Ffeedback](\u002Fdocs\u002Ffeedback) и `GET \u002Fv1\u002Ffeedback\u002F{id}`.\n\n**Стало**\n\nМаскируется только то состояние, для которого маска и заводилась: авто-архив. Закрытый тикет отдаёт `RESOLVED`, тикет в работе — свой реальный статус, а авто-архив по-прежнему приходит как `NEW`. Ключи с доступом к обратной связи (management, скоуп `vibe:feedback`) как и раньше видят настоящий статус.\n\n**Влияние на интеграторов**\n\nКлиент, который читал `status` и ожидал `NEW` у такого тикета, теперь получит его фактический статус — это и есть исправление. Дополнительно: у тикета, отмеченного детекцией и уже закрытого, отзыв (`PATCH {\"status\":\"WITHDRAWN\"}`) и ответ автора теперь отклоняются с `409 FEEDBACK_CLOSED`, как у любого закрытого тикета; раньше они проходили, потому что гейт сверялся с замаскированным статусом.\n\n### FIX-0727-12: Connect-ключи: сохранённые права авторитетны — vibe:ai \u002F vibe:search больше не добавляются автоматически\n\n**Было**\n\nКлюч, выданный через VibeCode Connect, при каждом запросе автоматически получал платформенные права `vibe:ai` и `vibe:search`, даже если они не запрашивались и не были согласованы. Такой ключ мог обращаться к AI-эндпоинтам (`\u002Fv1\u002Fchat\u002Fcompletions`, `\u002Fv1\u002Fai\u002F*`) и поиску (`\u002Fv1\u002Fsearch`), и расход шёл со счёта аккаунта, к которому привязан портал.\n\n**Стало**\n\nСохранённые на ключе права теперь авторитетны — платформа не расширяет их автоматически. Ключ, выданный через Connect, обращается к AI- и поиск-эндпоинтам только если соответствующее право реально присутствует в ключе; иначе ответ `403`. Ротация ключа сохраняет этот признак. Обычные ключи, созданные в кабинете, поведения не меняют.\n\n### FIX-0727-13: Приложения: производный ключ и синхронизация прав не выдают больше, чем есть у вызывающего ключа\n\n**Было**\n\nВызов `POST \u002Fv1\u002Fapps` авторитетным ключом (выданным через VibeCode Connect или производным от него) минтил парный ключ приложения с полным набором платформенных прав по умолчанию (`vibe:infra`, `vibe:ai`, `vibe:search`, `vibe:storage`) — даже если у самого вызывающего ключа этих прав не было. Так же `PATCH \u002Fv1\u002Fapps\u002F:id` мог записать в объявление приложения `vibe:*`-право, которого у ключа нет. В обоих случаях производный ключ получал возможность обращаться к AI и поиску за счёт аккаунта, к которому привязан портал.\n\n**Стало**\n\nПроизводный ключ получает ровно те платформенные права, что реально есть у вызывающего ключа. Если у авторитетного ключа права нет, запрос с ним в теле возвращает `403` `SCOPE_GRANT_REQUIRES_CONSENT`, а список несогласованных прав приходит в `error.details.unconsented`. Права, которые у ключа есть (например согласованный `vibe:storage`), проходят как раньше. Срок жизни производного ключа наследуется от вызывающего. Синхронизация прав приложения на парные ключи больше никогда не добавляет `vibe:*` авторитетному ключу — сужение прав при этом по-прежнему применяется. Обычные ключи, созданные в кабинете, поведения не меняют: платформенные права им по-прежнему выдаются по умолчанию.\n\n### FIX-0727-14: таймаут вызова Битрикс24 стал управляемым, внутренний повтор после таймаута отменён\n\n**Было**\n\nПлатформа всегда обрывала HTTP-вызов к Битрикс24 на 15-й секунде, а для методов чтения после обрыва делала одну внутреннюю повторную попытку — итого до ~30 секунд до ответа `503 BITRIX_TIMEOUT`. Повтор при этом запускал второе параллельное выполнение того же вызова на портале: обрыв соединения не останавливает работу Битрикс24 над запросом.\n\n**Стало**\n\n- Лимит времени одного вызова к Битрикс24 настраивается платформой (по умолчанию прежние 15 секунд). На порталах, где Битрикс24 отвечает медленно, платформа может поднять лимит — запросы, которые раньше стабильно завершались `503 BITRIX_TIMEOUT`, доживают до реального ответа и возвращают данные.\n- Внутренняя повторная попытка после таймаута отменена для всех методов. `503 BITRIX_TIMEOUT` на чтениях приходит примерно вдвое быстрее (~15 секунд вместо ~30), и запрос больше не выполняется на портале дважды. Повторы по `429` (rate limit) не тронуты.\n- Текст ошибки `Bitrix24 did not respond within 15s` подставляет фактический лимит (например, `within 60s`) — не опирайтесь на константу в тексте.\n\n## 2026-07-25\n\n### NEW-0725-1: deploy: необязательное поле changelog\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) принимает новое необязательное поле `changelog` — обычный текст до 2000 символов с описанием, что изменилось в этой версии. При выходе новой версии приложения текст публикуется подписчикам в ленту канала приложения в мессенджере Bitrix24; если поле не передано, в ленту уходит только номер версии. Поле доступно и в JSON-, и в multipart-режиме деплоя. Прежние вызовы деплоя работают без изменений.\n\n### NEW-0725-2: вызовы моделей по короткоживущему токену партнёрской системы\n\n**Было**\n\nРучки AI-роутера `\u002Fv1\u002Fchat\u002Fcompletions`, `\u002Fv1\u002Fembeddings`, `\u002Fv1\u002Faudio\u002Ftranscriptions` и `\u002Fv1\u002Fmodels` принимали только обычный ключ платформы.\n\n**Стало**\n\nТе же ручки (и их `\u002Fv1\u002Fai\u002F...`-алиасы) дополнительно принимают короткоживущий токен нового типа. Его получает партнёрская система Битрикс24 по своему подписанному каналу; токен привязан к порталу и конкретному сотруднику, живёт один час и допущен ровно к этим восьми маршрутам — на любом другом пути ответ `SCOPE_FORBIDDEN`. Расход по такому токену считает сама платформа и списывает синхронно в AI-квоту портала, поэтому при исчерпании квоты вызов отбивается ещё до обращения к модели. Доступны только модели, включённые в программу квоты — перечень отдаёт `\u002Fv1\u002Fmodels` под этим же токеном.\n\nКоды отказа, которые теперь может вернуть этот маршрут: `TOKEN_INVALID`, `SCOPE_FORBIDDEN`, `MODEL_NOT_IN_QUOTA_PROGRAM`, `CREDENTIAL_NOT_PLATFORM`, `ACCOUNT_FROZEN`, `PORTAL_DELETED`, `PORTAL_BLOCKED`, `PORTAL_SUSPENDED`. Конверт прежний: `success` и `error` с полями `code` и `message`.\n\nПоведение обычных ключей платформы не изменилось: без токена нового типа ответы прежние, байт в байт.\n\n## 2026-07-24\n\n### FIX-0724-1: дела: нераспознанное имя поля фильтра теперь отклоняется с 400 UNKNOWN_FILTER_FIELD\n\n**Было**\n\n`GET \u002Fv1\u002Factivities` и `POST \u002Fv1\u002Factivities\u002Fsearch` молча отбрасывали неизвестный ключ фильтра: запрос возвращал `200 success` с фильтром, урезанным до пустого — то есть отдавал весь (owner-scoped или вообще весь) набор дел. Например `{\"filter\":{\"ownerTypeId\":2,\"ownerId\":3,\"bogusField\":123}}` игнорировал `bogusField` и возвращал все дела родительской сделки. Это расходилось с документацией и с поведением других сущностей CRM (компании, счета), где такой фильтр уже отклонялся.\n\n**Стало**\n\nИмя поля фильтра, которого нет в схеме дела — и которое не является пользовательским полем `UF_*`, ключом `id` или спец-токеном — теперь отклоняется до вызова Bitrix24 с `400 UNKNOWN_FILTER_FIELD` и списком доступных полей, как уже делают companies\u002Fquotes\u002Fcontacts. Полный список фильтруемых полей возвращает `GET \u002Fv1\u002Factivities\u002Ffields`.\n\n**Влияние на интеграторов**\n\nФильтрация по реальным полям дела (в camelCase или в родном ВЕРХНЕМ регистре Bitrix24), по полям `UF_*`, операторы (`>=`, `\u003C`, `!` и т.п.), диапазоны и AND\u002FNOT работают как прежде. Если вы полагались на молчаливый сброс нераспознанного ключа — уберите его из фильтра.\n\n### NEW-0724-2: \u002Ffields бизнес-процессных действий и роботов отдаёт названия и описания полей\n\n**Было**\n\n`GET \u002Fv1\u002Fbizproc-activities\u002Ffields` и `GET \u002Fv1\u002Fbizproc-robots\u002Ffields` описывали каждое поле только типом и флагом readonly, без человекочитаемых подписей.\n\n**Стало**\n\nПо каждому из 12 полей теперь приходят `label` и `description` на языке сегмента, что упрощает построение форм и подсказок. Типы полей не изменились.\n\n### FIX-0724-3: Скачивание своих файлов из хранилища больше не возвращает 403\n\n**Было**\n\n`GET \u002Fv1\u002Fstorage\u002Fobjects\u002F:key` мог вернуть `403` при обращении к объекту, которым владелец ключа законно владеет, но который физически лежит под префиксом другого «семейства» хранилища (например, файлы сервера, видимые в списке разработчика). Объект показывался в списке, но скачать его, получить presigned-ссылку или сделать `HEAD` не удавалось.\n\n**Стало**\n\nОбласть доступа временных ключей учитывает фактическое семейство объекта (портал при этом остаётся привязан к контексту вызывающего), поэтому скачивание (`?download`), потоковая отдача (`?inline`) и `HEAD` для собственных объектов работают независимо от семейства. Проверка владения не изменилась — по чужому объекту по-прежнему приходит `404`.\n\n### FIX-0724-4: GET \u002Fv1\u002Fworkflows учитывает параметр limit\n\n**Было**\n\n`GET \u002Fv1\u002Fworkflows` принимал `limit`, но молча его игнорировал — всегда возвращалась целая страница запущенных бизнес-процессов (до 50), сколько бы ни запросили.\n\n**Стало**\n\n`limit` уважается: в ответе не больше запрошенного числа записей. Значения больше 50 набираются постранично (потолок — 500); `meta.total` по-прежнему показывает общее число запущенных процессов.\n\n### FIX-0724-5: деплой: приложение в оборачивающей папке архива больше не падает с ENOENT package.json\n\n**Было**\n\nДеплой (`POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy`) на standalone-сервер архива, в котором проект завёрнут в единственную папку верхнего уровня (например, `myapp\u002Fpackage.json` вместо `package.json` в корне), падал на шаге установки:\n\n```\nnpm error enoent Could not read package.json ... open '\u002Fopt\u002Fapp\u002Fpackage.json'\n```\n\nЗагруженный архив оставался соседом распакованного содержимого, поэтому авто-выравнивание единственной оборачивающей папки не срабатывало (в корне оказывалось две записи — архив и папка), и `package.json` оставался вложенным.\n\n**Стало**\n\nЗагруженный архив удаляется до шага выравнивания, поэтому единственная оборачивающая папка «схлопывается», `package.json` оказывается в корне деплоя, и установка проходит штатно.\n\n**Влияние на интеграторов**\n\nДействий не требуется. Плоские архивы (файлы в корне архива) работают как прежде; для гарантии можно паковать плоско: `tar -czf build.tar.gz -C \u003Cпапка_проекта> .`.\n\n### FIX-0724-6: эндпоинты \u002Fv1\u002Fusers* перестали возвращать 403 и 500 на ключах с доступом user\n\n**Было**\n\nНа ключе только для чтения (READONLY) с доступом `user` вызов `GET \u002Fv1\u002Fusers\u002Fme` возвращал `403 WRITE_BLOCKED_READONLY_KEY`, хотя это ридовый эндпоинт. Отдельно `GET \u002Fv1\u002Fusers`, `GET \u002Fv1\u002Fusers\u002F:id`, `POST \u002Fv1\u002Fusers\u002Fsearch` и `GET \u002Fv1\u002Fusers\u002Ffields` возвращали `500 INTERNAL_ERROR` на порталах, где у одного из пользовательских полей (`UF_*`) пустое определение.\n\n**Стало**\n\n`GET \u002Fv1\u002Fusers\u002Fme` работает на ключе только для чтения. Остальные `\u002Fv1\u002Fusers*` возвращают данные и пропускают поле с пустым определением вместо падения.\n\n**Влияние на интеграторов**\n\nДействий не требуется — прежние вызовы продолжают работать, а ранее падавшие сценарии теперь отвечают корректно.\n\n### NEW-0724-7: доставка callback bizproc-активити и роботов на Black Hole-приложение\n\nРегистрация bizproc-активити или робота с `handler`, указывающим на ваш деплой-сервер Black Hole, теперь приводит к надёжной доставке callback выполнения (с токеном события, блоком авторизации, кодом и свойствами) в приложение. Раньше такой callback мог не дойти: онлайн-события Битрикс24 не повторяются, а спящий или просыпающийся сервер терял вызов. Платформа перехватывает `handler` при регистрации, ставит вызов в устойчивую очередь и повторяет доставку с будильником сервера и откатами.\n\nЗатрагивает [POST \u002Fv1\u002Fbizproc-activities](\u002Fdocs\u002Fentities\u002Fbizproc-activities) и [POST \u002Fv1\u002Fbizproc-robots](\u002Fdocs\u002Fentities\u002Fbizproc-robots) (а также их изменение). Новый код ошибки `SERVER_APP_MISMATCH` (400): сервер Black Hole за указанным `handler` должен принадлежать тому же приложению, что регистрирует активити. Регистрация такого `handler` через `\u002Fv1\u002Fbatch` не поддерживается — используйте одиночный запрос (`BIZPROC_CALLBACK_BATCH_UNSUPPORTED`). Кроме того, `POST \u002Fv1\u002Fbizproc-robots` теперь заранее требует `code`, `name` и `handler` — при их отсутствии возвращается `400 MISSING_REQUIRED_FIELDS` вместо сырой ошибки Битрикс24 (как уже было у активити).\n\nВозможность раскатывается постепенно и включается по аккаунтам: до включения на вашем аккаунте регистрация проходит как прежде, без управляемой доставки. **После включения меняется поведение batch-регистрации:** попытка зарегистрировать BH-`handler` через `\u002Fv1\u002Fbatch` начинает отклоняться (`BIZPROC_CALLBACK_BATCH_UNSUPPORTED`) — переведите такие регистрации на одиночный `POST \u002Fv1\u002Fbizproc-activities` или `\u002Fv1\u002Fbizproc-robots`.\n\n## 2026-07-23\n\n### NEW-0723-1: библиотека чертежей приложений — ТЗ по API-ключу\n\nГотовые технические задания популярных приложений теперь доступны по ключу: `GET \u002Fv1\u002Fapp\u002Fblueprints\u002F:slug?locale=ru|en` возвращает сырой markdown ТЗ (`Content-Type: text\u002Fmarkdown`). Эндпоинт требует `Authorization: Bearer \u003Cключ>`; неизвестный или скрытый чертёж — `404 BLUEPRINT_NOT_FOUND`. Ссылку на ТЗ несёт копируемый «Промт для AI» при создании ключа — AI-агент скачивает ТЗ тем же ключом. Прежний анонимный путь `\u002Fapi\u002Fpublic\u002Fblueprints\u002F:slug.md` удалён.\n\n### NEW-0723-2: исходники удалённого сервера: доступ, уборка и честный ответ на вычищенные байты\n\nИсходники переживают сервер — это давняя гарантия платформы, но добраться до них через API было нельзя: весь набор `\u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources*` отвечал `404` на удалённый сервер, поэтому владелец не мог ни посмотреть свои версии, ни снять с них тег, ни удалить. Для версии с тегом `published` или `manual` это был тупик: снятие тега — единственный разрешённый способ обойти `409 PROTECTED_BY_TAG`, а именно оно и было недоступно.\n\nТеперь на удалённом сервере работают чтение и уборка: список версий, метаданные версии, скачивание, `tag`, `PATCH`, `DELETE` и `cleanup`. Сохранение новых версий (`POST \u002Fsources`) по-прежнему отвечает `404` — мёртвый сервер новых депозитов не принимает.\n\nЧтобы удалённый сервер вообще можно было найти, [GET \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Flist) принимает `?includeDeleted=true`. По умолчанию выдача не меняется. В каждой строке появилось поле `deletedAt` (`null` у живых серверов).\n\nОтдельно: версия, чьи байты уже вычищены из хранилища, теперь отвечает `410` с кодом `SOURCE_VERSION_BYTES_PURGED` вместо `404`. Разница существенная — `404` утверждал, что версии нет, тогда как запись о ней жива, а восстановление другое: перезалить архив, а не искать его в другом месте. Код приходит на скачивании и на деплое по `{\"source\": {\"versionId\": \"vN\"}}`.\n\n**Затронутые эндпоинты:** [GET \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Flist), [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy), `GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources`, `GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources\u002F:versionId\u002Fdownload` — контракт исходников описан на странице [Хранилище исходников](\u002Fdocs\u002Fsource-storage)\n\n### FIX-0723-3: деплой galaxy-приложения: обрыв туннеля при сборке больше не маскируется под «host unreachable»\n\n**Было**\n\nЕсли во время сборки на galaxy-хосте моргал туннель и здорового контейнера этого деплоя в итоге не оказывалось (сборку прервало, контейнер не поднялся, канал exec был занят или приложение крашилось), деплой (`POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy`) возвращал `502 GALAXY_HOST_UNREACHABLE` с советом «повторите, когда хост переподключится». Хост при этом часто был доступен — совет вводил в заблуждение, а вызывающая сторона не видела реальную причину (например, что её приложение падает при старте).\n\n**Стало**\n\nЕсли после моргания туннеля хост доступен, но деплой не довёл приложение до рабочего состояния, деплой возвращает новый код `502 GALAXY_DEPLOY_INTERRUPTED` — «хост доступен, но деплой прервался до старта приложения: отправьте тот же деплой повторно; если приложение раз за разом не стартует, сначала почините его (команду запуска, порт, зависимости, переменные окружения или лимит памяти)». Это по-прежнему повторяемая ошибка — слот цел, удалять и пересоздавать его не нужно. Код `GALAXY_HOST_UNREACHABLE` теперь остаётся только для действительно недоступного хоста (ни одна попытка проверки до него не достучалась). Если приложение действительно крашится в цикле, это надёжно выявляется уже на повторном деплое обычной проверкой живости (код `GALAXY_APP_START_FAILED`).\n\n### NEW-0723-4: подсказка в ошибке таймаута шага деплоя\n\nОшибка `DEPLOY_TIMEOUT` у [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) теперь несёт структурированное поле `error.hint` (`reason`, `recovery`, `recoveryAction`), привязанное к зависшему шагу (`error.step`). Для команд пользователя (`install`, `preStart`) подсказка объясняет, что команда не завершилась в отведённое время, и советует сделать её неинтерактивной и завершающейся, а долгоживущие сервисы запускать из команды `start` или в фоне (`docker compose up -d`). Для шага установки рантайма (`runtime` — платформенный шаг, а не команда пользователя) и прочих служебных шагов подсказка указывает на возможный обрыв туннеля и на `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Frepair`. Поле аддитивное: прежние `error.code`, `error.message` и `error.step` не изменились, менять интеграцию не нужно; подсказка приходит и в JSON-режиме, и в SSE-событии `error`.\n\n### FIX-0723-5: ошибка шага деплоя показывает реальную причину, а не безобидное предупреждение\n\n**Было**\n\nПри падении шага деплоя поле `data.steps[].stderr` (и, как следствие, `error.message`) могло нести только безобидное предупреждение из одного потока, теряя реальную причину сбоя.\n\n**Стало**\n\nОба потока возвращаются вместе, с метками `stderr:` и `stdout:`; реальная причина больше не скрывается. Форма ответа и имя поля не изменились.\n\n### FIX-0723-6: сортировка списка по неуникальному полю больше не теряет записи на второй странице\n\n**Было**\n\nЗапрос списка или поиск с сортировкой по неуникальному полю (например по датовому `begindate`) при выборке больше 50 записей мог молча вернуть меньше записей, чем есть: на границе страницы часть записей с одинаковым значением поля сортировки терялась. Ответ приходил с кодом 200, без признака неполноты. Затронуты сущности на основе CRM smart-process — сделки, лиды, контакты, компании, предложения, счета и элементы смарт-процессов `\u002Fv1\u002Fitems\u002F{entityTypeId}` — на путях `GET \u002Fv1\u002F{entity}`, `POST \u002Fv1\u002F{entity}\u002Fsearch`, в подвызовах `POST \u002Fv1\u002Fbatch` и per-entity `POST \u002Fv1\u002F{entity}\u002Fbatch`. Тот же класс нестабильности затрагивал и числовую агрегацию (`POST \u002Fv1\u002F{entity}\u002Faggregate` с `sum`\u002F`avg`\u002F`min`\u002F`max`\u002F`groupBy`): выборка записей для агрегата шла без порядка, поэтому на объёмах больше 50 записей часть строк могла теряться и искажать результат.\n\n**Стало**\n\nК сортировке автоматически добавляется вторичный ключ по `id` — порядок становится строго определённым, и постраничная выборка не теряет и не дублирует записи независимо от поля сортировки. Пользовательская сортировка остаётся основным ключом; записи с одинаковым значением поля упорядочиваются по `id` по возрастанию. Менять запросы не нужно.\n\n### FIX-0723-7: переоткрытие тикета комментарием больше не оставляет штамп решения\n\n**Было**\n\nКомментарий команды через `POST \u002Fv1\u002Ffeedback\u002F:id\u002Fcomments`, возвращающий тикет из `RESOLVED` или `WITHDRAWN` обратно в активный статус (`NEW`, `REVIEWING`, `AWAITING_USER`, `NEEDS_REVIEW`), не сбрасывал `resolvedAt` и `resolvedBy`. Они «зависали» от прошлого закрытия, и на чтении (`GET \u002Fv1\u002Ffeedback\u002F:id`, `GET \u002Fv1\u002Ffeedback`) переоткрытый тикет выглядел одновременно активным и решённым.\n\n**Стало**\n\nТакой комментарий очищает `resolvedAt` и `resolvedBy` — на чтении активный тикет больше не несёт даты решения. `resolution` при этом не очищается: он отражает текст самого комментария. Комментарий, ставящий `RESOLVED`, по-прежнему проставляет штамп; переход в `ARCHIVED` и обычный переход между активными статусами штамп не трогают. Поведение согласовано с уже действовавшим сбросом на `PATCH \u002Fv1\u002Ffeedback\u002F:id`.\n\n### FIX-0723-8: фильтр списков statuses \u002F departments \u002F storages \u002F currencies \u002F products больше не игнорируется молча\n\n**Было**\n\n`GET \u002Fv1\u002Fstatuses`, `\u002Fv1\u002Fdepartments`, `\u002Fv1\u002Fstorages`, `\u002Fv1\u002Fcurrencies`, `\u002Fv1\u002Fproducts` работают через legacy-методы Битрикс24 (`crm.status.list`, `department.get`, `disk.storage.getlist`, `crm.currency.list`, `crm.product.list`), которые молча игнорируют нефильтруемые ключи и операторы. Неизвестное или неподдерживаемое поле фильтра — например `filter[system]` у статусов, `filter[module]` у хранилищ или `filter[price]` у товаров — а также операторы `$gt` \u002F `$contains` \u002F `$ne` возвращали `200` со всей таблицей. Клиент получал полный набор вместо ожидаемого подмножества — тихий отказ с неверными данными.\n\n**Стало**\n\nДля этих сущностей фильтр проверяется до вызова Битрикс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` у `\u002Fv1\u002Fcurrencies` возвращает `400` с подсказкой отфильтровать на стороне клиента.\n\n### BC-0723-9: POST \u002Fv1\u002Fapps больше не возвращает поля prefix и suffix в ответе на создание\n\n> Поддержка старого формата до: 21.07.2026\n\n**Было**\n\nОтвет POST \u002Fv1\u002Fapps на создание приложения содержал два значения на `vibe_app_` — короткий prefix и полный rawKey. Короткий prefix ошибочно принимали за ключ, и запрос с ним возвращал 401.\n\n**Стало**\n\nОтвет на создание содержит одно значение `vibe_app_` — рабочий rawKey. Поля prefix и suffix остаются в GET \u002Fv1\u002Fapps и GET \u002Fv1\u002Fapps\u002F:id для отображения маскированного ключа.\n\n**Что делать интеграторам**\n\nИспользуйте поле rawKey из ответа на создание как X-Api-Key. Маскированный префикс, если он нужен, берите из GET \u002Fv1\u002Fapps или GET \u002Fv1\u002Fapps\u002F:id вместо ответа на создание.\n\n### FIX-0723-10: POST \u002Fv1\u002Fbatch сохраняет телефон и почту при создании и обновлении лидов и контактов\n\n**Было**\n\nЧерез общий POST \u002Fv1\u002Fbatch значения телефона и почты (мультиполя) при создании или обновлении лида либо контакта терялись. Вызов возвращал успех, но поле не сохранялось. Те же данные через одиночный POST \u002Fv1\u002Fleads или PATCH \u002Fv1\u002Fcontacts\u002F:id и через POST \u002Fv1\u002F{entity}\u002Fbatch сохранялись корректно.\n\n**Стало**\n\nОбщий POST \u002Fv1\u002Fbatch сериализует мультиполя так же, как одиночные вызовы. Телефон и почта сохраняются при создании и обновлении.\n\n### FIX-0723-11: Поиск и research больше не отвечают 402 INSUFFICIENT_BALANCE при положительном балансе\n\n**Было**\n\n`POST \u002Fv1\u002Fsearch` и `POST \u002Fv1\u002Fresearch` с платформенным движком (`bitrix-search`) на персональном ключе могли вернуть `402 INSUFFICIENT_BALANCE` даже при достаточном балансе Vibe на счёте портала. Предварительная проверка баланса искала счёт по владельцу ключа, а счёт с недавних пор один на весь портал — и не находился.\n\n**Стало**\n\nПроверка и списание баланса всегда идут по счёту портала. При положительном балансе запрос выполняется и списывается корректно; `402` возвращается только при реальной нехватке средств. Форма запроса и ответа не изменилась.\n\n### FIX-0723-12: POST \u002Fv1\u002Fapps честнее сообщает о необходимости подписки Маркетплейса на cloud-shared пути\n\n**Было**\n\nПри создании приложения через cloud-shared путь выпуска ключа (единая cloud↔box-модель, раскатка по кольцу порталов) на портале без активной подписки «BitrixGPT + Маркетплейс» [POST \u002Fv1\u002Fapps](\u002Fdocs\u002Fapps\u002Fcreate) возвращал непрозрачный `502 CONNECTOR_APP_INSTALL_FAILED` без указания причины.\n\n**Стало**\n\nОтказ по подписке теперь классифицируется заранее: до обращения к коннектору `POST \u002Fv1\u002Fapps` проверяет авторитетное состояние подписки портала и, если оно отсутствует, сразу возвращает `403` с кодом `B24_MARKET_SUBSCRIPTION_REQUIRED` (или `B24_MARKET_TRIAL_USED`, если демо уже использован), понятным сообщением и ссылкой на оформление в `error.details.upgradeUrl`. Если состояние не авторитетно, тот же результат срабатывает при явном отказе коннектора по подписке. Прочие отказы cloud-shared выпуска (модуль не установлен, доступ запрещён портал-админом, прочие ошибки) классифицируются как прежде.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал `502 CONNECTOR_APP_INSTALL_FAILED` при создании приложения, стоит дополнительно ловить `403 B24_MARKET_SUBSCRIPTION_REQUIRED` \u002F `B24_MARKET_TRIAL_USED` и подсказывать пользователю оформить подписку на портале.\n\n### FIX-0723-13: запуск сервера не отвечает ошибкой, если машина уже работает\n\n**Было**\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fstart](\u002Fdocs\u002Finfra\u002Flifecycle\u002Fstart) для сервера в состоянии `error`\nвызывал запуск машины у облака и любой отказ отдавал как `502 PROVIDER_ERROR`. Если машина к этому\nмоменту уже работала — например, её подняло автоматическое восстановление после вытеснения, — облако\nотвечало отказом «машина уже в состоянии RUNNING», и вызов возвращал ошибку на операции, которая\nфактически удалась. Клиент видел `502` и не мог отличить это от настоящего сбоя.\n\n**Стало**\n\nТакой отказ распознаётся как идемпотентный успех: если машина уже работает или находится в\nпереходном состоянии, вызов возвращает `200` и сервер переходит в `provisioning`, как при обычном\nзапуске. Настоящие отказы — недостаточно прав, исчерпана квота, машина не найдена — по-прежнему\nвозвращают `502 PROVIDER_ERROR`.\n\nЭто тот же критерий идемпотентности, который уже применяли запуск агента и внутренний путь\nпробуждения сервера.\n\n### BC-0723-14: форма deployment.standalone.requiredFields.create в \u002Fv1\u002Fme стала объектом + документирует slug поля name\n\n> Поддержка старого формата до: 22.01.2027\n\n**Было**\n\nВ ответе `GET \u002Fv1\u002Fme` per-kind под-блок `deployment.standalone.requiredFields.create` был массивом `[\"provider\", \"name\", \"plan\", \"region\"]` — формат `name` не указывался; у соседнего `deployment.galaxyApp.requiredFields.create` поле `name` говорило лишь «required». Имя с кириллицей или заглавными буквами при [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) отклонялось с `400 INVALID_REQUEST`, но self-discovery об этом ограничении молчал.\n\n**Стало**\n\n`deployment.standalone.requiredFields.create` теперь объект (как соседний `deployment.galaxyApp.requiredFields.create`), и в обоих под-блоках `name` несёт формат: slug из строчных латинских букв по маске `^[a-z][a-z0-9-]*$`, длина 2–63 символа. Человекочитаемую подпись кладите в необязательное поле `displayName`.\n\n**Что делать интеграторам**\n\nПлоский `deployment.requiredFields[\"POST \u002Fv1\u002Finfra\u002Fservers\"]` (массив `[\"provider\",\"name\",\"plan\",\"region\"]`) НЕ изменился — если вы читаете его, делать ничего не нужно, набор обязательных полей тот же. Если же ваш код парсил per-kind под-блок `deployment.standalone.requiredFields.create` как массив (`.forEach` \u002F `.includes(\"name\")` \u002F `.length` \u002F `[0]`), перейдите на чтение объекта: ключи — имена полей (`provider`\u002F`name`\u002F`plan`\u002F`region`), значения — их описания.\n\n### NEW-0723-15: GET \u002Fv1\u002Fcontacts\u002Ffields получил label и description для всех полей\n\nВ ответе [GET \u002Fv1\u002Fcontacts\u002Ffields](\u002Fdocs\u002Fentities\u002Fcontacts\u002Ffields) теперь у всех 28 статических полей контакта есть человекочитаемые `label` и `description`. Раньше базовые поля (`name`, `lastName`, `typeId` и другие) приходили только с `type` и `readonly`, без описания смысла. Метки локализуются под язык сегмента (на `.tech` — по-русски, на `.com` — по-английски). Семантику поля можно получить программно из ответа, без сверки со статической документацией. Кроме того, `GET \u002Fv1\u002Fopenapi.json` публикует эти метки и описания (по-английски) как `title` и `description` в схемах `Contact` и `ContactInput`.\n\n### NEW-0723-16: smart-processes: поля relations и linkedUserFields во входной схеме\n\nПоля `relations` (связи с сущностями CRM — parent\u002Fchild, например привязка смарт-процесса к сделкам) и `linkedUserFields` теперь объявлены во входной схеме и видны в `GET \u002Fv1\u002Fsmart-processes\u002Ffields`. Их можно передавать в `POST \u002Fv1\u002Fsmart-processes` и `PATCH \u002Fv1\u002Fsmart-processes\u002F:entityTypeId`, чтобы связать смарт-процесс с другими сущностями CRM и вывести его в пользовательских полях. Фильтрация и сортировка по этим полям не поддерживаются — это вложенные структуры записи, а не поля выборки.\n\n### FIX-0723-17: bizproc-activities и bizproc-robots: тип documentType в \u002Ffields исправлен на array\n\n**Было**\n\n`GET \u002Fv1\u002Fbizproc-activities\u002Ffields` и `GET \u002Fv1\u002Fbizproc-robots\u002Ffields` показывали для `documentType` тип `object`, тогда как поле — массив из трёх элементов (`[moduleId, entity, documentType]`), как уже было объявлено у `bizproc-templates`.\n\n**Стало**\n\nТип `documentType` в `\u002Ffields` теперь `array` у всех трёх сущностей — согласованно с реальным контрактом.\n\n### FIX-0723-18: PATCH \u002Fv1\u002Fbizproc-templates возвращает id числом\n\n**Было**\n\n[PATCH \u002Fv1\u002Fbizproc-templates\u002F:id](\u002Fdocs\u002Fentities\u002Fbizproc-templates\u002Fupdate) возвращал `data.id` строкой (`\"1215\"`), тогда как `POST` возвращает число (`1215`). Клиент, сравнивавший id из ответа создания с ответом обновления, получал ложное несовпадение.\n\n**Стало**\n\nОтвет `PATCH` возвращает `data.id` числом (`1215`) — так же, как `POST`.\n\n### FIX-0723-19: userfields: тип label в схеме создания исправлен на string\n\n**Было**\n\nOpenAPI-схема `POST \u002Fv1\u002Fuserfields\u002F{entity}` объявляла `label` как `object`. B24 `crm.\u003Centity>.userfield.add` принимает `LABEL` только строкой, поэтому SDK, сгенерированный по спеке (где `label` — объект), отправлял неверный тип и получал ошибку. OpenAPI-спека — публичный контракт: клиенты генерируют по ней SDK, и у тех, у кого тип был «объект», клиент был сломан.\n\n**Стало**\n\n`label` в схеме создания объявлен как `string` (подпись на языке портала по умолчанию). Мультиязычные подписи задаются через `editFormLabel` \u002F `listColumnLabel` \u002F `listFilterLabel` (PATCH после создания). Рантайм не менялся — правка только генерируемой спеки.\n\n### FIX-0723-20: sleep-now для galaxy-приложения теперь отвечает 400 — управляйте им со страницы «Галактики»\n\n**Было**\n\n`POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsleep-now` для приложения, размещённого в галактике (`GALAXY_APP`), усыплял контейнер и отвечал `200`. Это расходилось с сессионным маршрутом, который такое приложение уже отклонял, и могло рассинхронизировать состояние контейнера с хостом.\n\n**Стало**\n\nТот же вызов для galaxy-приложения отвечает `400` с `error.code = \"GALAXY_APP_USE_GALAXY_ROUTE\"` и не меняет состояние: контейнер остаётся `RUNNING`. Управляйте жизненным циклом приложения через маршруты галактики. Для обычных (standalone) серверов поведение sleep-now не изменилось.\n\n### FIX-0723-21: комментарий задачи больше не отдаётся под чужой задачей\n\n**Было**\n\nНа старых порталах `GET \u002Fv1\u002Ftasks\u002F:taskId\u002Fcomments\u002F:id` возвращал `200` и сам комментарий, даже когда комментарий не принадлежал задаче `:taskId`: один и тот же комментарий отдавался под любой задачей, а поле `taskId` в ответе было простым эхом пути.\n\n**Стало**\n\nПеред выдачей комментарий сверяется с задачей из пути. Если комментарий не принадлежит `:taskId`, эндпоинт отвечает `404` с кодом `NOT_FOUND` и сообщением «Comment not found». Поле `taskId` в ответе теперь совпадает с реальной родительской задачей. Запросы комментария по его настоящей задаче работают как прежде.\n\n### FIX-0723-22: тип компании — единое поле typeId, не companyType\n\n**Было**\n\nИмя поля «тип компании» различалось на слоях. [POST \u002Fv1\u002Fcompanies](\u002Fdocs\u002Fentities\u002Fcompanies\u002Fcreate) с полем `companyType` молча игнорировал тип — компания создавалась с типом по умолчанию; сохранить тип можно было только полем `typeId`. Чтение (`GET`, поиск) всегда возвращало тип в поле `typeId`. Фильтр же принимал `companyType`, но не `typeId`.\n\n**Стало**\n\nТип компании — единое поле `typeId` во всех операциях: создание и изменение, чтение и поиск, фильтр (`filter[typeId]`) и группировка (`groupBy: typeId`). Значения прежние — `CUSTOMER`, `SUPPLIER`, `COMPETITOR` (список: `GET \u002Fv1\u002Fstatuses?filter[entityId]=COMPANY_TYPE`). Поле теперь описано в [GET \u002Fv1\u002Fcompanies\u002Ffields](\u002Fdocs\u002Fentities\u002Fcompanies\u002Ffields).\n\n**Влияние на интеграторов**\n\nУказывайте тип полем `typeId`. Чтение не меняется — тип всегда приходил в `typeId`. На создании и изменении `companyType` больше не описан (он и раньше не сохранял значение). В фильтре и группировке теперь работает `typeId`, а `companyType` возвращает `400` (`UNKNOWN_FILTER_FIELD` в фильтре, `INVALID_AGGREGATION_FIELD` в группировке) — замените имя на `typeId`.\n\n## 2026-07-22\n\n### BC-0722-1: иконка сервера отдаётся как PNG\n\n> Поддержка старого формата до: 21.07.2026\n\nИконка сервера теперь отдаётся как PNG 256×256 (`Content-Type: image\u002Fpng`) — платформа рендерит её из вашего загруженного SVG. Загрузка (`POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Ficon`) по-прежнему принимает только SVG и теперь может вернуть `400 ICON_RASTERIZE_FAILED`, если файл не удаётся растеризовать в PNG.\n\n### NEW-0722-2: пробуждение по расписанию (wake-schedules) доступно на всех порталах\n\nCRUD для окон пробуждения — [GET|POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules](\u002Fdocs\u002Finfra\u002Fwake-schedules\u002Flist), [PATCH|DELETE \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules\u002F:scheduleId](\u002Fdocs\u002Finfra\u002Fwake-schedules\u002Fupdate) — теперь доступен на всех порталах для отдельных серверов (`kind: \"STANDALONE\"`): запрос больше не отвечает `403 WAKE_SCHEDULE_DISABLED`. Платформа поднимает спящий сервер к заданному моменту по cron-выражению, дальше запуск задачи делает собственный cron внутри уже поднятой машины. Galaxy-приложения (`kind: \"GALAXY_APP\"`) пока не участвуют в раскрытии и по-прежнему отвечают `403 WAKE_SCHEDULE_GALAXY_DISABLED`. Ответ `GET \u002Fv1\u002Finfra\u002Fservers\u002F:id` и список серверов теперь содержат аддитивные поля `nextScheduledWakeAt` (время ближайшего пробуждения, ISO 8601 или `null`) и `wakeScheduleCapable` — прежние поля не меняются.\n\n### NEW-0722-3: 409 SERVER_NOT_READY при деплое несёт признак повторяемости\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) в ответе `409 SERVER_NOT_READY`, когда на сервере уже идёт восстановление подключения (запущенное параллельным деплоем или ремонтом), теперь дополнительно возвращает поля `error.retryable: true` и `error.retryAfter` (секунды) и заголовок `Retry-After`. Это машинный сигнал: повтор запроса имеет смысл — подождите указанный интервал и повторите деплой. Прежние клиенты не затронуты: код и текст ошибки прежние, поля добавлены аддитивно.\n\n### NEW-0722-4: Новый эндпоинт активации триала Маркета для портала ключа\n\n`POST \u002Fv1\u002Fportals\u002F:id\u002Factivate-market-trial` активирует одноразовый триал Битрикс24 Маркета для портала, которому принадлежит вызывающий ключ. Раньше активация была доступна только из кабинета — у ключей API программного пути не было.\n\n`:id` обязан совпадать с порталом ключа, иначе `403 PORTAL_MISMATCH`. Тело не требуется. Лимит — 3 запроса в час на портал.\n\nОтвет при успехе: `{ \"success\": true, \"data\": { \"status\": \"activated\", \"trialEndsAt\": \"...\" } }` (или `\"status\": \"already_active\"`, если триал\u002Fдоступ уже есть). Ошибки: `403 WRITE_BLOCKED_READONLY_KEY` (ключ только для чтения), `403 PURPOSE_KEY_FORBIDDEN` (служебный ключ специального назначения не может активировать триал), `404 NOT_FOUND` (портал не найден), `409 ALREADY_ACTIVATED` (триал уже активирован), `409 TRIAL_ACTIVATION_UNAVAILABLE` (триал недоступен для этого портала), `503 TRIAL_ACTIVATION_RETRY` (временная ошибка, повторите позже).\n\n### FIX-0722-5: портал с активной подпиской Маркетплейса больше не получает ложный отказ `MARKETPLACE_REQUIRED`\n\n**Было**\n\nНа портале с несколькими держателями ключа разработчика состояние подписки Маркетплейса могло не прочитаться: если первый опрошенный ключ отвечал данными о портале, но без блока подписки (урезанный набор прав), опрос на этом останавливался и до ключа, способного прочитать подписку, дело не доходило. Подписка оставалась неизвестной, и портал с реально оплаченной подпиской получал `402 MARKETPLACE_REQUIRED` на [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra), а `GET \u002Fv1\u002Fme` отдавал `capabilities.servers.create.available: false`. Повторный вызов `GET \u002Fv1\u002Fme?refresh=tariff` отказ не снимал.\n\n**Стало**\n\nОпрос продолжается до ключа, который вернёт блок подписки. Порталу с оплаченной подпиской создание сервера разрешается, `capabilities.servers.create.available` становится `true`. Формат ответов не изменился, действий со стороны клиента не требуется. Состояние обновляется при очередном обновлении тарифа портала (не позже часа) либо сразу по `GET \u002Fv1\u002Fme?refresh=tariff`.\n\n**Влияние на интеграторов**\n\nДействий не требуется. Клиент, который ветвился на `402` при создании сервера, продолжает работать: на затронутых порталах этот ответ просто перестаёт приходить.\n\n### FIX-0722-6: поле `region` в ответах об инфраструктуре всегда возвращает идентификатор региона\n\n**Было**\n\nУ сервера, созданного и запущенного на международном сегменте, [GET \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers) и `GET \u002Fv1\u002Finfra\u002Fservers\u002F:id` могли вернуть в поле `region` внутренний идентификатор **зоны размещения**, а не идентификатор региона из каталога. Значение не совпадало ни с одним `id` из [GET \u002Fv1\u002Finfra\u002Fproviders\u002F:providerId\u002Fregions](\u002Fdocs\u002Finfra\u002Fproviders), поэтому сопоставить сервер с регионом каталога по этому полю было нельзя, и оно раскрывало детали внутреннего размещения.\n\n**Стало**\n\n`region` всегда содержит нейтральный идентификатор региона из того же пространства имён, что и каталог, — например `bc-eu-central`. Значение совпадает с `id` соответствующей записи `GET \u002Fv1\u002Finfra\u002Fproviders\u002F:providerId\u002Fregions`, поэтому сервер сопоставляется с регионом каталога напрямую. Зона размещения — внутренняя деталь: при создании сервера указывается регион, конкретную зону внутри него платформа выбирает сама.\n\n**Что делать интеграторам**\n\nНичего, если вы передаёте в `region` значение, взятое из каталога регионов, — этот сценарий не менялся. Если ваш код сравнивал `region` из ответа о сервере со строкой, полученной ранее из ответа о **сервере**, а не из каталога, — сравнение теперь надёжно: оба конца в одном пространстве имён. На вход по-прежнему принимаются и идентификаторы из прежнего каталога.\n\n## 2026-07-21\n\n### FIX-0721-1: имя и описание встроенного поискового движка, имя облачного провайдера\n\nПубличное имя платформенного поискового движка приведено к продуктовому: `GET \u002Fv1\u002Fsearch\u002Fproviders`\nи `\u002Fv1\u002Fme` возвращают `Bitrix24 AI Search` в поле `name` (латинские локали). Идентификатор\nпровайдера `bitrix-search` не менялся — клиентам ничего делать не нужно.\n\nИз описания того же провайдера снято упоминание цитирования источников: возможность зависит от\nдвижка, привязанного к инстансу, и объявляется машинно в `capabilities.output.citations` того же\nответа.\n\n`GET \u002Fv1\u002Finfra\u002Fproviders` на международном сегменте возвращает в поле `name` бренд `Bitrix24 Cloud`\nвместо `Bitrix Cloud`. Идентификатор провайдера `bitrix-cloud` не менялся.\n\n**Было**\n\n`\"name\": \"Bitrix AI Search\"` · `\"description\": \"Platform AI search with agentic mode and source citations\"` · `\"name\": \"Bitrix Cloud\"`\n\n**Стало**\n\n`\"name\": \"Bitrix24 AI Search\"` · `\"description\": \"Platform AI search with agentic mode\"` · `\"name\": \"Bitrix24 Cloud\"`\n\n### FIX-0721-2: создание шаблона бизнес-процесса теперь принимает файл шаблона\n\n**Было**\n\n[POST \u002Fv1\u002Fbizproc-templates](\u002Fdocs\u002Fentities\u002Fbizproc-templates) отвечал `422 Incorrect field TEMPLATE_DATA!` при любом теле — создать шаблон было невозможно: поле с содержимым файла `.bpt` не входило в схему сущности и до Битрикс24 не доходило.\n\n**Стало**\n\nПоле `templateData` (файл `.bpt` в виде массива `[имя файла, содержимое в base64]`) принимается и передаётся в Битрикс24, шаблон создаётся. Поле обязательно на создание: без него запрос отклоняется с `400 MISSING_REQUIRED_FIELDS` до обращения к Битрикс24 (раньше приходила сырая ошибка `Incorrect field TEMPLATE_DATA!`). Оно доступно на запись и в описании полей `GET \u002Fv1\u002Fbizproc-templates\u002Ffields`, но не возвращается при чтении.\n\n### NEW-0721-3: Сводный реестр исходников — GET \u002Fv1\u002Fme\u002Fsources\n\nНовый эндпоинт [GET \u002Fv1\u002Fme\u002Fsources](\u002Fdocs\u002Fsource-storage) — программный аналог кабинетной страницы «Исходники приложений». Возвращает снапшоты исходников по всем серверам и приложениям, которыми владеет ключ (а ключ администратора аккаунта — по всему аккаунту), с пагинацией (`page`\u002F`limit`\u002F`search`) и стандартным конвертом `{ success, data, total, page, limit }`. Каждая строка несёт указатель для перехода вглубь — `listEndpoint` и `latestDownloadEndpoint` — плюс `reachableViaApi` и, для строк-серверов, `blackholeStatus`. В отличие от [GET \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Flist), который ограничен серверами вызывающего ключа, этот реестр охватывает и сервер на другом ключе того же владельца.\n\nОтветы server-scoped эндпоинтов исходников ([POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources](\u002Fdocs\u002Fsource-storage) и соседние list\u002Fdownload\u002Ftag\u002Fcleanup) теперь описаны в документации; поле `versions[].serverContext` (`{ serverId, serverName, serverDisplayName, linkedApp }`) закреплено в контракте.\n\n### NEW-0721-4: необязательное поле error.b24Code в ответах 422 BITRIX_ERROR\n\nВ ответах `422 BITRIX_ERROR` появилось необязательное поле `error.b24Code` — сырой код ошибки Битрикс24 для программной обработки (например, `PERIOD_REQUIRED`, `INVALID_FILTER`). Изменение аддитивно: прежние клиенты, разбирающие только `error.code` и `error.message`, не затронуты.\n\n### NEW-0721-5: Статистика Открытых линий — 6 методов дашборда\n\nНовый раздел API для дашбордов контакт-центра: [POST \u002Fv1\u002Fopenlines\u002Fstats](\u002Fdocs\u002Fopenlines\u002Fstats) (агрегаты за период), [GET \u002Fv1\u002Fopenlines\u002Foperators](\u002Fdocs\u002Fopenlines\u002Foperators) (real-time нагрузка операторов), [POST \u002Fv1\u002Fopenlines\u002Fsessions\u002Fsearch](\u002Fdocs\u002Fopenlines\u002Fsessions), [POST \u002Fv1\u002Fopenlines\u002Fsessions\u002Fstats](\u002Fdocs\u002Fopenlines\u002Fsessions\u002Fstats), [POST \u002Fv1\u002Fopenlines\u002Fsessions\u002Ftransfers](\u002Fdocs\u002Fopenlines\u002Fsessions\u002Ftransfers), [POST \u002Fv1\u002Fopenlines\u002Fratings\u002Fsearch](\u002Fdocs\u002Fopenlines\u002Fratings). Требуется скоуп `imopenlines` и право тарифа `report_open_lines` (иначе `403 B24_TARIFF_RESTRICTION`).\n\n**В процессе раскатки** — методы выходят в обновлении Битрикс24 `imopenlines 26.700.0` и доступны не на всех порталах. Пока обновление не приехало на портал, методы возвращают `422 METHOD_NOT_YET_AVAILABLE` с целевой версией в ответе — это признак раскатки, а не ошибка интеграции.\n\n### FIX-0721-6: тарифный отказ Битрикс24 отдаётся как 403 B24_TARIFF_RESTRICTION на всех эндпоинтах\n\n**Было**\n\nОтказ Битрикс24 по тарифному праву приходил как `422 BITRIX_ERROR` с непрозрачным сообщением — отличить его от прочих ошибок Битрикс24 программно было нельзя.\n\n**Стало**\n\nТакой отказ отдаётся как `403` с кодом `B24_TARIFF_RESTRICTION`. Правило общее для всего V1 API, а не только для Открытых линий: любой эндпоинт, вызвавший метод Битрикс24, недоступный на тарифе портала, теперь отвечает этим кодом.\n\n**Влияние на интеграторов**\n\nКлиенты с обычной обработкой ошибок продолжают работать без изменений — отказ остаётся ошибкой, просто становится точнее. Если вы отдельно ветвились на `422` для тарифных отказов, перенесите ветку на `403` и `error.code === 'B24_TARIFF_RESTRICTION'`. Этот код не означает сбой интеграции: возможность не входит в тариф портала Битрикс24, и повторять запрос бессмысленно до смены тарифа.\n\n### FIX-0721-7: Версионные рантаймы деплоя ставят заявленную версию на Ubuntu 24.04\n\n**Было**\n\nРантайм `node20` устанавливал Node.js 18 (в репозиториях Ubuntu 24.04 нет Node 20), а `python311` и RAG-рантаймы (`node20-rag`, `python311-rag`) падали на шаге установки — нужных пакетов в дистрибутиве нет. В ответе `GET \u002Fv1\u002Finfra\u002Fruntimes` поле `packages` показывало `postgresql-14`, хотя ставилась PostgreSQL 16.\n\n**Стало**\n\n`node20` ставит Node.js 20 (с проверкой мажорной версии), `python311` — Python 3.11, RAG-рантаймы — PostgreSQL 16 с расширением pgvector в базе приложения. Поле `packages` отражает фактическую версию (`postgresql-16`). Публичные идентификаторы рантаймов и формат запроса `\u002Fdeploy` не изменились.\n\n### FIX-0721-8: статус ремонта сервера корректен при опросе\n\n**Было**\n\nОпрос [GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Frepair-status](\u002Fdocs\u002Finfra\u002Flifecycle\u002Frepair-status) при многоузловом бэкенде мог кратковременно вернуть `idle`, даже когда ремонт ещё шёл, — если запрос попадал на другой обслуживающий узел, чем тот, что выполняет ремонт. Клиент, опрашивающий статус в цикле, мог из-за этого ошибочно решить, что ремонт завершился, ещё до его старта.\n\n**Стало**\n\nЭндпоинт надёжно возвращает реальный прогресс ремонта (`running` \u002F `done` \u002F `failed`) независимо от того, на какой узел попал опрос.\n\n**Влияние на интеграторов**\n\nФорма ответа не изменилась, действий со стороны клиента не требуется.\n\n### FIX-0721-9: приложение на standalone-сервере больше не работает с правами администратора\n\n**Было**\n\nПриложение, задеплоенное на standalone Black Hole сервер, запускалось с правами администратора и без изоляции. Любая уязвимость в самом приложении (например, выполнение произвольного кода) сразу давала полный контроль над всей виртуальной машиной: доступ к ключам подключения сервера, к служебным настройкам и к системным файлам.\n\n**Стало**\n\nПриложение работает под выделенной непривилегированной учётной записью и видит только свой каталог (`extractTo`, по умолчанию `\u002Fopt\u002Fapp`), которым владеет. Системные каталоги защищены от записи, повышение привилегий запрещено. Порт ниже 1024 по-прежнему работает — платформа выдаёт для него отдельное разрешение.\n\nКоманды деплоя (`install`, `preStart`) и `\u002Fexec` по-прежнему выполняются с правами администратора — здесь ничего не изменилось, `sudo` не нужен.\n\nНичего менять не требуется: если приложению для запуска действительно нужны права администратора, деплой автоматически возвращает прежний режим, завершается успешно и добавляет предупреждение с причиной. Чтобы сразу пропустить эту попытку (актуально для nginx в качестве команды запуска, MySQL через системный сокет и запуска через Docker), передайте `\"hardening\": \"off\"` в теле деплоя.\n\nВ `data.steps[]` появились два новых значения `step`: `service_user` — передача каталога деплоя непривилегированной учётной записи, и `hardening` — предупреждение о возврате приложения к прежнему режиму. Клиентам, которые разбирают шаги по имени, стоит их учесть.\n\n### BC-0721-10: битый кандидат в image_url больше не роняет весь запрос\n\n> Поддержка старого формата до: 21.01.2027\n\n**Было**\n\nМассив `content` мог нести несколько частей `image_url`. Если хотя бы одна из них\nсодержала не изображение — например HTML-страницу с ошибкой, закодированную в Base64\nи объявленную как `image\u002Fpng`, — платформа передавала её модели как есть. Модель не\nмогла её декодировать, и весь запрос завершался ошибкой `502` с кодом\n`ai_provider_unavailable`, даже когда остальные изображения были корректны.\n\nОтдельно: части с неподдерживаемым MIME-типом, повреждённым Base64 или превышением\nлимита 20 МиБ отклонялись ответом `400 invalid_image_payload` — тоже на весь запрос\nцеликом.\n\n**Стало**\n\nПеред отправкой модели платформа проверяет фактическое содержимое каждой части\n`image_url` по сигнатуре байтов, а не по заявленному MIME-типу. Часть, содержимое\nкоторой является веб-ответом (HTML, XML, JSON, ответ HTTP) либо не декодируется,\nзаменяется на своей позиции текстовой заглушкой `[image unavailable: \u003Cпричина>]`.\nОстальные изображения обрабатываются как обычно, запрос завершается успешно.\n\nПозиции частей сохраняются: длина массива `content` не меняется, поэтому нумерация\nкандидатов на стороне клиента остаётся верной.\n\nОтклонённые части видны в ответе — в поле `warnings` появляется запись с кодом\n`IMAGE_CONTENT_REJECTED`, а в заголовках `X-Image-Parts-Rejected` с их количеством.\nДля потоковых ответов заголовок приходит вместе с началом потока.\n\nОтветом `400` теперь завершаются только структурные ошибки: отсутствующее поле\n`url`, строка, не являющаяся ни URL, ни data-URI, неподдерживаемая схема и `http:\u002F\u002F`\nв production.\n\n**Что делать интеграторам**\n\nЕсли ваш код полагался на `400 invalid_image_payload` как на признак того, что\nизображение не принято, — читайте вместо этого `warnings` или заголовок\n`X-Image-Parts-Rejected`. Запрос теперь завершается успешно, и молчаливой потери\nизображения не происходит: факт замены всегда отражён в ответе.\n\nДополнительно изменилось поведение при отказе модели. Раньше любой не-2xx ответ\nпровайдера приходил как `502`, теперь статус отражает причину:\n\n- ответ модели `400` или `422` → `400` с кодом `ai_provider_rejected`. Повторять\n  такой запрос без изменений бесполезно;\n- превышение лимита на стороне модели (`429`) → `429` с заголовком `Retry-After`.\n  Повторить нужно, выдержав указанную задержку. В потоковом ответе заголовок\n  невозможен, поэтому задержка приходит полем `retryAfter` в кадре ошибки;\n- таймаут на стороне модели (`408`) → `503` с кодом `ai_provider_timeout`.\n\nОтветы `401`, `403` и `5xx` по-прежнему приходят как `502`. Изменение затрагивает\n[POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions) и\n[POST \u002Fv1\u002Fembeddings](\u002Fdocs\u002Fai\u002Fembeddings).\n\nОшибки со стороны модели теперь дополнительно несут поле `providerStatusCode` —\nисходный HTTP-статус ответа модели. По нему `429` от модели отличается от `429`\nсобственного лимита платформы (у последнего поля нет): код `rate_limit_exceeded`\nу обоих одинаковый, чтобы SDK ретраили единообразно, а различитель — это новое\nнеобязательное поле.\n\nТакже в data-URI теперь допускаются параметры между типом и `;base64` —\n`data:image\u002Fjpeg;name=photo.jpg;base64,...` больше не отклоняется.\n\n## 2026-07-20\n\n### NEW-0720-1: Ответы приложений теперь возвращают статус публикации\n\nОтветы раздела приложений — [список](\u002Fdocs\u002Fapps\u002Flist), [данные приложения](\u002Fdocs\u002Fapps\u002Fget), [создание](\u002Fdocs\u002Fapps\u002Fcreate), [публикация](\u002Fdocs\u002Fapps\u002Fpublish) и [снятие с публикации](\u002Fdocs\u002Fapps\u002Funpublish) — теперь несут два новых поля: `catalogStatus` (`PRIVATE` \u002F `PUBLISHED` \u002F `UNPUBLISHED`) и `publishedAt` (дата публикации, ISO 8601, или `null`). Раньше прочитать статус публикации через V1 было нельзя — приходилось угадывать по массиву `placements`, что ненадёжно: снятое с публикации приложение может сохранить ранее привязанные коды, а `PRIVATE` и `UNPUBLISHED` по `placements` неразличимы. Поля добавлены аддитивно — прежние вызовы работают без изменений.\n\n### FIX-0720-2: переименование чата больше не отвечает ложным успехом тому, кто не участник\n\n**Было**\n\n[PATCH \u002Fv1\u002Fchats\u002F:chatId](\u002Fdocs\u002Fchats\u002Fmanagement\u002Frename), вызванный от имени администратора портала, который не состоит в чате, возвращал `{ \"success\": true, \"data\": true }`, хотя название чата не менялось: Битрикс24 отвечал на такой вызов ложным успехом. Отличить его от настоящего переименования по ответу было нельзя, и интегратор считал операцию выполненной. Вызывающий при этом не мог даже прочитать этот чат.\n\n**Стало**\n\nПеред переименованием проверяется, что вызывающий состоит в чате. Если нет — ответ `404 CHAT_NOT_FOUND_OR_NO_ACCESS`, тот же, что и для любого другого не-участника, и попытки переименования не происходит. Ложный успех больше не выдаётся. Переименование участником, у которого есть права, работает без изменений.\n\n### NEW-0720-3: стриминг chat-completions обрывает зависший ответ апстрима явной ошибкой\n\nЕсли при стриминге ([POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fstreaming) с `stream: true`) апстрим-модель отдала заголовки, но затем замолчала в середине ответа и не присылает новых данных дольше окна ожидания, прокси теперь прерывает вызов и присылает в поток терминальный кадр ошибки перед `data: [DONE]`:\n\n`data: {\"error\":{\"code\":\"stream_idle_timeout\",\"type\":\"server_error\",\"retryable\":true,\"retryAfter\":\u003Cсекунды>}}`\n\nРаньше такой вызов висел бесконечно (агент оставался в состоянии «receiving stream response»). Ошибка повторяемая — прочитайте поток до конца (`[DONE]`) и повторите запрос с учётом `retryAfter`. Обычные (не зависшие) стримы и «думающие» модели, которые непрерывно присылают токены рассуждений, не затронуты.\n\n### NEW-0720-4: tasks: новое поле timeSpentInLogs\n\nПоле `timeSpentInLogs` (фактически затраченное время в секундах, сумма записей учёта времени) теперь задекларировано в схеме задач — доступно в `select`, фильтре и сортировке [GET \u002Fv1\u002Ftasks](\u002Fdocs\u002Fentities\u002Ftasks\u002Flist) и `POST \u002Fv1\u002Ftasks\u002Fsearch`, и присутствует в [GET \u002Fv1\u002Ftasks\u002Ffields](\u002Fdocs\u002Fentities\u002Ftasks\u002Ffields).\n\nРаньше поле возвращалось, только если в `select` были указаны ОБА написания сразу (`timeSpentInLogs` и `TIME_SPENT_IN_LOGS`); теперь достаточно любого одного. Поле только для чтения — фиксируется через эндпоинт учёта времени, не через обновление задачи.\n\n### FIX-0720-5: GET \u002Fv1\u002Ffiles\u002F:id?include=folder теперь возвращает папку\n\n**Было**\n\n[GET \u002Fv1\u002Ffiles\u002F:id](\u002Fdocs\u002Fentities\u002Ffiles\u002Fget) с `?include=folder` отвечал `200`, но без блока `_included`, хотя [GET \u002Fv1\u002Ffiles\u002Ffields](\u002Fdocs\u002Fentities\u002Ffiles\u002Ffields) объявлял `folder` как includable — включение молча не срабатывало.\n\n**Стало**\n\n`?include=folder` прикрепляет папку в `_included.folder`, как и обещает `\u002Ffields`.\n\n**Влияние на интеграторов**\n\nДействий не требуется. Клиенты, читавшие `_included.folder`, теперь получают объект вместо его отсутствия.\n\n### FIX-0720-6: \u002Fv1\u002Fme: блок infra стал точным по лимиту серверов, идентификатору провайдера и разбивке\n\n**Было**\n\n`GET \u002Fv1\u002Fme` в блоке `infra` возвращал `limits.max: 3` независимо от реально применяемого лимита серверов на ключ; `providers` мог отдавать внутренний идентификатор провайдера (расходясь с `GET \u002Fv1\u002Finfra\u002Fproviders`) и с возможными дублями; `limits.breakdown` относил виртуальные машины управляемых ботов к `direct` вместо `bots`.\n\n**Стало**\n\n`limits.max` отражает реально применяемый лимит серверов на ключ; `providers` отдаёт публичный идентификатор провайдера, согласованный с `GET \u002Fv1\u002Finfra\u002Fproviders`, без дублей; `limits.breakdown` учитывает машины ботов в `bots`. Интеграцию менять не нужно — значения просто стали корректными.\n\n### FIX-0720-7: deal-categories: неизвестные поля фильтра отклоняются, сортировка по id учитывает направление\n\n**Было**\n\n`GET \u002Fv1\u002Fdeal-categories` с фильтром по неизвестному полю молча возвращал ВСЮ таблицу воронок с `200` — Bitrix24 игнорирует неизвестные ключи фильтра legacy-метода и отдаёт весь список. А сортировка `?sort=id&order=desc` игнорировала направление и всегда возвращала один и тот же порядок.\n\n**Стало**\n\nФильтр по неизвестному или неподдерживаемому полю (а также операторные префиксы `>`\u002F`>=`\u002F`!`\u002F… и операторные объекты) отклоняется до вызова Bitrix24 с `400 UNSUPPORTED_FILTER`; в сообщении перечислены фильтруемые поля (`id`, `name`, `sort`). Фильтр точным совпадением и `$in` по этим полям работают как прежде. Сортировка `?sort=id` теперь корректно учитывает `asc`\u002F`desc`.\n\n### FIX-0720-8: openline-configs: нераспознанные поля в теле записи больше не пропадают молча\n\n**Было**\n\n`POST \u002Fv1\u002Fopenline-configs` (и `PATCH`) молча игнорировал нераспознанные поля тела — Bitrix24 отбрасывает неизвестные ключи метода `imopenlines.config.*`. Тело из одних только неизвестных полей при этом создавало конфигурацию со значениями по умолчанию и отвечало `200`.\n\n**Стало**\n\nЕсли в теле НЕТ ни одного известного поля — запрос отклоняется с `400 VALIDATION_ERROR` до вызова Bitrix24, и в сообщении перечислены нераспознанные поля. Если известное поле есть, но часть полей нераспознана — запись выполняется как прежде, а в ответе возвращается `meta.warnings` с перечнем проигнорированных полей (раньше они исчезали без следа). Поля только для чтения (`id`, `queue`, `dateCreate` и другие) в теле записи теперь отклоняются с `400 READONLY_FIELD` — раньше они проходили как «известные» и могли привести к созданию конфигурации со значениями по умолчанию.\n\n### FIX-0720-9: GET \u002Fv1\u002F{entity}\u002Ffields сигналит о неполных метаданных при сбое Bitrix24\n\n**Было**\n\nЕсли запрос динамических полей к Bitrix24 (`*.fields`) падал (rate-limit, `QUERY_LIMIT_EXCEEDED`, таймаут очереди), эндпоинт молча отдавал 200 только со статическими полями схемы — у многих из них нет человекочитаемой метки (`label`). Ответ выглядел полным, клиент не мог отличить его от корректного и строил недетерминированные маппинги полей.\n\n**Стало**\n\nПри сбое запроса полей ответ по-прежнему 200 со статическими полями, но несёт `meta.warnings: [{ \"code\": \"fields_partial\", \"message\": \"...\" }]` — клиент видит, что набор полей неполный, и может повторить запрос. Формат предупреждения — объект `{ code, message }`, тот же канал и та же форма, что у `meta.warnings` в list\u002Fsearch, поэтому один разбор по `warning.code` работает на всех эндпоинтах. Сбой теперь также логируется на стороне Vibe.\n\n## 2026-07-19\n\n### BC-0719-1: Cowork\u002FCode: тарифная линейка переименована — Free \u002F Pro \u002F Max \u002F Ultra, цена Ultra снижена\n\n> Поддержка старого формата до: 18.07.2026\n\n**Было**\n\nПоле `tier` в ответах [GET \u002Fv1\u002Fcowork\u002Fstate](\u002Fdocs\u002Fcowork\u002Fstate) и [GET \u002Fv1\u002Fcowork\u002Fme](\u002Fdocs\u002Fcowork\u002Fme) (а также `recommendation.upgrade.nextTier` и каталог `tiers[]`) принимало значения `FREE`, `START`, `PRO`, `MAX`. Тариф `MAX` (×20) стоил 40 000 Ꝟ\u002Fмес.\n\n**Стало**\n\nЛинейка переименована со сдвигом: `START` → `PRO`, `PRO` → `MAX`, `MAX` → `ULTRA`; набор значений теперь `FREE`, `PRO`, `MAX`, `ULTRA`. Множители тарифов не изменились: `PRO` ×1 (база), `MAX` ×5, `ULTRA` ×20; у `FREE` — 5% от `PRO`. Цена `ULTRA` (бывший `MAX`, ×20 объёма) снижена с 40 000 до 20 000 Ꝟ\u002Fмес. Объёмы окон откалиброваны по фактическому использованию: 5-часовое окно выросло вдвое на всех тарифах, месячные объёмы уменьшены; в течение текущего оплаченного периода лимиты подписки не меняются — новые значения применяются со следующего продления. Переименование применяется атомарно в момент релиза: значение `START` больше не возвращается, добавилось значение `ULTRA`. Клиенты, ветвящиеся по строковым значениям `tier` \u002F `nextTier`, должны обновить маппинг с учётом сдвига смысла (`PRO` теперь база, `MAX` — средний тариф); клиенты, отображающие серверные `multiplier` \u002F `feeVibes` как есть, продолжают работать без изменений.\n\n### FIX-0719-2: цены тарифов Cowork\u002FCode на международной версии приведены к долларовой шкале\n\n**Было**\n\nНа международной версии платформы каталог тарифов в [GET \u002Fv1\u002Fcowork\u002Fstate](\u002Fdocs\u002Fcowork\u002Fstate) отдавал цены в масштабе российской версии: `feeVibes` 2000 \u002F 10000 \u002F 20000 за Pro \u002F Max \u002F Ultra. При курсе 1 Vibe credit = 1 доллар это читалось как 2000–20000 долларов в месяц.\n\n**Стало**\n\nКаталог тарифов на международной версии отдаёт долларовую сетку: Pro — `feeVibes: 20`, Max — `100`, Ultra — `200` в месяц; квоты трёх окон масштабированы согласованно, поэтому ёмкость тарифа в запросах не изменилась. Российская версия не затронута.\n\n**Влияние на интеграторов**\n\nЕсли ваш клиент читает `tiers[].feeVibes` из [GET \u002Fv1\u002Fcowork\u002Fstate](\u002Fdocs\u002Fcowork\u002Fstate) на международной версии — отображаемые значения уменьшились в 100 раз и теперь совпадают с реально списываемой ценой активации. Ничего менять в коде не нужно.\n\n## 2026-07-18\n\n### FIX-0718-1: деплой переиспользует сервер приложения вместо создания дубликата\n\n**Было**\n\n`POST \u002Fv1\u002Finfra\u002Fservers` c `source` всегда создавал новый сервер, даже если у приложения, которому принадлежит ключ, уже был сервер — на счёт заводился второй, простаивающий. А `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` отвечал `WRONG_KEY`, если ключ вызова отличался от того, которым сервер был создан (например, у приложения есть личный ключ и ключ авторизации).\n\n**Стало**\n\nЕсли ключ вызова принадлежит приложению, у которого уже есть живой сервер, `POST \u002Fv1\u002Finfra\u002Fservers` возвращает этот сервер с полем `reused: true` вместо создания нового. Если в том же запросе передан `source`, а переиспользуемый сервер — это приложение галактики (`kind: \"GALAXY_APP\"`), **у которого ещё нет работающего контейнера** (ни разу не деплоилось или прошлый деплой упал), исходный код сразу разворачивается в его собственный сервер (ответ содержит `reused: true` и `deploying: true`, а статус сервера на время сборки — `provisioning`) — так же, как при обычном create с `source`: опрашивайте `GET \u002Fv1\u002Finfra\u002Fservers\u002F:id` до статуса `running`, второй вызов деплоя не нужен. Если же приложение галактики **уже работает**, ответ содержит `reused: true` и `next: \"deploy\"` — разверните исходный код отдельным вызовом `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` (так живой контейнер не затрагивается на время сборки). Для обычного сервера или запроса без `source` ответ содержит `reused: true` и `next: \"deploy\"` — разверните исходный код отдельным вызовом `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy`. `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` теперь принимает любой ключ этого же приложения и деплоит в его сервер.\n\n**Влияние на интеграторов**\n\nНичего менять не нужно. Дубликаты серверов больше не создаются. Раньше one-shot create с `source` на уже существующий сервер приложения возвращал `next: \"deploy\"` и терял переданный `source` — теперь исходный код разворачивается сразу. И деплой, и чтение статуса (`GET \u002Fv1\u002Finfra\u002Fservers\u002F:id`) сервера приложения работают под любым ключом этого приложения — независимо от того, каким из них вы вызываете API.\n\n### FIX-0718-2: DELETE \u002Flock снимает зависший лок и на удалённом сервере\n\n**Было**\n\n[`DELETE \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Flock`](\u002Fdocs\u002Finfra\u002Fdeploy\u002Flock) возвращал `404 NOT_FOUND`, если сервер был удалён — даже когда лок операции остался в памяти платформы и продолжал держать сервер. Из-за этого сценарий «предыдущий сервер удалён, лок завис, следующий деплой падает с `EXEC_BUSY`» не имел выхода: снять такой лок через API было нельзя.\n\n**Стало**\n\n`DELETE \u002Flock` снимает зависший лок и на удалённом сервере — при условии, что он всё ещё принадлежит вашему API-ключу (владение остаётся единственной проверкой; лок не хранит данных и не держит облачных ресурсов). Успешный вызов возвращает `200` с `data.released: true`. `404 NOT_FOUND` теперь означает только «сервер не существует или принадлежит другому ключу».\n\n### NEW-0718-3: коды ошибок коннектора при установке приложения теперь возможны и на облачных порталах (поэтапная раскатка)\n\n[POST \u002Fv1\u002Fapps](\u002Fdocs\u002Fapps) на облачном портале теперь тоже может устанавливать приложение через модуль `vibecodeconnector` и, соответственно, возвращать те же коды ошибок коннектора, что раньше были возможны только на коробочных порталах: `403 CONNECTOR_APP_INSTALL_FORBIDDEN` (администратор портала Битрикс24 запретил пользователю установку приложений), `409 CONNECTOR_MODULE_NOT_INSTALLED` (модуль `vibecodeconnector` не установлен на портале) и `502 CONNECTOR_APP_INSTALL_FAILED` (прочие сбои установки). Изменение аддитивное: ответ при успешной установке не изменился, а раскатка идёт поэтапно — на большинстве облачных порталов путь установки пока прежний. Клиентам, которые уже обрабатывают эти коды на коробочных порталах, менять ничего не нужно; клиентам, которые их не обрабатывали, стоит добавить обработку.\n\n## 2026-07-17\n\n### FIX-0717-1: деплой на спящий galaxy-хост отвечает раньше клиентских таймаутов\n\n**Было**\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) на спящем общем хосте удерживал соединение открытым до ~6,5 минут, пока хост просыпался. HTTP-клиенты с типовым таймаутом ожидания заголовков (~300 секунд — дефолт Node fetch) обрывали соединение раньше ответа платформы: деплой выглядел как сетевой сбой `fetch failed` без кода ошибки и рекомендаций. Неудачное пробуждение к тому же возвращало хост в сон, и каждый повтор начинал загрузку хоста заново.\n\n**Стало**\n\nПлатформа будит хост в фоне и ждёт подключения не дольше ~4 минут. Хост успел подключиться — деплой выполняется одним вызовом, как раньше. Не успел — сразу возвращается повторяемый `502 GALAXY_HOST_UNREACHABLE` с `hint` (повторить тот же запрос через 1–2 минуты, слот не удалять), а хост продолжает просыпаться в фоне — повторный деплой подхватывает уже идущую загрузку вместо новой. В `deployment.galaxyApp._rules` и `deployment.standalone._rules` (GET \u002Fv1\u002Fme) добавлена рекомендация держать таймаут HTTP-клиента не ниже 690 секунд — строго выше платформенного окна в 660 секунд.\n\n**Влияние на интеграторов**\n\nИзменений в запросах не требуется. Если деплой на спящий galaxy-хост раньше завершался у вас сетевой ошибкой без ответа платформы — теперь придёт либо успех, либо 502 с инструкцией повторить.\n\n### NEW-0717-2: поиск сотрудников на сервере подсказывает причину пустого списка\n\nЭндпоинт [GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fb24-users](\u002Fdocs\u002Finfra\u002Faccess\u002Fb24-users) теперь возвращает дополнительное поле `hint`, когда список пуст из-за отсутствия доступа к Битрикс24 — приложение ещё не авторизовано на портале или ключ отозван. Прежние вызовы работают без изменений: поле аддитивное и отсутствует при успешной выдаче.\n\n### NEW-0717-3: справочники полей каталога сообщают о nullable-полях\n\nСправочники [GET \u002Fv1\u002Fcatalog-prices\u002Ffields](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Ffields), [GET \u002Fv1\u002Fcatalog-sections\u002Ffields](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Ffields) и [GET \u002Fv1\u002Fcatalog-products\u002Ffields](\u002Fdocs\u002Fentities\u002Fcatalog-products\u002Ffields) теперь добавляют ключ `\"nullable\": true` полям, которые могут вернуть `null`: у цен это `quantityFrom`, `quantityTo` и `extraId`, у разделов — `iblockSectionId`, `xmlId`, `code` и `description`, у товаров — `iblockSectionId`, `code`, `weight`, `purchasingPrice`, `purchasingCurrency`, `quantity` и `quantityReserved`. Тот же признак приходит в `data.entities[].fieldsDetailed` ответа [GET \u002Fv1\u002Fguide](\u002Fdocs\u002Fkeys-auth\u002Fguide), который читается ключом OAuth-приложения без сессии, а в машинной спеке [GET \u002Fv1\u002Fopenapi.json](\u002Fdocs\u002Fcli) такие поля описаны union-типом вида `[\"number\", \"null\"]`. Клиент, который строит типизированную модель по справочнику, теперь получает верную nullability и не падает на первом же `null`. Набор полей, их типы и значения в ответах не изменились.\n\n### NEW-0717-4: EXEC_BUSY подсказывает, через сколько повторить\n\nОтвет `409 EXEC_BUSY` (другая операция держит блокировку сервера) на [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fexec](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fexec) и `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` теперь несёт retry-подсказку: HTTP-заголовок ответа `Retry-After` (в секундах) и два новых поля в теле ошибки — `retryable: true` и `retryAfter` (в секундах). Значение `retryAfter` — короткий интервал опроса (повторяйте с ним, пока не пройдёт), а не полное время до автоматического снятия блокировки; полный верхний предел по-прежнему в `error.hint.autoExpiresInSeconds`. Изменение аддитивное: код, поле `message` и `hint` не меняются, клиенты, читающие `error.code`, продолжают работать без правок.\n\n### FIX-0717-5: деплой galaxy-приложения со слишком большим архивом отдаёт 413, а не «хост недоступен»\n\n**Было**\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) с `source.content`, превышающим лимит загрузки, возвращал `502 GALAXY_HOST_UNREACHABLE` — с текстом про недоступность хоста и советом «повторить, когда хост переподключится». Хост при этом был полностью доступен, а повтор того же архива давал тот же результат: интегратор оказывался в бесконечном цикле бесполезных попыток.\n\n**Стало**\n\nТот же случай возвращает `413 GALAXY_UPLOAD_TOO_LARGE` со структурированным `error.hint`. Причина детерминирована (архив слишком большой), а не транзиентна, поэтому повтор без изменений не поможет. `hint` подсказывает уменьшить архив — исключить `node_modules`, `.git` и артефакты сборки (зависимости платформа ставит на хосте). У galaxy-приложения источник — только встроенный `source.content` (`source.url` отклоняется с `400 GALAXY_DEPLOY_CONTENT_ONLY`), поэтому уменьшить архив — единственный способ восстановления. Отдельный смежный случай: тело запроса, превышающее жёсткий внешний лимит платформы (500 МБ на base64-тело ≈ ~375 МБ бинарного архива), теперь отклоняется на краю кодированным `413 PAYLOAD_TOO_LARGE` (на Vibe-REST `\u002Fv1\u002F`-маршрутах — deploy\u002Fupload\u002Fcreate; OpenAI-совместимые AI-роуты отдают ошибку в своём конверте) вместо сырого HTML — раньше клиент получал недекодируемый ответ.\n\n**Влияние на интеграторов**\n\nНичего менять не нужно: успешные деплои не затронуты. Клиенты, которые ветвились на коде ошибки для этого сбоя, теперь видят честный `413 GALAXY_UPLOAD_TOO_LARGE` вместо вводящего в заблуждение `502 GALAXY_HOST_UNREACHABLE` — последний остаётся для настоящего обрыва туннеля во время сборки.\n\n### NEW-0717-6: displayName и description в деплое задают карточку в каталоге Битрикс24\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) принял два необязательных поля тела — `displayName` и `description`. [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) (создание сервера) принял необязательный `description`. Значения становятся именем и описанием карточки приложения в каталоге Битрикс24. Если деплой прошёл без `displayName` и `description`, ответ содержит `warnings: string[]` с подсказкой задать их; одношаговое создание galaxy-приложения с `source` (тело `POST \u002Fv1\u002Finfra\u002Fservers` с полем `source`) тоже возвращает `warnings` в ответе 201, когда поля не заданы. Обратная совместимость сохранена — запросы без новых полей продолжают работать как раньше.\n\n### FIX-0717-7: деплой галактик отдаёт 503 при временной перегрузке базы\n\n**Было**\n\nПри кратковременном исчерпании пула соединений с базой во время деплоя приложения в галактику [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) возвращал общий `502 GALAXY_APP_DEPLOY_FAILED` — тот же код, что и настоящий провал сборки. Клиент не мог отличить временную перегрузку от терминальной ошибки и часто читал ответ как окончательный.\n\n**Стало**\n\nВременная перегрузка базы теперь отдаётся как `503 POOL_EXHAUSTED` с заголовком `Retry-After` (число секунд для повторной попытки). Настоящий провал сборки по-прежнему `502 GALAXY_APP_DEPLOY_FAILED`.\n\n**Влияние на интеграторов**\n\nНичего менять не нужно. Если ваш клиент повторяет запросы, теперь на `503` он получает явный сигнал бэкоффа через `Retry-After` вместо непрозрачного `502`.\n\n### FIX-0717-8: фильтр и сортировка списка комментариев задачи работают на обеих карточках\n\n**Было**\n\nЗапрос [GET \u002Fv1\u002Ftasks\u002F:taskId\u002Fcomments](\u002Fdocs\u002Fentities\u002Ftask-comments\u002Flist) с параметром `filter` или с сортировкой не по `ID` на портале с новой карточкой задачи возвращал `200` и пустой список, даже когда комментарии в задаче были. Ошибки не приходило, поэтому отличить «под фильтр ничего не подошло» от «фильтр не сработал» было нельзя.\n\n**Стало**\n\nТакой запрос возвращает подошедшие комментарии. Фильтр и сортировка работают по полям `ID`, `AUTHOR_ID` и `POST_DATE`, перед именем поля в фильтре допустим префикс `!`, `>`, `>=`, `\u003C` или `\u003C=`. Фильтр по `AUTHOR_NAME` и сортировка по `AUTHOR_NAME` или `AUTHOR_EMAIL` на новой карточке отвечают `400` с кодом `UNSUPPORTED_FILTER_FIELD` или `UNSUPPORTED_SORT_FIELD` и указывают на `AUTHOR_ID`, на старой карточке эти поля по-прежнему принимаются. Добавлен параметр `offset` — он учитывается на новой карточке при запросе с `filter` или сортировкой не по `ID`, на остальных путях чтения игнорируется. Код `INVALID_FILTER` теперь приходит ещё и тогда, когда `filter` — скаляр или пустой массив вместо объекта (`0`, `false`, `\"\"`, `[]`), значение поля `ID` или `AUTHOR_ID` не число, значение `POST_DATE` не разбирается как дата, либо значение поля — объект или массив вместо скаляра. В `meta` добавлено поле `truncated` со значением `true` — просмотрено предельное окно истории, и часть комментариев осталась за его границей.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно: запрос, который раньше отдавал пустой список, начинает отдавать данные. Учтите три границы. Первая — фильтр по `AUTHOR_NAME` и сортировка по `AUTHOR_NAME` или `AUTHOR_EMAIL` на портале с новой карточкой вместо пустого `200` теперь отвечают `400`, переведите такой запрос на `AUTHOR_ID`, идентификатор сотрудника по имени даёт `GET \u002Fv1\u002Fusers`. Вторая — `meta.total` на запросе с фильтром к новой карточке считает подошедшие комментарии в пределах просмотренного окна, а не во всей истории задачи, и при `meta.truncated: true` это неполное число. Третья — значение `POST_DATE` на новой карточке сравнивается с `createdAt` в UTC, поэтому результат на границе суток может отличаться от выборки на старой карточке. На порталах со старой карточкой поведение не изменилось.\n\n## 2026-07-16\n\n### BC-0716-1: json_object на реасонинг-модели восстанавливает JSON, тело 422 уточнено\n\n> Поддержка старого формата до: 15.01.2027\n\n**Было**\n\nЗапрос [POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions) с `response_format` типа `json_object` на модели с рассуждением (например `bitrix\u002Fbitrixgpt-5.5-agent`) стабильно возвращал `422 structured_output_truncated`, даже когда модель завершилась сама (`finish_reason: \"stop\"`) и положила готовый валидный JSON в служебный канал `reasoning_content` — ответ терялся. В теле любой такой ошибки присутствовали поля `error.suggestedMaxTokens` и `error.param`, а текст утверждал «finish_reason=length» независимо от реальной причины остановки.\n\n**Стало**\n\nЕсли модель на `json_object` завершилась сама и валидный JSON лежит в `reasoning_content`, платформа восстанавливает его и возвращает `200` с этим JSON в `content` (в потоковом режиме — чанком `content` перед терминальным чанком с `finish_reason`). Тело `422` стало правдивым: `error.suggestedMaxTokens` и `error.param` присутствуют **только** при реальной обрезке (`finish_reason: \"length\"`); при завершении по любой другой причине (`stop` и т.д.) эти поля **опущены**, а текст называет фактический `finish_reason`. Поведение `json_schema` не изменилось — там строгая схема проверяется на стороне модели.\n\n**Что делать интеграторам**\n\nНичего, если вы просто обрабатываете `422` по `error.code`. Если ваш код **безусловно** читает `error.suggestedMaxTokens` или `error.param` на ошибке `structured_output_truncated` — сделайте чтение опциональным: при `finish_reason !== \"length\"` этих полей теперь нет. Потоковым клиентам с `response_format` — собирать `content` по всем дельтам до `data: [DONE]`.\n\n### FIX-0716-2: env, отправленный файлом в multipart-деплое, больше не игнорируется молча\n\n**Было**\n\nПри `multipart\u002Fform-data` в [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) поле `env`, отправленное как файл или Blob, молча игнорировалось — деплой завершался успехом, но приложение стартовало без переменных окружения.\n\n**Стало**\n\nТакой запрос возвращает 400 с кодом `VALIDATION_ERROR` и подсказкой отправлять `env` текстовым полем с JSON-строкой.\n\n**Влияние на интеграторов**\n\nКорректный способ (текстовое поле `env` со значением вида `{\"KEY\":\"value\"}`) не затронут. Кто отправлял `env` файлом или Blob — теперь получает явную ошибку вместо ложного успеха.\n\n### FIX-0716-3: деплой крупных архивов через source.content больше не падает с Gateway HTTP 413\n\n**Было**\n\nНа части порталов [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) с `source.content` (base64-архив) падал на шаге `download` с `{ \"code\": \"DEPLOY_FAILED\", \"message\": \"Gateway HTTP 413\", \"step\": \"download\" }`, если base64-тело превышало ~1 МБ (примерно 768 КБ исходного `tar.gz`) — вопреки заявленному лимиту 500 МБ. Обходом была загрузка через `source.url`.\n\n**Стало**\n\nЛимит 500 МБ на inline-загрузку (`source.content` и `multipart`) действует на всех порталах. `source.url` продолжает работать как раньше.\n\n### BC-0716-4: POST \u002Fv1\u002Finfra\u002Fservers отклоняет неизвестные поля в теле\n\n> Поддержка старого формата до: 15.01.2027\n\n**Было**\n\nНеизвестное поле в теле запроса молча игнорировалось. Запрос с `deployMode: \"STANDALONE\"` (несуществующее поле) возвращал `201` и создавал galaxy-приложение вместо ожидаемого выделенного сервера — правильное поле называется `placement: \"dedicated\"`.\n\n**Стало**\n\n[POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) отклоняет тело с неизвестным полем ошибкой `400 UNKNOWN_PARAM`. В `details.unknownFields` перечислены лишние поля, в `details.suggestions` — подсказка правильного имени (`deployMode` → `placement`), в `details.validParams` — полный список допустимых полей.\n\n**Что делать интеграторам**\n\nУбрать из тела поля, которых нет в списке параметров создания, либо исправить опечатку по подсказке `details.suggestions`. Модель размещения задаётся полем `placement` (`auto` по умолчанию, `dedicated` — отдельная виртуальная машина).\n\n### NEW-0716-5: \u002Fv1\u002Fsites\u002Ffields описывает допустимые значения поля type\n\n`GET \u002Fv1\u002Fsites\u002Ffields` теперь возвращает у поля `type` перечень допустимых значений в `type.enum` с подписями: `PAGE` (лендинг), `STORE` (интернет-магазин), `KNOWLEDGE` (база знаний 2.0), а также `VIBE` (сайт из конструктора) и `SMN` (связка с модулем «Управление сайтом»). Значения `VIBE` и `SMN` встречаются только в ответах и доступны только для чтения — создать сайт такого типа через API нельзя.\n\n### NEW-0716-6: Избранное и закрепление задачи без прав на редактирование\n\nДобавлены четыре ручки для «личных» действий над задачей, которые в Битрикс24 разрешены при доступе только на чтение (не на редактирование): `POST \u002Fv1\u002Ftasks\u002F:taskId\u002Ffavorite` добавляет задачу в избранное, `DELETE \u002Fv1\u002Ftasks\u002F:taskId\u002Ffavorite` убирает из избранного, `POST \u002Fv1\u002Ftasks\u002F:taskId\u002Fpin` закрепляет задачу в списке задач текущего пользователя, `DELETE \u002Fv1\u002Ftasks\u002F:taskId\u002Fpin` открепляет. Раньше единственным способом изменить задачу был `PATCH \u002Fv1\u002Ftasks\u002F:id`, который требует прав на редактирование и возвращал «Нет доступа к редактированию задачи», из-за чего разрешённые пользователю действия были недоступны.\n\n### NEW-0716-7: предупреждение о вытесняемом тарифе в ответах пробуждения по расписанию\n\nОтветы [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules](\u002Fdocs\u002Finfra\u002Fwake-schedules\u002Fcreate) и [PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules\u002F:scheduleId](\u002Fdocs\u002Finfra\u002Fwake-schedules\u002Fupdate) теперь дополнительно несут два поля верхнего уровня: `preemptibleAdvisoryCode` и `preemptibleAdvisory`. Если сервер работает на вытесняемом тарифе, `preemptibleAdvisoryCode` равен `\"PREEMPTIBLE_BEST_EFFORT\"`, а `preemptibleAdvisory` — краткое английское пояснение того же факта: подъём такого сервера к моменту окна не гарантирован, окно может быть пропущено при нехватке свободной ёмкости. Для сервера на невытесняемом тарифе оба поля — `null`. Предупреждение не блокирует создание или обновление окна — это тот же неблокирующий паттерн, что уже используют поля `tzWarning`\u002F`tzWarningCode`. Старые интеграции, не читающие новые поля, продолжают работать без изменений.\n\n## 2026-07-14\n\n### NEW-0714-1: OpenAPI-спека: валидность 3.1, семантика полей и срез по скоупу\n\n**Было**\n\nМашинная спека [GET \u002Fv1\u002Fopenapi.json](\u002Fdocs\u002Fcli) содержала 3.0-ключ `nullable` (невалиден в 3.1), не объявляла path-параметр `{entityTypeId}` на batch\u002Faggregate\u002Ffields\u002Fproducts, не несла описаний\u002Fдопустимых значений\u002Fпримеров полей, описывала только ключи `vibe_app_` и отдавалась одним монолитом.\n\n**Стало**\n\nСпека валидна по OpenAPI 3.1: nullable-поля используют union-тип `[\"\u003Cтип>\",\"null\"]`, все path-параметры объявлены. У свойств появились `title`\u002F`description`, допустимые значения (`x-enumValues` + расшифровка в описании) и примеры. Секьюрити-схемы называют все три вида ключей (`vibe_api_`\u002F`vibe_app_`\u002F`vibe_live_`), у каждой операции есть `x-required-scope`, а в корне — каталог `x-scopes`. Добавлены корневые `tags`\u002F`externalDocs`, блок `webhooks` для бот-событий и `anyOf` для мультиполей. `GET \u002Fv1\u002Fopenapi.json?scope=\u003Cscope>` (например `?scope=crm`) отдаёт срез спеки по одному скоупу, чтобы он помещался в контекст агента. Указатели на спеку добавлены в `\u002Fv1\u002Fme` и `\u002Fv1\u002Fguide`. Массовый перенос curl-примеров по каждой операции — отдельная последующая работа.\n\n### FIX-0714-2: Общая 5xx-ошибка на AI-эндпоинтах теперь в OpenAI-совместимом конверте\n\n[AI-эндпоинты](\u002Fdocs\u002Fai) (`\u002Fv1\u002Fai\u002F*`, `\u002Fv1\u002Fmodels`, `\u002Fv1\u002Fchat\u002F*`, `\u002Fv1\u002Faudio\u002F*`) документированы с OpenAI-совместимым форматом ошибок. При исчерпании пула соединений это уже соблюдалось, но общая (непредвиденная) `5xx`-ошибка на этих эндпоинтах отдавала обычный V1-конверт.\n\n**Было**\n\nОбщая `5xx`-ошибка на AI-эндпоинте: `{\"success\": false, \"error\": {\"code\": \"...\", \"message\": \"...\"}}` — не тот формат, что SDK-клиенты OpenAI ожидают для этих путей.\n\n**Стало**\n\nТот же случай теперь отдаёт `{\"error\": {\"message\": \"...\", \"type\": \"...\", \"code\": \"...\"}}` — единый конверт для всех ошибок AI-эндпоинтов, включая общие `5xx`.\n\n**Влияние на интеграторов**\n\nКлиенты на OpenAI SDK, уже читающие `error.type`\u002F`error.code` (штатный путь для этих эндпоинтов), не заметят изменения. Код, который на AI-эндпоинтах ожидал `success`\u002F`error.code` в верхнем уровне именно на общих `5xx`, должен переключиться на `error.type`\u002F`error.code`.\n\n### FIX-0714-3: `versions` в списке версий исходников приложения ограничен 500 записями\n\n`GET \u002Fv1\u002Fapps\u002F:id\u002Fsources` возвращал весь список версий без ограничения — для приложений с очень длинной историей это была неограниченная выборка.\n\n**Было**\n\n`data.versions` — весь список версий без ограничения по размеру; `data.totalVersions` всегда равнялся `data.versions.length`.\n\n**Стало**\n\n`data.versions` содержит не более 500 последних версий (по `savedAt`, убывание). `data.totalVersions` и `data.totalSizeBytes` по-прежнему считаются по полной выборке — точность агрегатов не зависит от кэпа.\n\n**Влияние на интеграторов**\n\nДля приложений с историей до 500 версий поведение не меняется. Для приложений с историей больше 500 версий `data.versions.length` теперь может быть меньше `data.totalVersions` — код, полагавшийся на их равенство, должен ориентироваться на `data.totalVersions`\u002F`data.totalSizeBytes` для агрегатов и не считать `data.versions` полным списком.\n\n### FIX-0714-4: Телефония: `userId`\u002F`duration` теперь по-настоящему валидируются, а не только на truthy\n\n`POST \u002Fv1\u002Fcalls\u002Fregister`, `\u002Fv1\u002Fcalls\u002F:callId\u002Fshow`, `\u002Fv1\u002Fcalls\u002F:callId\u002Fhide`, `\u002Fv1\u002Fcalls\u002F:callId\u002Ffinish` принимали `userId` (и `duration` у `finish`) без проверки типа\u002Fформы — любое truthy-значение (например, строка `\"abc\"` или объект) проходило в Битрикс24 и падало там уже непрозрачной ошибкой апстрима.\n\n**Было**\n\n`{\"userId\": \"abc\"}` (или любое другое truthy, не являющееся положительным целым) проходил валидацию и уходил в Битрикс24; ошибка `Required: userId (number)` появлялась только на полностью пустом\u002Ffalsy значении.\n\n**Стало**\n\n`userId` принимается как положительное целое число либо как числовая строка (`\"42\"`), иначе — чистый `400 MISSING_PARAMS` с уточнённым текстом `Required: userId (positive integer)` (для `register` — плюс `phoneNumber`). `duration` у `finish` аналогично — неотрицательное число либо числовая строка, иначе `400`.\n\n**Влияние на интеграторов**\n\nКорректные вызовы (`userId` — число или числовая строка) не меняются. Вызовы с ранее «проходившим» некорректным `userId`\u002F`duration` (не число, не числовая строка) теперь получают явный `400` вместо непрозрачной ошибки со стороны Битрикс24.\n\n### FIX-0714-5: Создание комментария к несуществующей задаче — понятная ошибка вместо ложного успеха\n\n[POST \u002Fv1\u002Ftasks\u002F:taskId\u002Fcomments](\u002Fdocs\u002Fentities\u002Ftask-comments\u002Fcreate) и его пакетный вариант [POST \u002Fv1\u002Ftasks\u002F:taskId\u002Fcomments\u002Fbatch](\u002Fdocs\u002Fentities\u002Ftask-comments\u002Fcomments-batch) (`action: create`) обращаются к Битрикс24 для создания комментария на старой карточке задачи. Если задача не существует или недоступна ключу, Битрикс24 не создаёт комментарий и не возвращает идентификатор.\n\n**Было**\n\nОба эндпоинта отвечали успехом с пустым идентификатором — одиночный вызов `201 {\"success\": true, \"data\": {\"id\": null}}`, пакетный — элементом `{\"success\": true, \"id\": null}`. Комментарий не создавался, но интегратор не мог отличить это от штатного случая.\n\n**Стало**\n\nОдиночный вызов возвращает `404 TASK_NOT_FOUND`. Пакетный вариант помечает соответствующий элемент как `{\"success\": false, \"error\": \"TASK_NOT_FOUND\"}`, не отменяя обработку остальных элементов пакета. Отдельный, не связанный с этой ошибкой случай сохраняется: на новой карточке задачи комментарий уходит в чат, и если система не смогла восстановить его id отдельным поиском, ответ по-прежнему `success: true` с `id: null` — комментарий в этом случае реально создан.\n\n**Влияние на интеграторов**\n\nКод, проверяющий `data.id` \u002F `data[i].id` на `null` как признак ошибки, продолжит работать без изменений и получит более точный код ошибки. Код, полагавшийся на молчаливый успех с `id: null` при недоступной задаче, должен обрабатывать `404` \u002F `TASK_NOT_FOUND` явно.\n\n### FIX-0714-6: Лимит запросов `\u002Fv1\u002Fsearch`, `\u002Fv1\u002Fresearch`, `\u002Fv1\u002Fbatch` теперь считается на портал\n\n**Было**\n\nЛимиты (`\u002Fv1\u002Fsearch` 60\u002Fмин, `\u002Fv1\u002Fresearch` 20\u002Fмин, `\u002Fv1\u002Fbatch` 30\u002Fмин) фактически считались по IP-адресу, а не по порталу. Портал с несколькими API-ключами (или несколько порталов за одним общим egress-IP) мог получить больше документированного лимита, а само ограничение обходилось ротацией IP.\n\n**Стало**\n\nЛимит считается **на портал**: все API-ключи одного портала делят единый бакет (60 \u002F 20 \u002F 30 запросов в минуту соответственно). Документированный лимит на тенант теперь применяется корректно и не обходится ни числом ключей, ни сменой IP.\n\n**Влияние на интеграторов**\n\nЕсли ваш портал распределял нагрузку на `\u002Fv1\u002Fsearch` \u002F `\u002Fv1\u002Fresearch` \u002F `\u002Fv1\u002Fbatch` между несколькими API-ключами, суммарный предел теперь — единый лимит портала, а не сумма по ключам. При достижении предела возвращается `429` с заголовком `Retry-After` (как и раньше).\n\n### FIX-0714-7: Календарь: рабочий batch-delete секций, чистые ошибки update и без утечки sync-полей\n\n**Было**\n\n- `POST \u002Fv1\u002Fcalendar-sections\u002Fbatch` с `action: \"delete\"` не имел канала для `type`\u002F`ownerId`, которые требует `calendar.section.delete` — каждый элемент падал на обеих платформах.\n- `PATCH \u002Fv1\u002Fcalendar-sections\u002F{id}` без `type`\u002F`ownerId`\u002F`name` уходил в Битрикс24 и возвращал сырой `422` с именем внутреннего метода.\n- `GET \u002Fv1\u002Fcalendar-sections` отдавал недокументированные сырые поля Битрикс24 `GAPI_CALENDAR_ID`, `CAL_DAV_CON`, `SYNC_TOKEN`, `PAGE_TOKEN`, `EXTERNAL_TYPE` (три из них — токены синхронизации).\n- `GET \u002Fv1\u002Fcalendar-events` и `GET \u002Fv1\u002Fcalendar-events\u002F{id}` отдавали внутреннее поле `attendeesEntityList` — схема пыталась его вырезать, но не срабатывала из-за несовпадения регистра ключа.\n\n**Стало**\n\n- Batch-delete секций читает `type`\u002F`ownerId` из тела рядом с `ids` и прокидывает их в каждую команду удаления. Отсутствие любого — чистый `400 MISSING_REQUIRED_PARAMS` до вызова Битрикс24.\n- Partial-update секций требует якорные `type`\u002F`ownerId`\u002F`name` (у секций нет get-by-id для подстановки) — чистый `400`, без сырого `422` и без утечки имени метода.\n- Оба календарных read-пути убирают перечисленные внутренние\u002Fsync-поля из ответа.\n\n**Затронутые эндпоинты:**\n\n- [`POST \u002Fv1\u002Fcalendar-sections\u002Fbatch`](\u002Fdocs\u002Fentities\u002Fcalendar-sections\u002Fcreate)\n- [`PATCH \u002Fv1\u002Fcalendar-sections\u002F{id}`](\u002Fdocs\u002Fentities\u002Fcalendar-sections\u002Fupdate)\n- [`GET \u002Fv1\u002Fcalendar-sections`](\u002Fdocs\u002Fentities\u002Fcalendar-sections\u002Flist)\n- [`GET \u002Fv1\u002Fcalendar-events`](\u002Fdocs\u002Fentities\u002Fcalendar-events\u002Flist)\n\n### FIX-0714-8: PATCH catalog-product-properties снова работает (был неустранимый catch-22)\n\n**Было**\n\nОбновить свойство товара было невозможно ни при каком теле: `PATCH` без `iblockId` → `422` («Required fields: iblockId» — B24 требует его на каждом update), а `PATCH` **с** `iblockId` → `400 READONLY_FIELD` (поле только для создания). Итог — весь глагол UPDATE мёртв: ни одно поле нельзя было изменить после создания.\n\n**Стало**\n\n`iblockId` теперь подставляется автоматически из существующей записи (pre-fetch, как у `catalog-sections`), так что `PATCH {name:\"…\"}` доходит до B24 с нужным `iblockId` и возвращает `200`. `iblockId` в теле по-прежнему не нужен (и по-прежнему отклоняется как read-only, если его прислать) — его сохраняет сам сервис.\n\n**Влияние на интеграторов**\n\nЕсли раньше ваш `PATCH` свойства товара всегда падал `422`\u002F`400` — теперь шлите только изменяемые поля (`PATCH {name:\"…\"}`), `iblockId` передавать не надо.\n\n### FIX-0714-9: Валидация entityTypeId: мусорные формы → 400 вместо тихого усечения\n\n**Было**\n\nПять поверхностей парсили `entityTypeId` (селектор типа smart-process \u002F динамической сущности) снисходительно — неякорным `parseInt` или коэрсирующим `Number()`, — и мусорная форма молча превращалась в ДРУГОЙ (в рамках портала) тип сущности:\n\n- Путевой `entityTypeId` (`\u002Fv1\u002Fitems\u002F:entityTypeId\u002F...`, `\u002Fv1\u002Fcategories\u002F:entityTypeId\u002F...` — CRUD и `\u002Faggregate`): `GET \u002Fv1\u002Fitems\u002F1058abc` усекался до `1058`, `1e3` → `1`, `1.5` → `1`.\n- Глобальный `POST \u002Fv1\u002Fbatch`: `params.entityTypeId` через `Number()` принимал дробные (`1.5`), hex (`'0x10'` → 16), переполнение до `Infinity`, а также массив `[1058]` → 1058.\n- `\u002Fv1\u002Fitems\u002F:entityTypeId\u002Fuserfields\u002F*`: собственный парсер — `2abc` резолвил пользовательские поля типа `2`.\n- `POST \u002Fv1\u002Fsmart-processes\u002Fbatch`: `ids: ['1030abc']` усекался до `1030` — delete\u002Fupdate молча выполнялся против ЧУЖОГО реального типа; дробное число (`1030.5`) тоже проходило.\n- `POST \u002Fv1\u002Ftriggers\u002Ffire` (`entityType=\"item\"`): `entityTypeId: '1038abc'` → триггер автоматизации выстреливал по типу `1038`.\n\n**Стало**\n\nВсе пять поверхностей требуют каноничную форму положительного целого (`\u002F^[1-9]\\d*$\u002F` для строк, `Number.isInteger` для чисел): любая иная форма → `400` с прежним кодом ошибки поверхности (`INVALID_DYNAMIC_PARAM` \u002F `INVALID_ENTITY_TYPE_ID` \u002F `BATCH_ITEM_VALIDATION` \u002F `MISSING_PARAMS`) ДО вызова Битрикс24. Aggregate использует общий с CRUD-маршрутами валидатор вместо инлайн-копии.\n\n**Влияние на интеграторов**\n\nФормы, которые раньше коэрсились в корректное значение и обслуживались — `007` → 7, `%20`-пробелы, `+2` → 2, массив `[1058]` в batch — теперь отклоняются с `400`: значение должно быть каноничным целым без префиксов, хвостов и ведущих нулей. Boolean в batch отклонялся и раньше (коэрсился в reserved-тип); меняется только код ошибки — теперь `INVALID_DYNAMIC_PARAM`. Корректные вызовы не меняются.\n\n### FIX-0714-10: Переоткрытие фидбека очищает поля решения\n\n**Было**\n\n`PATCH \u002Fv1\u002Ffeedback\u002F:id` (и админ-эндпоинт `PATCH \u002Fapi\u002Fplatform\u002Ffeedback\u002F:id`, через который переоткрывает админ-UI) при возврате тикета в активный статус (`NEW`, `REVIEWING`, `AWAITING_USER`, `NEEDS_REVIEW`) из `RESOLVED`\u002F`WITHDRAWN` не сбрасывал `resolvedAt`, `resolvedBy` и `resolution` — они «зависали» от предыдущего закрытия, и переоткрытый тикет выглядел одновременно активным и решённым.\n\n**Стало**\n\nВозврат тикета **из** `RESOLVED`\u002F`WITHDRAWN` в активный статус очищает `resolvedAt`, `resolvedBy` и `resolution`. `RESOLVED`\u002F`WITHDRAWN` по-прежнему проставляют штамп решения; `ARCHIVED` не трогает поля (архивирование сохраняет историю решения). Обычный переход между активными статусами (например `AWAITING_USER → REVIEWING`) поля НЕ трогает — на активных тикетах `resolution` отражает последний комментарий команды. Явно переданный в том же запросе `resolution` имеет приоритет над очисткой.\n\n**Влияние на интеграторов**\n\nЕсли вы читали `resolvedAt`\u002F`resolution` переоткрытого тикета и получали значения от прошлого закрытия — теперь они `null` для активного тикета.\n\n### FIX-0714-11: `INVALID_JSON_BODY` больше не цитирует внутренний текст парсера\n\n**Было**\n\nШесть маршрутных групп (`\u002Fapi\u002Fbilling\u002F*`, `\u002Fv1\u002Fapps*`, `\u002Fv1\u002Fbots*`, `\u002Fv1\u002Fkeys*`, `\u002Fv1\u002Fnote*`, `\u002Fv1\u002Finfra\u002Fservers\u002F*` deploy\u002Fexec\u002Fupload) на битый JSON отвечали `400` с сообщением вида `Invalid JSON: Unexpected token } in JSON at position 41` — сырой текст движка V8 (отпечаток рантайма, деталь реализации). Группа deploy\u002Fexec\u002Fupload при этом не ставила и код ошибки.\n\n**Стало**\n\nВсе шесть отвечают единым статическим сообщением `Request body is not valid JSON.` — как `\u002Fv1\u002F\u003Centities>` (тот же класс закрывается там отдельным исправлением). На V1-поверхностях код — `INVALID_JSON_BODY` (deploy\u002Fexec\u002Fupload теперь тоже его ставит); статус `400` не изменился.\n\n**Влияние на интеграторов**\n\nЕсли ваш код парсил текст сообщения (например, вытаскивал позицию ошибки) — опирайтесь на код `INVALID_JSON_BODY`; позиция ошибки больше не сообщается.\n\n### FIX-0714-12: Смета: amount, currency и даты в ответе GET снова заполнены (были null)\n\n**Было**\n\nЧтение сметы (`GET`\u002Flist\u002Fsearch `\u002Fv1\u002Fquotes`) отдавало `null` для `amount`, `currency`, `beginDate`, `closeDate` — значения «протекали» только под сырыми ключами Битрикс24 (`opportunity`, `currencyId`, `begindate`, `closedate`). Запись работала правильно, но READ-проекция теряла все поля-алиасы: **сумма сметы и валюта были 100% невидимы через задокументированный API**.\n\n**Стало**\n\nREAD-ветка теперь реверс-мапит объявленные алиасы (зеркало write-маппинга): `opportunity → amount`, `currencyId → currency`, `begindate → beginDate`, `closedate → closeDate` — с приведением типов. Сырые ключи Битрикс24 в ответе больше не появляются.\n\n**Влияние на интеграторов**\n\nЕсли вы читали `amount`\u002F`currency` смет и получали `null` — теперь они заполнены. Код, читавший обходным путём сырой `opportunity`\u002F`currencyId` из ответа, их там больше не найдёт — переключитесь на документированные `amount`\u002F`currency`.\n\n### FIX-0714-13: Валидация записи: фантомный чек-лист → 404, мусорные типы и `:id` → 400\n\n**Было**\n\n- `POST \u002Fv1\u002Ftasks\u002F:taskId\u002Fchecklist` на несуществующую задачу возвращал `201` с правдоподобным `id`, хотя пункт не создавался (его нельзя было получить GET-ом).\n- `POST \u002Fv1\u002Fwarehouses` принимал нестроковые `title`\u002F`address` (число, объект) и пересылал их в Битрикс24 с непредсказуемым результатом; `POST \u002Fv1\u002Fdoc-templates` так же пропускал нестроковые `name`\u002F`region` и нечисловой `numeratorId`.\n- Нечисловой `:id` в путях сущностей с типизированным числовым id (`GET\u002FPATCH\u002FDELETE \u002Fv1\u002Fquotes\u002Fabc`, `\u002Fv1\u002Fdeals\u002F1.5`, `\u002Fv1\u002Fleads\u002F1e3`) уходил в Битрикс24 как есть — в ответ прилетала непрозрачная ошибка B24 вместо внятного кода. У smart-processes `12abc` усекался `parseInt`-ом до `12` и попадал в ЧУЖОЙ тип.\n\n**Стало**\n\n- Чек-лист: родительская задача проверяется до создания пункта; несуществующая (или недоступная ключу) задача → `404 TASK_NOT_FOUND`.\n- Склады и шаблоны документов: значение не того типа → `400 INVALID_PARAMS` без вызова Битрикс24 (числовая строка в `numeratorId` по-прежнему принимается).\n- Сущности с явно типизированным числовым id: не-канонично-целый `:id` → `400 INVALID_PARAMS` до вызова Битрикс24 (id `0` — основная воронка сделок `categories` — остаётся валидным). Smart-processes сохраняют `INVALID_ENTITY_TYPE_ID` и теперь отклоняют `12abc` на GET\u002FPATCH\u002FDELETE, а не усекают до `12`. Сущности, у которых тип id не задекларирован в схеме, сохраняют прежнее сквозное поведение.\n\nЗатронутые эндпоинты: [POST \u002Fv1\u002Ftasks\u002F:taskId\u002Fchecklist](\u002Fdocs\u002Fentities\u002Ftasks\u002Fchecklist), [POST \u002Fv1\u002Fwarehouses](\u002Fdocs\u002Fentities\u002Fwarehouses\u002Fcreate), [POST \u002Fv1\u002Fdoc-templates](\u002Fdocs\u002Fentities\u002Fdoc-templates\u002Fcreate) + GET\u002FPATCH\u002FDELETE по сущностям с типизированным числовым id.\n\n**Влияние на интеграторов**\n\nЕсли ваш код опирался на фантомный `201` от чек-листа или отправлял мусорные значения «на авось» — теперь придёт явный `4xx` с кодом. Корректные вызовы не меняются.\n\n### FIX-0714-14: Пять тихих false-success\u002Fhint-дефектов: честный ответ вместо мнимого успеха\n\n**Было**\n\n- `PATCH \u002Fv1\u002Fuserfields\u002F{entity}\u002F{id}` c `label` — тихий no-op на обновлении: ответ `200`, но подпись поля не менялась (Битрикс24 `crm.*.userfield.update` игнорирует `LABEL`).\n- `POST \u002Fv1\u002Fhumanresources\u002Fnodes\u002F{id}` c `parentId` — ложный «перенос удался»: `name` применялся, `parentId` молча игнорировался, а в ответе эхом возвращался старый родитель.\n- `POST \u002Fv1\u002Fchats\u002Fmessages\u002Fbulk` — псевдоним `dialogId: \"me\"` не разрешался в bulk-цикле (одиночные роуты его разрешают) → сообщение уходило не туда.\n- `POST \u002Fv1\u002Fbots\u002F{botId}\u002Fchats\u002F{dialogId}\u002Fusers` — предохранитель `USERS_NOT_ADDED` был мёртвым кодом (не совпадал с формой ответа метода v2) → неудачное добавление проходило как успех.\n- Ошибки `crm.item.list` получали нерелевантную подсказку «Maximum 50 records…» даже когда причина была иной (например «entity type does not exist»).\n\n**Стало**\n\n- `label` на обновлении разворачивается в реальные параметры `EDIT_FORM_LABEL`\u002F`LIST_COLUMN_LABEL`\u002F`LIST_FILTER_LABEL` — подпись действительно меняется.\n- `parentId` и `type` теперь только для создания: на `PATCH` они отклоняются с `400` (перенос — через `POST \u002Fv1\u002Fhumanresources\u002Fnodes\u002F{id}\u002Fmove`), а не имитируют успех.\n- Bulk-чтение сообщений разрешает `dialogId: \"me\"` для каждого элемента, как и одиночные роуты.\n- Предохранитель добавления в бот-чат снова срабатывает: непринятые пользователи возвращаются в `warning.USERS_NOT_ADDED`.\n- Подсказки об известных ограничениях привязаны к релевантности сообщения ошибки, а не только к имени метода.\n\n**Затронутые эндпоинты:**\n\n- [`PATCH \u002Fv1\u002Fuserfields\u002F{entity}\u002F{id}`](\u002Fdocs\u002Fuserfields\u002Fcrm\u002Fupdate)\n- [`PATCH \u002Fv1\u002Fhumanresources\u002Fnodes\u002F{id}`](\u002Fdocs\u002Fhumanresources\u002Fnodes\u002Fupdate)\n- [`POST \u002Fv1\u002Fchats\u002Fmessages\u002Fbulk`](\u002Fdocs\u002Fchats)\n- [`POST \u002Fv1\u002Fbots\u002F{botId}\u002Fchats\u002F{dialogId}\u002Fusers`](\u002Fdocs\u002Fbots)\n\n### FIX-0714-15: GET \u002Fv1\u002Ftask-time теперь честно возвращает больше 50 записей при limit>50\n\n**Было**\n\n[GET \u002Fv1\u002Ftask-time](\u002Fdocs\u002Fentities\u002Ftasks\u002Ftime) с `limit` больше 50 возвращал только 50 записей, хотя `meta.limit` повторял запрошенное значение, а `meta.hasMore` мог вводить в заблуждение. Клиент, обходивший данные постранично с шагом больше 50, молча терял часть записей.\n\n**Стало**\n\nЗапрос отдаёт до `limit` записей (максимум 500), собирая их постранично на стороне бэкенда; `meta.total` и `meta.hasMore` соответствуют фактически возвращённому окну. При `limit` больше 50 значение `offset` теперь указывает на правильную позицию, а не сдвигается на первые страницы.\n\n### NEW-0714-16: GET \u002Fv1\u002Fcompanies\u002Ffields и системные поля catalog-prices получили label и description\n\nВ ответе [GET \u002Fv1\u002Fcompanies\u002Ffields](\u002Fdocs\u002Fentities\u002Fcompanies\u002Ffields) теперь у всех полей компании есть человекочитаемые `label` и `description`. В [GET \u002Fv1\u002Fcatalog-prices\u002Ffields](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Ffields) те же метки добавлены системным полям `extraId`, `priceScale` и `timestampX`. Метки локализуются под язык сегмента. Семантику поля можно получить программно из ответа, без сверки со статической документацией.\n\n### FIX-0714-17: \u002Fstop и \u002Freboot различают отсутствующий сервер и неподходящий статус\n\n**Было**\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fstop](\u002Fdocs\u002Finfra\u002Flifecycle\u002Fstop) и [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Freboot](\u002Fdocs\u002Finfra\u002Flifecycle\u002Freboot) на сервере не в статусе `running` (например, спящем) отвечали плоским `404 NOT_FOUND` с текстом «Running server not found» — по нему нельзя было понять, что сервер существует, и агент решал, что он удалён.\n\n**Стало**\n\nОба маршрута ведут себя как [\u002Fstart](\u002Fdocs\u002Finfra\u002Flifecycle\u002Fstart) и [\u002Fwake](\u002Fdocs\u002Finfra\u002Flifecycle\u002Fwake): `404 SERVER_NOT_FOUND` — только когда сервера с таким `id` действительно нет; `422 SERVER_WRONG_STATE` — когда сервер есть, но не в статусе `running`. В `error.currentState` возвращается текущее состояние, в `error.availableActions` — доступные действия (`wake`\u002F`start`\u002F`repair`\u002F`delete`).\n\n### FIX-0714-18: понятная ошибка на \u002Ffields у комментариев и учёта времени задач\n\n**Было**\n\nЗапрос [GET \u002Fv1\u002Ftasks\u002F:taskId\u002Fcomments\u002Ffields](\u002Fdocs\u002Fentities\u002Ftask-comments) отвечал сбивающим с толку `400 INVALID_PARAMS` с текстом «taskId and id must be positive integers» (спрашивали про поля — ответ про идентификатор), а [GET \u002Fv1\u002Ftasks\u002F:taskId\u002Ftime\u002Ffields](\u002Fdocs\u002Fentities\u002Ftasks\u002Ftime) утекал сырой ошибкой Bitrix24 с внутренним PHP-методом и HTML-тегом.\n\n**Стало**\n\nОбе сущности распознают сегмент `fields` и возвращают понятный `400 WRONG_PATH`: у них нет метода `\u002Ffields` (состав полей описан в справочнике `\u002Fv1\u002Fguide`), в тексте перечислены доступные маршруты. Нечисловой или дробный идентификатор в маршрутах по id теперь отклоняется как `400 INVALID_PARAMS` до обращения к Bitrix24 — без утечки внутренней ошибки.\n\n### FIX-0714-19: Лимиты `\u002Fv1\u002Fai\u002Fcredentials*` считаются на портал; `CREDENTIAL_NOT_FOUND` подсказывает решение\n\n**Было**\n\n- Лимиты BYOK-маршрутов (`POST \u002Fv1\u002Fai\u002Fcredentials`, `\u002F:id\u002Ftest`, `\u002F:id\u002Ffetch-models` — 10\u002Fмин; `\u002F:id\u002Fmodels` добавление\u002Fудаление — 30\u002Fмин) фактически считались по IP-адресу: перебор чужих ключей обходился ротацией IP, а арендаторы за одним общим egress-IP делили один бакет. Маршрут `PATCH \u002F:id` (при передаче `credentials` он проверяет ключ у апстрима — тот же оракул, что `\u002F:id\u002Ftest`) не имел лимита вовсе.\n- `404 CREDENTIAL_NOT_FOUND` от `\u002Fv1\u002Fsearch` и `\u002Fv1\u002Fresearch` содержал только слаг провайдера — без указания, как настроить ключ.\n\n**Стало**\n\n- Лимит считается **на портал**: все API-ключи одного портала делят единый бакет; ротация IP и число ключей на предел не влияют. При превышении — `429` с `Retry-After`. Дополнительно `PATCH \u002F:id` (проверяет ключ у апстрима, как `\u002F:id\u002Ftest`) раньше вовсе не имел лимита — теперь тоже 10\u002Fмин на портал.\n- В ответ `CREDENTIAL_NOT_FOUND` добавлено поле `hint` с точным рецептом: `POST \u002Fv1\u002Fsearch\u002Fcredentials {provider, apiKey}`, список провайдеров — `GET \u002Fv1\u002Fsearch\u002Fproviders`.\n\n### FIX-0714-20: PATCH bizproc-шаблонов, роботов и активностей через \u002Fv1 применяет изменения\n\n**Было**\n\n`PATCH \u002Fv1\u002Fbizproc-templates\u002F:id`, `\u002Fv1\u002Fbizproc-robots\u002F:code` и `\u002Fv1\u002Fbizproc-activities\u002F:code` с полями метаданных (`name`, `description`, `autoExecute`) возвращали `422 BITRIX_ERROR \"No fields to update.\"` — обновить сущность было нельзя (то же и в пакетных запросах). Поле `autoExecute`, переданное числом, дополнительно отвергалось как `Incorrect field AUTO_EXECUTE!`.\n\n**Стало**\n\nПоля корректно применяются (в том числе `autoExecute`, переданное числом); эндпоинт подтверждает успех и возвращает `id` обновлённой сущности. Работают и одиночный `PATCH`, и пакетные запросы. Создание (`POST`) не изменилось.\n\n### FIX-0714-21: Транскрипция: проверка кошелька до вызова распознавания\n\n**Было**\n\n`POST \u002Fv1\u002Faudio\u002Ftranscriptions` (и `\u002Fv1\u002Fai\u002Faudio\u002Ftranscriptions`) не проверял состояние кошелька перед вызовом: PREPAY-счёт, ушедший за овердрафт (но ещё не замороженный фоновой проверкой), всё равно запускал распознавание и уводил баланс глубже в минус. Чат и эмбеддинги отклоняли такой вызов заранее; полностью замороженный счёт и раньше блокировался глобально (`ACCOUNT_FROZEN`).\n\n**Стало**\n\nКак у чата и эмбеддингов: денежная проверка выполняется **до** обращения к Whisper. Превышенный овердрафт → `402 insufficient_balance`, распознавание не запускается. BYOK-ключи (USER-scope) бесплатны — для них проверки нет и поведение не меняется.\n\n### FIX-0714-22: удаление приложения больше не блокируется галактическим хостом на его ключе\n\n**Было**\n\n`DELETE \u002Fv1\u002Fapps\u002F:id` возвращал `409 APP_HAS_ACTIVE_SERVERS`, если на ключе приложения оказывался галактический хост (общая инфраструктура портала). Снять хост через удаление приложения было нельзя, а объяснения в ответе не было.\n\n**Стало**\n\nГалактический хост исключён из проверки блокирующих серверов: он управляется на уровне портала, а не ключа, поэтому не должен мешать удалению приложения. Отдельные серверы приложения (в том числе контейнеры приложений) по-прежнему блокируют удаление с `409 APP_HAS_ACTIVE_SERVERS` — их сначала нужно перепривязать к другому ключу.\n\n### FIX-0714-23: OpenAPI: тело per-entity батч-эндпоинтов теперь описано верно (action + items\u002Fids\u002Fcalls)\n\n**Было**\n\nСпека (`GET \u002Fv1\u002Fopenapi.json`) описывала тело каждого пер-сущностного батча как `{create:[], update:[], delete:[]}`. Рантайм же (общий обработчик батчей) требует `{action, items|ids|calls}` и отвечает `400 INVALID_BATCH_ACTION` на документированную форму. Клиент, сгенерированный из спеки (codegen \u002F AI-агент), получал **100% отказ batch-write** на всех ~45 пер-сущностных батч-эндпоинтах. Сама фича работает — врала только спека (глобальный `POST \u002Fv1\u002Fbatch`, `\u002Fv1\u002Ftasks\u002F{taskId}\u002Fcomments\u002Fbatch` и `\u002Fv1\u002Fguide` уже описывали правильную форму).\n\n**Стало**\n\nГенератор спеки выдаёт правильную форму: один `action` (`create`\u002F`update`\u002F`delete`\u002F`list`\u002F`get`\u002F`fields`); `create`\u002F`update` шлют `items`, `delete` — `ids`, чтения — `calls`. Совпадает с рантаймом и с глобальным `\u002Fv1\u002Fbatch`.\n\n**Влияние на интеграторов**\n\nЕсли вы генерировали клиент из `openapi.json` и batch-write падал `INVALID_BATCH_ACTION` — перегенерируйте: тело теперь `{action:\"create\", items:[…]}` вместо `{create:[…]}`. Handwritten-клиенты, уже славшие `{action,…}`, не затронуты.\n\n### FIX-0714-24: Валюты: fullName, формат и число знаков теперь сохраняются при плоской записи\n\n**Было**\n\n`POST`\u002F`PATCH \u002Fv1\u002Fcurrencies` с плоскими `fullName`, `formatString`, `decimals`, `decPoint`, `thousandsSep` возвращал успех, но значения **молча терялись** — Битрикс24 хранит их в структуре локализации по языкам (`LANG`), а API слал их плоско. Задокументированный обходной путь «передавайте сырой `LANG`» тоже не работал: `LANG` — поле только для чтения, запрос отклонялся.\n\n**Стало**\n\nAPI упаковывает плоские локализуемые поля в локализацию **вашего** языка (языка API-ключа) перед вызовом Битрикс24, поэтому плоская запись сохраняется и читается обратно (`POST {fullName:\"…\",decimals:3}` → `GET` вернёт их). Работает на всех путях записи: одиночном, `POST \u002Fv1\u002Fcurrencies\u002Fbatch` и глобальном `POST \u002Fv1\u002Fbatch`. Сырой `LANG` в теле по-прежнему отклоняется как read-only. Разные значения сразу для нескольких языков через API пока не задаются. Язык записи определяется локалью API-ключа (ru или en) и может не совпадать с языком отображения валюты на портале — на порталах с иной локалью (de\u002Fpl\u002Fua…) правка ляжет под en.\n\n**Влияние на интеграторов**\n\nЕсли вы обходили баг сырым `LANG` (и упирались в `400 READONLY_FIELD`) — уберите его и шлите плоские поля. Уже работавшие плоские запросы теперь ещё и сохраняют значения.\n\n### FIX-0714-25: Aggregate уважает обязательные фильтры; infra-валидация не течёт сырым Zod\n\n**Было**\n\n- `POST \u002Fv1\u002F\u003Centity>\u002Faggregate` с сущностью, требующей фильтр (напр. `catalog-products` требует `iblockId`), пропускал пустой запрос в Битрикс24 и возвращал сырой `422`, тогда как GET-list\u002Fsearch на том же условии отдают чистый `400`.\n- `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002F{deploy,exec,upload,logs}` при ошибке валидации тела клал в `error.message` многострочный JSON-сериализованный массив Zod-issues (сырой отпечаток валидатора).\n\n**Стало**\n\n- Aggregate проверяет обязательные фильтры\u002Fпараметры до вызова Битрикс24: нет обязательного фильтра — `400 MISSING_REQUIRED_FILTER` (напр. `catalog-products`→`iblockId`); нет обязательного list-параметра — `400 MISSING_REQUIRED_PARAMS` (напр. `calendar-events`→`type`,`ownerId`; `humanresources-nodes`→`type`). Как у list\u002Fsearch.\n- Infra-валидация форматирует ошибку компактно (`поле: сообщение; …`), как соседний `infra.ts`. Код (`VALIDATION_ERROR`) и статус `400` не изменились.\n\n**Влияние на интеграторов**\n\nЕсли вы ловили сырой `422` от aggregate без фильтра — теперь придёт `400 MISSING_REQUIRED_FILTER`. Если парсили infra-`error.message` как JSON — теперь это плоская строка `поле: сообщение`.\n\n### FIX-0714-26: Batch: работает per-entity batch для items и создание папок через batch\n\n**Было**\n\n- `POST \u002Fv1\u002Fitems\u002F{entityTypeId}\u002Fbatch` возвращал `404` — per-entity batch-роут для сущностей с динамическим параметром (`items`, `categories`) монтировался по адресу без сегмента параметра (`\u002Fv1\u002Fitems\u002Fbatch`), поэтому документированный путь не находился, а `entityTypeId` не доходил до команды Битрикс24. Обходной путь — глобальный `POST \u002Fv1\u002Fbatch` — работал.\n- `POST \u002Fv1\u002Ffolders\u002Fbatch` с `action: \"create\"` падал на каждом элементе с `ERROR_ARGUMENT`: batch-создание слало `fields[...]`, тогда как `disk.folder.addsubfolder` ожидает родительскую папку верхним параметром `id`, а остальные поля — под `data[...]`.\n\n**Стало**\n\n- Per-entity batch-роут для `items`\u002F`categories` монтируется с сегментом `:{entityTypeId}` и прокидывает валидированный `entityTypeId` (положительное целое; id выделенных API вроде `deals=2` отклоняются с подсказкой — как в одиночных роутах) в каждую команду — для **всех** действий: `create`\u002F`update`\u002F`delete` и read-действий `list`\u002F`get`\u002F`fields`.\n- Batch-создание папок повторяет форму одиночного роута: `id=\u003Cродитель>&data[...]`. Пропущенный `parentId` — чистый per-item `400` до вызова Битрикс24.\n\n**Затронутые эндпоинты:**\n\n- [`POST \u002Fv1\u002Fitems\u002F{entityTypeId}\u002Fbatch`](\u002Fdocs\u002Fentities\u002Fitems\u002Fcreate)\n- [`POST \u002Fv1\u002Ffolders\u002Fbatch`](\u002Fdocs\u002Fentities\u002Ffolders\u002Fcreate)\n\n### NEW-0714-27: восстановление зависшего exec-канала сервера\n\nНовый эндпоинт [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Funstick](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fexec) принудительно освобождает залипший канал команд Black Hole-сервера, когда деплой или exec продолжает отвечать `EXEC_BUSY` («Another command is running») даже после `DELETE \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Flock`. Эндпоинт снимает блокировку на стороне платформы и разрывает туннель агента — при переподключении агент завершает зависшую команду и освобождает свой мьютекс. Перезагрузка сервера не выполняется.\n\nЕсли в момент вызова на сервере ещё выполняется легитимная операция (деплой, exec, харден), эндпоинт по умолчанию отвечает `409 OPERATION_IN_PROGRESS` и НЕ трогает её — расклинивать нужно только зависший канал. Повторите с `?force=true`, если уверены, что канал команд действительно завис.\n\nОтвет: `{ 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`. Ошибка `\u002Fexec` (`EXEC_BUSY`) и провал `\u002Fdeploy` (код `DEPLOY_FAILED`, сообщение «Another command is running») теперь несут подсказку `hint` с этим эндпоинтом.\n\n### NEW-0714-28: описание сервера в `PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id`\n\n**Было**\n\n`PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id` принимал только `displayName`. Поле `description` в контракте отсутствовало, а `GET`-ответы его не отдавали.\n\n**Стало**\n\n`PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id` принимает опциональное поле `description` (строка, до 500 символов; пустая строка или `null` очищает описание; отсутствие поля оставляет текущее значение без изменений). Значение синхронизируется с карточкой приложения в каталоге. Поле `description` теперь возвращается в `GET \u002Fv1\u002Finfra\u002Fservers`, `GET \u002Fv1\u002Finfra\u002Fservers\u002F:id` и в ответе `PATCH`. Старые запросы без `description` продолжают работать без изменений.\n\n### FIX-0714-29: деплой galaxy-приложения при обрыве связи во время сборки возвращает честную ошибку, а не ложный успех\n\n**Было**\n\nЕсли соединение с хостом обрывалось прямо во время сборки galaxy-приложения (частое явление под нагрузкой тяжёлой сборки), `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` мог вернуть `200` со статусом `running` и пометкой `[recovered]` в `buildLog`, хотя новая версия так и не собралась и не поднялась — на хосте продолжал работать прежний контейнер. Повторный деплой упирался в тот же обрыв и снова рапортовал ложный успех.\n\n**Стало**\n\nДеплой считается восстановленным, только если он полностью завершился: под именем приложения запущен контейнер именно этой попытки, и деплой прошёл до конца. Если связь оборвалась во время сборки — или в любой момент до завершения деплоя — и новая версия не поднялась полностью, эндпоинт возвращает retryable-ошибку `502` с кодом `GALAXY_HOST_UNREACHABLE` и подсказкой «повторите тот же деплой, не удаляйте сервер» — вместо ложного `200`. Только если деплой завершился полностью и потерялся лишь финальный ответ, восстановление в `200` работает как прежде.\n\n### BC-0714-30: переименование приложения в каталоге доходит до Битрикс24, publish потерял menuTitle\n\n> Поддержка старого формата до: 14.01.2027\n\n**Было**\n\n[PATCH \u002Fv1\u002Fapps\u002F:id](\u002Fdocs\u002Fapps\u002Fupdate) с полем `title` у приложения, добавленного в каталог, возвращал `200`, но не менял ничего из того, что видит пользователь: карточка каталога и привязки мест встраивания на портале (пункт левого меню, вкладки CRM) оставались со старым именем. Отказа не было никогда — вызов всегда успешен.\n\nУ [POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish](\u002Fdocs\u002Fapps\u002Fpublish) тело не проверялось, а заголовок места встраивания задавался отдельным полем `menuTitle`.\n\n**Стало**\n\nДля приложения в каталоге `title` — это одна операция над отображаемым именем: имя синхронизируется в карточку каталога (вместе с `title` пишется `catalogTitle`) и перепривязывается в места встраивания на портале. Поэтому вызов, который раньше всегда отдавал `200`, теперь может честно отказать:\n\n- `400 NO_USER_TOKEN` — приложение не авторизовано на портале, перепривязать места встраивания нечем;\n- `400 TITLE_TOO_LONG_FOR_CATALOG` — имя приложения в каталоге ограничено 100 символами, тогда как `title` допускает 255;\n- `502 BITRIX_PARTIAL_REBIND` — Битрикс24 отклонил привязку. Имя в этом случае не записывается, повтор того же запроса чинит состояние.\n\nТело `POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish` теперь валидируется, а параметр `menuTitle` удалён: заголовок места встраивания всегда равен отображаемому имени приложения. Пустое тело по-прежнему работает — публикация берёт значения из записи приложения.\n\n**Что делать интеграторам**\n\n- Убрать `menuTitle` из тела публикации: поле игнорируется, имя пункта меню задаётся через `title` и `catalogTitle`.\n- Держать имя приложения в каталоге в пределах 100 символов.\n- Обработать отказы при переименовании: на `NO_USER_TOKEN` авторизовать приложение на портале, на `BITRIX_PARTIAL_REBIND` повторить запрос.\n- Учесть побочный эффект: переименование через `title` теперь пишет и `catalogTitle` — у приложения в каталоге эти поля держатся синхронными.\n\n### NEW-0714-31: GET \u002Fv1\u002Ftasks\u002F:taskId\u002Fcomments\u002Ffields — схема полей комментариев задачи\n\nУ комментариев к задаче появился метод `\u002Ffields`, как у остальных сущностей: `GET \u002Fv1\u002Ftasks\u002F:taskId\u002Fcomments\u002Ffields` возвращает статическую схему из 5 полей (`id`, `taskId`, `authorId`, `message`, `createdAt`) с типом, признаком «только для чтения», названием и описанием на языке сегмента. Метод не обращается к Битрикс24. Раньше этот путь возвращал `400 WRONG_PATH` — состав полей был доступен только из статических доков.\n\n### FIX-0714-32: пер-сущностный batch с action list теперь применяет filter\n\n**Было**\n\n`POST \u002Fv1\u002F{entity}\u002Fbatch` с `action: \"list\"` игнорировал `filter`: имена полей не приводились к именам Битрикс24 (например `statusId` у лидов не превращался в `stageId`), а операторы `$gt` \u002F `$contains` \u002F `$in` и другие не работали. Вызов возвращал `200` со всей таблицей — тихий отказ с неверными данными. Глобальный `POST \u002Fv1\u002Fbatch` и `POST \u002Fv1\u002F{entity}\u002Fsearch` при этом фильтровали корректно.\n\n**Стало**\n\nПер-сущностный batch пропускает `filter` через тот же транслятор, что `search` и глобальный `\u002Fv1\u002Fbatch`. Псевдонимы полей и операторы (`$gt`, `$gte`, `$lt`, `$lte`, `$ne`, `$contains`, `$in`, `$nin`, префиксные `>=`, `\u003C=`, `!` и т.д.) применяются. Некорректный `filter` (неизвестное поле у сущности с полной схемой, неподдерживаемый оператор, префиксы `@` \u002F `!@`, логические токены `$or` \u002F `$and`) теперь возвращает `400` с указанием индекса вызова, а не молча всю выборку — так же, как на одиночных эндпоинтах.\n\n### FIX-0714-33: авто-пагинация больше не дублирует записи на границах страниц\n\n**Было**\n\nДля методов Битрикс24 без поддержки сортировки (например список объектов хранилища) запись на границе страниц могла сместиться между запросами соседних страниц и попасть в обе — при `limit > 50` в выдаче появлялся дубль, занимавший слот, и клиент обрабатывал одну и ту же запись дважды.\n\n**Стало**\n\nПосле склейки всех страниц выдача дедуплицируется по `id` (сохраняется первое вхождение). Для методов со стабильной сортировкой ничего не меняется (дублей нет — операция вхолостую).\n\n### FIX-0714-34: список полей шаблона реквизитов больше не пустеет; создание не возвращает чужую запись\n\n**Было**\n\n`GET \u002Fv1\u002Frequisite-presets\u002F:presetId\u002Ffields` мог вернуть `200` с пустым `data: []`, хотя в шаблоне есть поля: метод Битрикс24 отдаёт `result` то массивом `[{…}]`, то объектом-картой `{\"0\":{…},\"1\":{…}}`, а обработчик принимал только массив. При создании поля (`POST …\u002Ffields`) ответ мог содержать ЧУЖУЮ существующую запись — эхо созданной строки читалось по id из ответа `add`, а на некоторых порталах чтение по этому id возвращало другое поле.\n\n**Стало**\n\nСписок нормализует обе формы ответа Битрикс24 (массив и объект-карту) — поля больше не теряются. Эхо созданной записи возвращается только если её `fieldName` совпадает с тем, что создавали; при любом расхождении ответ содержит `{ id }` (сам объект не подставляется), чтобы клиент не получил постороннюю строку.\n\n**Влияние на интеграторов**\n\nИдентификатор поля шаблона (`id`) — это позиционный идентификатор Битрикс24: он может меняться после операций записи в шаблоне и на части порталов не является устойчивым ключом. Не кэшируйте `id` между изменениями шаблона — перечитывайте список полей перед `get`\u002F`update`\u002F`delete` по конкретному полю.\n\n### FIX-0714-35: привязка плейсмента для ранее созданных приложений больше не отклоняется по обработчику\n\n**Было**\n\n[POST \u002Fv1\u002Fplacements\u002Fbind](\u002Fdocs\u002Fkeys-auth) мог вернуть `400` с кодом `PLATFORM_HANDLER_UNRESOLVABLE` для приложения, созданного до перехода на единый платформенный обработчик (у такого приложения в качестве обработчика сохранился его собственный технический адрес). Привязка отклонялась даже тогда, когда платформенный обработчик `\u002Fv1\u002Fbitrix-handler` был доступен, — приложение нельзя было опубликовать заново через API.\n\n**Стало**\n\nПривязка проходит: обработчик плейсмента регистрируется на платформенный `\u002Fv1\u002Fbitrix-handler`, а в ответе появляются `handlerRewritten: true` и `requestedHandler` с исходным значением. Код `PLATFORM_HANDLER_UNRESOLVABLE` теперь возвращается только когда платформенный обработчик действительно недоступен. [POST \u002Fv1\u002Fplacements\u002Funbind](\u002Fdocs\u002Fkeys-auth) снимает такой плейсмент по тому же адресу.\n\n## 2026-07-13\n\n### FIX-0713-1: Платформенные scope ключа OAuth-приложения синхронизируются при создании и правке\n\n**Было**\n\nКлюч, выписанный вместе с OAuth-приложением через [POST \u002Fv1\u002Fapps](\u002Fdocs\u002Fapps), не получал платформенные scope (`vibe:infra`, `vibe:ai`, `vibe:search`, `vibe:storage`) — в отличие от ключа, созданного в кабинете. Из-за этого [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers) под таким ключом отвечал 403 `INFRA_SCOPE_REQUIRED`. Добавление `vibe:infra` в scope приложения через [PATCH \u002Fv1\u002Fapps\u002F:id](\u002Fdocs\u002Fapps) меняло только приложение, но не парный ключ — эффекта на доступ не было.\n\n**Стало**\n\nПарный ключ при создании через `POST \u002Fv1\u002Fapps` получает те же платформенные scope по умолчанию, что и ключ из кабинета. Изменение `vibe:*`-scope через `PATCH \u002Fv1\u002Fapps\u002F:id` (добавление И удаление) теперь синхронизируется в активные ключи приложения. Ключ в режиме «только чтение» (READONLY) при этом отклоняется с 403 `WRITE_BLOCKED_READONLY_KEY` на трёх write-операциях: создание сервера (`POST \u002Fv1\u002Finfra\u002Fservers`), изменение scope приложения (`PATCH \u002Fv1\u002Fapps\u002F:id`) и выписка READWRITE-ключа (`POST \u002Fv1\u002Fapps` с `mode: \"READWRITE\"`).\n\n**Влияние на интеграторов**\n\nПриложения, созданные через API, теперь могут управлять инфраструктурой без пересоздания. Ключ, у которого приложение уже объявляет `vibe:infra`, но сам ключ его лишён (старое расхождение), чинится одной правкой: снять `vibe:infra` из scope приложения и вернуть обратно через `PATCH \u002Fv1\u002Fapps\u002F:id` — вторая правка синхронизирует ключ; либо пересоздать приложение. READONLY-ключ по-прежнему может создавать READONLY-приложение (`mode: \"READONLY\"`).\n\n### FIX-0713-2: product-sections: неподдерживаемые фильтры возвращают 400, а не всю таблицу\n\n**Было**\n\nОператоры (`>`, `\u003C`, `!`, `%`, `$ne`, `$contains`, `$nin`) и поля вне точного равенства (`sort`, неизвестные) в фильтре [GET \u002Fv1\u002Fproduct-sections](\u002Fdocs\u002Fentities\u002Fproduct-sections\u002Flist) и [POST \u002Fv1\u002Fproduct-sections\u002Fsearch](\u002Fdocs\u002Fentities\u002Fproduct-sections\u002Fsearch) молча игнорировались — возвращался код 200 и весь список без фильтра.\n\n**Стало**\n\nТакие фильтры отклоняются с `400 UNSUPPORTED_FILTER`. Фильтруйте точным равенством или `$in` по `id`, `name`, `xmlId`, `code`, `catalogId`, `sectionId`. Сортировка (`order`\u002F`sort`) не изменилась.\n\n**Влияние на интеграторов**\n\nВызовы с операторами или `filter[sort]`, ранее возвращавшие 200 с неотфильтрованными данными, теперь возвращают `400` — переключитесь на точное равенство или `$in`.\n\n### FIX-0713-3: причина неудачного первого провижининга сервера теперь видна\n\n**Было**\n\nЕсли сервер не поднимался при первом создании (вытесняемый тариф вытеснен, нет свободной ёмкости), он молча оказывался в статусе `sleeping` без объяснения причины. Клиент, опрашивающий [GET \u002Fv1\u002Finfra\u002Fservers\u002F:id](\u002Fdocs\u002Finfra\u002Fservers\u002Fget), видел `sleeping` и не понимал, почему деплой заблокирован.\n\n**Стало**\n\nТакой сервер теперь переходит в `status: \"error\"` с заполненным `provisionError` (человекочитаемая причина) и новым полем `provisionErrorCode` — машиночитаемой категорией сбоя (`PREEMPTIBLE_EVICTION` \u002F `PROVISION_TIMEOUT` \u002F `NO_CAPACITY` \u002F `GENERIC`). Поле `provisionErrorCode` добавлено в ответы [GET \u002Fv1\u002Finfra\u002Fservers\u002F:id](\u002Fdocs\u002Finfra\u002Fservers\u002Fget) и [GET \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Flist) рядом с `provisionError` (аддитивно, `null`, если ошибок не было).\n\n**Влияние на интеграторов**\n\nНичего менять не нужно: `error` — уже существующий статус. Восстановление такого сервера — через `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fstart` или `\u002Frepair` (не `\u002Fwake`: на статусе `error` он вернёт `422`; поле `availableActions` в ответе подсказывает доступное действие).\n\n### NEW-0713-4: POST и PATCH \u002Fv1\u002Ftasks\u002F:taskId\u002Ftime принимают createdDate\n\nНеобязательное поле `createdDate` теперь передаётся в [POST \u002Fv1\u002Ftasks\u002F:taskId\u002Ftime](\u002Fdocs\u002Fentities\u002Ftasks\u002Ftime\u002Fcreate) и [PATCH \u002Fv1\u002Ftasks\u002F:taskId\u002Ftime\u002F:itemId](\u002Fdocs\u002Fentities\u002Ftasks\u002Ftime\u002Fupdate) — запись учёта времени ложится на указанную дату, а не на текущий момент (сценарий понедельничного добивания трека за прошлую неделю). Принимаются ISO 8601 со смещением, ISO без смещения и `YYYY-MM-DD` — значение уходит в `CREATED_DATE` без изменений. Если поле не передано, поведение прежнее — дата равна моменту создания.\n\n### FIX-0713-5: meta.hasMore перестаёт зависать в true при filter + offset\n\n**Было**\n\nПри пагинации списка с фильтром (например [GET \u002Fv1\u002Fdeals](\u002Fdocs\u002Fentities\u002Fdeals\u002Flist) с `filter` и `offset`) `meta.hasMore` оставался `true` на любом offset — даже далеко за пределами `meta.total`. Цикл `while (meta.hasMore) { offset += limit }` уходил в бесконечность.\n\n**Стало**\n\nКогда Bitrix24 игнорирует смещение за пределами отфильтрованного набора и отдаёт его целиком, `meta.hasMore` считается по окну запроса (`offset + limit \u003C meta.total`), а не выставляется в `true` безусловно. При `offset=0` ответ прежний; после того как окно перекрыло `total`, `hasMore` становится `false`. Затрагивает список и `POST \u002Fsearch` для всех сущностей.\n\n### FIX-0713-6: GET \u002Fv1\u002Flists и \u002Fv1\u002Flists\u002F:iblockId\u002Felements учитывают offset\n\n**Было**\n\n[GET \u002Fv1\u002Flists\u002F:iblockId\u002Felements](\u002Fdocs\u002Flists\u002Felements) и [GET \u002Fv1\u002Flists](\u002Fdocs\u002Flists\u002Flists) молча игнорировали `offset` — при `?limit=50&offset=50` возвращалась та же первая страница, и клиент, листающий через `offset`, никогда не доходил до строк 51 и дальше.\n\n**Стало**\n\n`offset` мапится в нативный для этих методов параметр `start`, так что пагинация через `offset` работает. Явный `start` сохраняет приоритет, если переданы оба.\n\n### NEW-0713-7: Флаг preserveEnv в деплое сохраняет .env при cleanDeploy\n\nТело `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` приняло необязательный булев `preserveEnv` (по умолчанию `false`). Когда `cleanDeploy: true` (очищает каталог приложения вместе с файлом `.env`), а `preserveEnv: true` — существующий `.env` считывается до очистки и восстанавливается после неё, если в этом же запросе не передан `env` (переданный `env` имеет приоритет). Без флага повторный деплой с `cleanDeploy` мог поднять приложение без переменных окружения — на порту по умолчанию.\n\n### NEW-0713-8: у неудачной сборки galaxy-приложения появилась подсказка `buildHint`\n\n**Было**\n\nПровал сборки galaxy-приложения возвращал только `GALAXY_APP_BUILD_FAILED`\n(и `GALAXY_APP_START_FAILED`) c «хвостом» лога в `buildLog` — разобрать причину\nможно было лишь вручную. `GET \u002Fv1\u002Finfra\u002Fservers\u002F:id` по ERROR-приложению отдавал\nкороткий `provisionError`, но без готовой рекомендации.\n\n**Стало**\n\nОтвет теперь несёт разобранную причину. В теле ошибки `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy`\n(502) при классифицируемом провале дополнительно приходят `error.category`\n(машинная категория, например `MODULE_NOT_FOUND`, `INSTALL_AUTH`, `RESOURCE`) и\n`error.buildHint` — локализованная строка-рекомендация с конкретным следующим шагом.\n`GET \u002Fv1\u002Finfra\u002Fservers\u002F:id` по ERROR-приложению добавляет то же поле `data.buildHint`.\nПоля аддитивные: если причина не распознана, они отсутствуют (`buildHint` — `null`),\nа `provisionError`\u002F`buildLog` продолжают приходить как раньше — старые запросы\nне меняются.\n\n### FIX-0713-9: одновременные одинаковые сохранения исходников больше не создают дубль-версию\n\n**Было**\n\nДва одновременных сохранения одинаковых байтов на один сервер (`POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources`, а также автосохранение при деплое) при редком стечении таймингов создавали **две** байт-идентичные версии вместо одной — дедупликация по содержимому была best-effort.\n\n**Стало**\n\nДедупликация детерминированная: одинаковые байты, отправленные одновременно, всегда сходятся на одну версию. На `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources` оба ответа возвращают один и тот же `versionId`, проигравший запрос получает `deduplicated: true`; автосохранение при деплое сходится на ту же единственную версию (формат ответа деплоя не менялся).\n\n## 2026-07-12\n\n### NEW-0712-1: Ответ при исчерпании AI-квоты подсказывает путь к пополнению\n\nОшибка `402 ai_quota_exhausted` с `reason: wallet_empty` теперь дополнительно возвращает поля `hint` и `topupUrl`. `hint` — короткая подсказка на английском: месячная AI-квота и баланс портала исчерпаны, и администратор портала может пополнить баланс, чтобы возобновить работу. `topupUrl` — ссылка, которую администратор открывает для пополнения. Поля аддитивны: прежние поля ответа (`reason`, `resetAt`) не изменились, а ветки `reason: breaker` и `reason: wallet_off` их не несут. Так агент-клиент может передать человеку путь к пополнению. Ошибку возвращают вызовы моделей, см. [POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions).\n\n### FIX-0712-2: `sleep-now` на сервере с давно прошедшим расчётным пробуждением теперь честно усыпляет\n\n**Было**\n\nДля сервера с включённым расписанием пробуждения вызов `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsleep-now` мог навсегда возвращать `{ data: { slept: false, reason: \"WAKE_IMMINENT\" } }`, если денормализованное «следующее пробуждение» осталось в далёком прошлом (сервер разбудили вне планировщика, и штамп не пересчитался). Сервер никогда не засыпал и держал тариф включённым круглосуточно.\n\n**Стало**\n\nШтамп старше динамического окна («поле для манёвра» = margin + типовой lead) считается протухшим, а не «неминуемым»: `sleep-now` честно усыпляет сервер и отвечает `{ success: true }`, после чего планировщик тихо перекатывает якорь к будущему окну без пробуждения. `WAKE_IMMINENT` остаётся штатным ответом только для действительно близкого пробуждения.\n\n## 2026-07-11\n\n### FIX-0711-1: галактики: ошибка GALAXY_HOST_UNREACHABLE стала действенной — подсказка hint и точная причина в provisionError\n\n**Было**\n\nКогда хост галактики был временно недоступен, [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fdeploy) отвечал голым 502 с кодом `GALAXY_HOST_UNREACHABLE`, а слот, созданный одним вызовом [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) с `source`, переходил в статус ошибки с текстом «Deploy failed unexpectedly — please retry; details are in the server logs». Ни причина, ни путь восстановления не сообщались — клиенты удаляли слот и создавали новый, что не помогает: новый слот попадает на тот же хост.\n\n**Стало**\n\nОтвет 502 `GALAXY_HOST_UNREACHABLE` — при деплое, exec-команде и удалении ([DELETE \u002Fv1\u002Finfra\u002Fservers\u002F:id](\u002Fdocs\u002Finfra\u002Fservers\u002Fdelete)) — несёт структурную подсказку `error.hint`: состояние временное, слот и его данные целы, нужно повторить тот же запрос через 1–2 минуты, удалять слот не нужно. На пути «создание с `source`» поле `provisionError` теперь содержит настоящую причину («Galaxy host … became unreachable during build …») и тот же совет повторить деплой в существующий слот. Дополнительно: когда несколько серверов работают под одним OAuth-приложением, сохранённая версия исходников больше не теряется из-за конфликта нумерации версий — ни при автосохранении на деплое, ни при явном сохранении через [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources](\u002Fdocs\u002Fsource-storage).\n\n**Влияние на интеграторов**\n\nИзменение аддитивное: коды и статусы ответов не менялись, добавилось поле `error.hint` и уточнился текст `provisionError`. Обновлять клиентов не нужно. AI-агентам стоит читать `hint.recovery` — там прямо сказано, что делать.\n\n### NEW-0711-2: отдельный код ошибки, когда на портале не установлен модуль Vibecode Connector\n\nВыписка ключа приложения через модуль-коннектор ([POST \u002Fv1\u002Fapps](\u002Fdocs\u002Fapps)) теперь при отсутствии на портале модуля `vibecodeconnector` возвращает `409` с кодом `CONNECTOR_MODULE_NOT_INSTALLED` и понятным сообщением «установите модуль», вместо прежнего непрозрачного `502 CONNECTOR_APP_INSTALL_FAILED`. Это законное, постоянное состояние (особенно для коробки), а не временный сбой — повторять запрос бессмысленно, нужно установить модуль на портале. Остальные коды выписки не изменились.\n\n### FIX-0711-3: pacing в GET \u002Fv1\u002Fai\u002Fquota теперь может заполняться платформой по умолчанию (поэтапная раскатка)\n\nПлатформа теперь умеет включать пейсинг (равномерное расходование AI-квоты) по умолчанию для портала — без действий администратора. Раскатка поэтапная (пилотные порталы → все порталы): пока портал не попал под платформенный default-on, поле `data.pacing` в ответе [GET \u002Fv1\u002Fai\u002Fquota](\u002Fdocs\u002Fai\u002Fconsumption\u002Fquota) остаётся `null`, как и раньше. Когда портал под default-on: `mode: \"ignore\"` — информационный режим, `active: false` — лимит окна не отклоняет запросы (отказ `429 ai_pacing_limited` невозможен). Форма ответа не изменилась; интеграторам, уже обрабатывающим `data.pacing` как опциональное поле, ничего менять не нужно.\n\n## 2026-07-10\n\n### NEW-0710-1: управление окнами пробуждения по расписанию (wake-schedules)\n\nНовый CRUD-контракт на серверах Black Hole: `GET`\u002F`POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules`, `PATCH`\u002F`DELETE \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules\u002F:scheduleId`. Позволяет объявить одно или несколько повторяющихся окон пробуждения (`cronExpr` + обязательная IANA-таймзона `timezone`, необязательные `label`\u002F`lead`\u002F`enabled`) — платформа будит спящий сервер к нужному моменту, дальше запуск задачи делает собственный cron внутри VM.\n\nРаскатывается постепенно и пока не работает на всех порталах — до включения на конкретном портале запрос отвечает `403` с кодом `WAKE_SCHEDULE_DISABLED`. Доступно только для серверов в режиме BLACKHOLE (иначе `400 BLACKHOLE_ONLY`) и пока не поддержано для галактик (`400 GALAXY_NOT_SUPPORTED` на хосте и вложенном приложении — появится позже). Минимальный интервал между срабатываниями и лимит окон на сервер (50) заданы платформой; нарушение отвечает `400 CADENCE_TOO_LOW` и `403 WAKE_SCHEDULE_LIMIT` соответственно. Ответ создания\u002Fобновления дополнительно несёт поле `tzWarning` — предупреждение о том, что таймзона в VM могла разойтись с таймзоной окна, если сервер не переразвёртывался. **`PATCH` заменяет окно целиком (PUT-семантика): не переданные необязательные поля сбрасываются к значениям по умолчанию — `enabled`→`true`, `label`\u002F`lead`→пусто.** Передавайте полный объект окна при обновлении.\n\nЗатронутые эндпоинты: `GET|POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules`, `PATCH|DELETE \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules\u002F:scheduleId`.\n\n### NEW-0710-2: машиночитаемый код tz-предупреждения в ответах wake-schedules (`tzWarningCode`)\n\nОтвет создания\u002Fобновления окна пробуждения (`POST`\u002F`PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules[\u002F:scheduleId]`) теперь дополнительно несёт поле `tzWarningCode` рядом с существующим текстовым `tzWarning` — `\"SINGLE_ZONE\"` \u002F `\"MULTI_ZONE\"` \u002F `null` (когда после мутации у сервера не осталось включённых окон). Значение — машиночитаемый эквивалент того же предупреждения, чтобы клиент мог локализовать текст сам вместо отображения английской строки `tzWarning` как есть. Поле аддитивное, `tzWarning` не меняется и остаётся для обратной совместимости.\n\nЗатронутые эндпоинты: `POST|PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules[\u002F:scheduleId]`.\n\n### FIX-0710-3: wake-schedules: расписание запрещено на серверах «Всегда онлайн»\n\n`POST`\u002F`PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules` теперь отклоняют создание или обновление окна пробуждения на сервере в режиме «Всегда онлайн» (24\u002F7) — ответ `400` с кодом `ALWAYS_ON_CONFLICT`. Такой сервер работает на невытесняемом тарифе и не уходит в авто-сон, поэтому расписание пробуждения тихо нарушило бы оплаченную гарантию постоянной доступности. Обычные засыпающие серверы Black Hole и вытесняемые агенты\u002Fботы не затронуты. Отключите «Всегда онлайн», чтобы объявлять окна пробуждения.\n\nЗатронутые эндпоинты: `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules`, `PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules\u002F:scheduleId`.\n\n### FIX-0710-4: wake-schedules теперь можно объявлять на вложенных приложениях galaxy\n\n**Было**\n\n`POST`\u002F`PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules` отвечал `400 GALAXY_NOT_SUPPORTED` для любого сервера семейства galaxy — как для самого хоста, так и для вложенных приложений (`kind=GALAXY_APP`).\n\n**Стало**\n\nВложенные приложения galaxy (`kind=GALAXY_APP`) теперь принимаются — окно пробуждения можно объявить на конкретном приложении, и платформа разбудит его (и при необходимости — хост) к нужному моменту. Сам хост galaxy по-прежнему отвечает `400 GALAXY_NOT_SUPPORTED`: расписание объявляется на приложениях, а не на хосте.\n\nЗатронутые эндпоинты: `POST|GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules`, `PATCH|DELETE \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake-schedules\u002F:scheduleId`.\n\n### FIX-0710-5: sleep-now откладывает засыпание, когда пробуждение по расписанию близко\n\n**Было**\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsleep-now](\u002Fdocs\u002Finfra\u002Flifecycle\u002Fsleep-now) всегда усыплял сервер немедленно, даже если ближайшее пробуждение по расписанию наступало через минуту — сервер сразу же просыпался обратно.\n\n**Стало**\n\nЕсли у сервера есть включённое расписание пробуждения и ближайшее пробуждение наступит в пределах защитного окна, вызов не усыпляет сервер и отвечает `200` с телом `{ \"success\": true, \"data\": { \"slept\": false, \"reason\": \"WAKE_IMMINENT\" } }`. В остальных случаях сервер усыпляется, а планировщик разбудит его в следующее окно.\n\n**Влияние на интеграторов**\n\nБлок `data` с полем `slept: false` приходит только при отказе усыпить — проверяйте его наличие, если полагаетесь на то, что после вызова сервер обязательно засыпает. При успешном усыплении ответ состоит из одного `success: true`. Старые вызовы серверов без расписания работают без изменений.\n\n### FIX-0710-6: не-стриминговые chat\u002Fcompletions и embeddings больше не обрываются на 360 секундах\n\n**Было**\n\nНе-стриминговый (`stream:false`) запрос к `POST \u002Fv1\u002Fchat\u002Fcompletions` (и `POST \u002Fv1\u002Fembeddings`), генерация которого длилась дольше ~2 минут, стабильно обрывался на ~360 секундах с `{\"code\":\"ai_provider_unavailable\",\"message\":\"This operation was aborted\"}` — независимо от таймаута клиента. При этом на обречённую генерацию тратилось тройное количество вычислений.\n\n**Стало**\n\nТакой запрос обрабатывается в рамках единого бюджета ~850 секунд (одна попытка на весь бюджет, без утроения нагрузки). Если генерация всё же не укладывается в бюджет, возвращается осмысленный `503` с кодом `ai_provider_timeout`, локализованным `userMessage` и подсказкой (уменьшить объём запроса либо использовать потоковый режим `stream:true`) — без заголовка `Retry-After` (таймаут не транзиентный). Обрыв соединения клиентом теперь немедленно отменяет генерацию на стороне провайдера. Стриминговый режим (`stream:true`) этим лимитом не затрагивался.\n\n### FIX-0710-7: requisite-presets\u002F:presetId\u002Ffields и requisite-links: сортировка, фильтрация и валидация параметров списка\n\n**Было**\n\n[GET \u002Fv1\u002Frequisite-presets\u002F:presetId\u002Ffields](\u002Fdocs\u002Fentities\u002Frequisite-presets) игнорировал `sort`\u002F`order` и любые `filter[...]` — всегда возвращал полный список в порядке Битрикс24. [GET \u002Fv1\u002Frequisite-links](\u002Fdocs\u002Fentities\u002Frequisite-links) и [POST \u002Fv1\u002Frequisite-links\u002Fsearch](\u002Fdocs\u002Fentities\u002Frequisite-links) принимали только простое равенство, а операторы (`$gte`, `>` и т.п.), `sort`\u002F`order` и неизвестные поля молча пропускались — в результате приходила вся таблица.\n\n**Стало**\n\nОба списка честно применяют `sort`\u002F`order` (в том числе форму `order[поле]=asc|desc`) и `filter`. У requisite-links работают операторы сравнения (`$gt`\u002F`$gte`\u002F`$lt`\u002F`$lte`, `$in`\u002F`$nin`, префиксы `>=`\u002F`>`). Неизвестное поле фильтра или сортировки теперь возвращает `400` (`UNKNOWN_FILTER_FIELD` \u002F `UNKNOWN_SORT_FIELD`), логический оператор верхнего уровня (`$or`\u002F`$and`) — `400 INVALID_FILTER_OPERATOR`, а фильтр по `entityId` без `entityTypeId` — `400 MISSING_ENTITY_TYPE_ID` вместо сырого «Access denied».\n\n**Влияние на интеграторов**\n\nЗапросы по документированным полям продолжают работать и теперь действительно сортируются\u002Fфильтруются. Если раньше вы полагались на молчаливое игнорирование неизвестного параметра, теперь он вернёт `400` — уберите опечатку или используйте поле из ответа.\n\n### FIX-0710-8: Сортировка банковских реквизитов по id учитывает направление\n\n**Было**\n\nЗапрос списка банковских реквизитов ([GET \u002Fv1\u002Fbank-details](\u002Fdocs\u002Fentities\u002Fbank-details\u002Flist)) с сортировкой по `id` по убыванию (`?sort=-id`) возвращал записи по возрастанию — направление сортировки молча игнорировалось.\n\n**Стало**\n\n`?sort=-id` (и `?sort=id`) сортирует по идентификатору в запрошенном направлении.\n\n**Влияние на интеграторов**\n\nИзменений в коде не требуется — запросы, полагавшиеся на сортировку по `id`, теперь возвращают ожидаемый порядок.\n\n### NEW-0710-9: GET \u002Fv1\u002Fquotes\u002Ffields — объявлены ~26 полей предложения с человеческими названиями\n\nСхема сущности «предложения» (quotes) пополнена ~26 полями, которые Битрикс24 возвращал, но которые не были объявлены: `quoteNumber`, `updatedBy`, `lastActivityBy`, `lastActivityTime`, `content`, `terms`, `leadId`, `storageTypeId`, `storageElementIds`, `personTypeId`, `webformId`, `lastCommunicationTime`, `contactIds`, `contacts`, `locationId`, `taxValue`, `actualDate`, `mycompanyId`, `utmSource`\u002F`utmMedium`\u002F`utmCampaign`\u002F`utmContent`\u002F`utmTerm`, `lastCommunicationCallTime`\u002F`lastCommunicationEmailTime`\u002F`lastCommunicationImolTime`\u002F`lastCommunicationWebformTime`. Теперь они видны в [GET \u002Fv1\u002Fquotes\u002Ffields](\u002Fdocs\u002Fentities\u002Fquotes\u002Ffields) с читаемыми названиями (вместо служебных `STORAGE_TYPE_ID`\u002F`UTM_SOURCE`), а их значения приводятся к типам (числа, даты в ISO) в ответах list\u002Fget. Названиями снабжены также ранее необъявленные `stageId`, `opened`, `closed`.\n\n### FIX-0710-10: PATCH без единого записываемого поля отклоняется явной ошибкой\n\n**Было**\n\n`PATCH \u002Fv1\u002F{entity}\u002F:id` с пустым телом — или с телом, в котором нет ни одного распознанного записываемого поля (например из-за опечатки в имени поля) — доходил до Битрикс24, который молча игнорировал такой запрос и отвечал успехом. Обёртка возвращала `200` с неизменённым объектом, и клиент считал, что правка применилась, хотя на деле ничего не менялось — риск тихой рассинхронизации, особенно у AI-агентов. Это касалось всех трёх поверхностей обновления: одиночного `PATCH \u002Fv1\u002F{entity}\u002F:id`, пакетного `POST \u002Fv1\u002F{entity}\u002Fbatch` и общего `POST \u002Fv1\u002Fbatch`.\n\n**Стало**\n\nОбновление с пустым телом возвращает `400 EMPTY_UPDATE_BODY` (в пакетных вызовах — ошибку элемента) на всех трёх поверхностях, до обращения к Битрикс24. Для `\u002Fv1\u002Fcatalog-products\u002F:id` добавлена строгая проверка: `PATCH`, в котором нет ни одного распознанного записываемого поля, возвращает `400 NO_RECOGNIZED_UPDATE_FIELDS`. Осмысленное обновление всегда несёт хотя бы одно поле — передайте распознаваемое поле (пользовательские свойства `propertyNNN` и поля `UF_*` тоже принимаются). Сущности, у которых уже была проверка полей, ведут себя как прежде.\n\nДополнительно у товаров каталога в `GET \u002Fv1\u002Fcatalog-products\u002Ffields` появились и стали доступны для явного `select`, фильтра и сортировки поля из контракта обновления товара — `code`, `xmlId`, `sort`, `vatId`, `height`, `length`, `width`, `previewText`, `detailText` и другие (раньше фильтр по ним отвечал `400 UNKNOWN_FILTER_FIELD`). Надёжно фильтровать и сортировать стоит по индексируемым полям (`code`, `xmlId`, `sort`, `vatId`, размеры); по полнотекстовым (`previewText`, `detailText`) Битрикс24 фильтр может игнорировать. Ответы списков без явного `select` не изменились.\n\n### FIX-0710-11: leads, companies, quotes — поля дат в \u002Ffields теперь createdTime и updatedTime\n\n**Было**\n\n`GET \u002Fv1\u002Fleads\u002Ffields`, `GET \u002Fv1\u002Fcompanies\u002Ffields` и `GET \u002Fv1\u002Fquotes\u002Ffields` объявляли поля `createdAt` и `updatedAt`, хотя в ответах list\u002Fget\u002Fsearch Bitrix24 всегда возвращал `createdTime` и `updatedTime` — прочитать значение по имени `createdAt`\u002F`updatedAt` было нельзя. Фильтр и сортировка при этом принимали имена `createdAt`\u002F`updatedAt`.\n\n**Стало**\n\n`\u002Ffields` этих сущностей объявляют реальные ключи `createdTime` и `updatedTime` — как у contacts, invoices и items. Ключи в теле ответов те же (`createdTime`\u002F`updatedTime` приходили всегда), но теперь значение нормализуется к ISO-8601 в UTC (`2026-04-15T07:00:00.000Z`) — раньше приходило в исходном формате Bitrix24 со смещением портала (`2026-04-15T10:00:00+03:00`). Момент времени тот же, меняется только представление.\n\n**Влияние на интеграторов**\n\nЧитайте даты из `createdTime` и `updatedTime` (ключи не менялись). Если вы сравниваете строку даты побайтово или кэшируете по ней — учтите переход `+03:00` → `Z` (тот же момент времени). В фильтре и сортировке используйте `createdTime`\u002F`updatedTime`; прежние `createdAt`\u002F`updatedAt` теперь возвращают `400 UNKNOWN_FILTER_FIELD` (фильтр) и `400 UNKNOWN_SORT_FIELD` (сортировка). На запись `createdAt`\u002F`updatedAt` больше не отклоняются как readonly — они игнорируются как неизвестные поля (как у contacts\u002Finvoices\u002Fitems); задать дату создания\u002Fизменения по-прежнему нельзя.\n\n### FIX-0710-12: сон-настройки: флип в «Всегда онлайн» теперь запрещён при активных окнах пробуждения\n\n`PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsleep` теперь отклоняет установку `sleepAfterMinutes: null` («Всегда онлайн», 24\u002F7) на сервере, у которого есть включённые окна расписания пробуждения — ответ `400` с кодом `ALWAYS_ON_CONFLICT`. Это обратное направление уже существующего гейта: раньше запрещалось создавать окно пробуждения на сервере «Всегда онлайн», теперь симметрично запрещён и обратный переход — иначе сервер продолжал бы засыпать по расписанию, тихо нарушая оплаченную гарантию постоянной доступности. Удалите или отключите окна пробуждения, либо оставьте таймаут сна вместо «Никогда», чтобы включить «Всегда онлайн».\n\nЗатронутые эндпоинты: `PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsleep`.\n\n### NEW-0710-13: placement.bind на коробочном Битрикс24 отдаёт понятный SESSION_REQUIRES_ADMIN для не-администратора\n\nНа коробочном (self-hosted) Битрикс24 привязка плейсмента через путь developer-key требует, чтобы пользователь ключа был администратором аккаунта. Раньше запрос не-администратора возвращал глухой `502 BITRIX_UNAVAILABLE`.\n\nТеперь [POST \u002Fv1\u002Fplacements\u002Fbind](\u002Fdocs\u002Fkeys-auth) распознаёт отказ доступа со стороны Битрикс24 и возвращает `403 SESSION_REQUIRES_ADMIN` с подсказкой: выполните привязку под учётной записью администратора аккаунта либо попросите администратора выдать эти права. Требование заранее видно в [GET \u002Fv1\u002Fme](\u002Fdocs\u002Fkeys-auth) — блок `placements.bindPrerequisite` для коробочных аккаунтов теперь включает код `SESSION_REQUIRES_ADMIN`.\n\n### FIX-0710-14: capabilities в GET \u002Fv1\u002Fme отражают режим только для чтения (READONLY)\n\n**Было**\n\nДля ключа в режиме READONLY [GET \u002Fv1\u002Fme](\u002Fdocs\u002Fkeys-auth) отдавал `capabilities.managedBots.create`, `agents.create`, `servers.create` и `apps.*` со значением `available: true`, хотя любая операция записи блокируется с `403 WRITE_BLOCKED_READONLY_KEY`. Агент видел «можно создать» и упирался в отказ.\n\n**Стало**\n\nДля READONLY-ключа эти write-способности возвращаются как `available: false` с `reason: \"WRITE_BLOCKED_READONLY_KEY\"` и подсказкой переключить ключ в режим чтение+запись. Способности только для чтения и AI Router не меняются. Для ключей в режиме READWRITE ответ прежний.\n\n### FIX-0710-15: автор обращения без скоупа vibe:feedback снова может отвечать на своё обращение\n\n**Было**\n\n[POST \u002Fv1\u002Ffeedback\u002F:id\u002Fcomments](\u002Fdocs\u002Fkeys-auth) отклонял автора обращения с `403 FEEDBACK_SCOPE_REQUIRED`, если у ключа не было скоупа `vibe:feedback` — хотя ветка автора была задокументирована. Автор не мог ответить на своё же обращение в статусе `AWAITING_USER`, и обращение зависало.\n\n**Стало**\n\nПроверка на автора выполняется до скоуп-гейта: автор, отвечающий тем же ключом, которым создал обращение, попадает в авторскую ветку (`authorType=USER`, правило мяча `AWAITING_USER → NEEDS_REVIEW`) даже без скоупа `vibe:feedback`. Ключ, не являющийся автором и не имеющий скоупа, по-прежнему получает `403 FEEDBACK_SCOPE_REQUIRED`.\n\n### NEW-0710-16: Пейсинг AI-квоты: поле pacing в ответе и код ошибки 429 ai_pacing_limited\n\nОтвет [GET \u002Fv1\u002Fai\u002Fquota](\u002Fdocs\u002Fai\u002Fconsumption\u002Fquota) дополнен полем `pacing` — состоянием равномерного расходования месячной AI-квоты (сглаживание пиков через суточный и недельный лимит поверх общего месячного лимита; включается администратором портала в кабинете `\u002Fai`). Значение `null`, если пейсинг выключен на платформе или не настроен для портала; иначе объект `{ mode, active, day, week }`: `mode` — режим реакции на превышение (`wallet`\u002F`block`\u002F`ignore`), `active` — сработает ли превышение прямо сейчас в отказ (`false` в режиме наблюдения и всегда `false` при режиме `ignore` — окна считаются и только информируют, `429` не отдаётся), `day` и `week` — по `{ pctUsed, resetAt }` в процентах от собственного лимита окна. Ответ по-прежнему кэшируется на 30 секунд, поэтому состояние пейсинга может отставать на этот срок.\n\nПри срабатывании суточного или недельного лимита запросы [POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions), [POST \u002Fv1\u002Fembeddings](\u002Fdocs\u002Fai\u002Fembeddings) и [POST \u002Fv1\u002Faudio\u002Ftranscriptions](\u002Fdocs\u002Fai\u002Faudio\u002Ftranscriptions) могут вернуть `429` с телом `{ success: false, error: { code: \"ai_pacing_limited\", type: \"rate_limit_exceeded\", message, reason, overageDenied, resetAt, retryAfter } }` и заголовком `Retry-After`. `reason` — какое окно пробито (`day_window` или `week_window`); `overageDenied` — причина отказа в платном превышении лимита (`wallet_empty`, `breaker`, `wallet_off`), либо `null` в режиме жёсткой блокировки. Повторять вызов раньше `Retry-After`\u002F`resetAt` не имеет смысла — квота не станет доступнее за это время. По умолчанию пейсинг выключен — включается платформой.\n\n### FIX-0710-17: GET \u002Fv1\u002Fpages, \u002Fv1\u002Fsites и POST \u002Fv1\u002F{pages,sites}\u002Fsearch теперь учитывают offset\n\n**Было**\n\nЗапрос списка страниц или сайтов со смещением (`GET \u002Fv1\u002Fpages?offset=50`, `POST \u002Fv1\u002Fpages\u002Fsearch` с `offset`) возвращал ошибку `422 BITRIX_ERROR \"Unknown parameter: start\"`. Первая страница (без `offset`) работала.\n\n**Стало**\n\n`offset` для `pages` и `sites` обрабатывается корректно: возвращается запрошенное окно `[offset, offset+limit)`, а `meta.total` и `meta.hasMore` считаются по числу строк. Менять клиентский код не нужно — вызовы без `offset` работают как раньше.\n\n### NEW-0710-18: \u002Ffields сущностей документов, каталогов, цен, телефонных линий, позиций корзины и лидов дополнены метаданными\n\n`GET \u002F:entity\u002Ffields` нескольких сущностей теперь несёт более полную метаданную схемы. У документов ([GET \u002Fv1\u002Fdocuments\u002Ffields](\u002Fdocs\u002Fentities\u002Fdocuments\u002Ffields)), каталогов ([GET \u002Fv1\u002Fcatalogs\u002Ffields](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Ffields)), цен каталога ([GET \u002Fv1\u002Fcatalog-prices\u002Ffields](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Ffields)) и телефонных линий по каждому полю добавлены человекочитаемые `label` и `description` (на `.tech` — по-русски, на `.com` — по-английски).\n\nУ позиций корзины ([GET \u002Fv1\u002Fbasket-items\u002Ffields](\u002Fdocs\u002Fentities\u002Fbasket-items\u002Ffields)) поля `weight`, `vatRate`, `measureCode`, `measureName`, `dimensions`, `productXmlId`, `catalogXmlId` помечены флагом `nullable` — они могут прийти пустыми. У лидов ([GET \u002Fv1\u002Fleads\u002Ffields](\u002Fdocs\u002Fentities\u002Fleads\u002Ffields)) тем же флагом помечены `secondName`, `sourceDescription`, `comments`, а также объявлены ранее неописанные поля `originatorId`, `dateClosed`, `lastCommunicationTime` и метки `utmSource`\u002F`utmMedium`\u002F`utmCampaign`\u002F`utmContent`\u002F`utmTerm` — теперь по ним работают фильтр и сортировка, а `dateClosed` нормализуется к ISO-8601.\n\nУ сайтов лендингов объявлены измерения для группировки, поэтому [POST \u002Fv1\u002Fsites\u002Faggregate](\u002Fdocs\u002Fentities\u002Fsites\u002Faggregate) с `groupBy` (`type`, `active`, `deleted`, `lang`, `tplId`, `domainId`, `createdById`, `modifiedById`) больше не отвечает `Available: .`. У документов агрегация отключена (все числовые поля — идентификаторы): [POST \u002Fv1\u002Fdocuments\u002Faggregate](\u002Fdocs\u002Fentities\u002Fdocuments\u002Flist) возвращает `404`.\n\nПрежние вызовы работают без изменений — это дополнительные метаданные полей.\n\n### NEW-0710-19: Идемпотентное создание сервера — заголовок Idempotency-Key\n\n[POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) теперь принимает необязательный заголовок `Idempotency-Key` для создания отдельного сервера (standalone). Повторный запрос с тем же ключом — например, после потерянного ответа или разрыва сети — не создаёт второй сервер: он возвращает тот же самый сервер, что и первый запрос, со статусом 201 и заголовком ответа `Idempotent-Replayed: true`. Ключ — строка 1–255 символов из набора `[A-Za-z0-9_.:-]`; область действия — ваш API-ключ.\n\nПри повторе одноразовые SSH-учётные данные (`ssh.privateKey` \u002F `ssh.password`) НЕ выдаются повторно — в теле ответа они `null` и добавлено поле `note` с пояснением. Сохраните учётные данные из ответа первого создания.\n\nНовые коды ошибок: `400 INVALID_IDEMPOTENCY_KEY` (ключ не проходит валидацию), `400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION` (ключ вместе с `graduateFrom` для выделенного сервера не поддерживается), `409 IDEMPOTENCY_KEY_ALREADY_USED` (ключ уже использован для сервера, который затем был удалён), `409 IDEMPOTENCY_CONCURRENT_RETRY` (параллельный запрос с тем же ключом ещё выполняется — повторите чуть позже).\n\nЗаголовок учитывается только для standalone-серверов. На порталах с размещением в галактике корректный ключ игнорируется без ошибки, и защита от повторного создания на этот путь не распространяется.\n\nДополнительно: ответ создания теперь возвращает каноническое имя сервера (с суффиксом при разрешении конфликта имён), а не имя из запроса — при совпадении имён это ранее расходилось.\n\n### NEW-0710-20: локализованное сообщение при отказе переключения в режим OPEN\n\nОтветы [PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fmode](\u002Fdocs\u002Finfra\u002Faccess\u002Fmode) с кодами `OPEN_MODE_DISABLED` (режим OPEN выключен на уровне платформы) и `OPEN_MODE_NOT_ALLOWED` (режим OPEN запрещён политикой портала) теперь дополнительно несут поле `error.userMessage` — локализованную человекочитаемую формулировку с подсказкой использовать [Deploy API](\u002Fdocs\u002Finfra\u002Fdeploy) как штатную замену прямого SSH. Поле аддитивное: `error.message` (английская техническая строка), `error.code` и HTTP-статус не меняются. Совпадает по форме с уже существующим `error.userMessage` у отказа `OPEN_MODE_REQUIRES_COMMERCIAL`. Текст `userMessage` зависит от локали владельца ключа.\n\n### BC-0710-21: Поля конфигураций открытых линий приведены к camelCase и описаны в \u002Ffields\n\n> Поддержка старого формата до: 10.01.2027\n\n**Было**\n\n[GET \u002Fv1\u002Fopenline-configs](\u002Fdocs\u002Fopenlines\u002Fconfig\u002Flist), [GET \u002Fv1\u002Fopenline-configs\u002F{id}](\u002Fdocs\u002Fopenlines\u002Fconfig\u002Fget) и [POST \u002Fv1\u002Fopenline-configs\u002Fsearch](\u002Fdocs\u002Fopenlines\u002Fconfig\u002Fsearch) возвращали большинство полей конфигурации в «родном» для Bitrix24 виде — в верхнем регистре через подчёркивание (`CRM_CREATE`, `WELCOME_MESSAGE`, `QUEUE_TIME` и другие). Справка [GET \u002Fv1\u002Fopenline-configs\u002Ffields](\u002Fdocs\u002Fopenlines\u002Fconfig\u002Ffields) описывала только 6 полей, поэтому остальные не были видны для программного обнаружения.\n\n**Стало**\n\nВсе поля конфигурации приведены к единому camelCase (`crmCreate`, `welcomeMessage`, `queueTime` и так далее), а `\u002Ffields` описывает полный набор полей с человеческими названиями (`label`) и описаниями (`description`). Фильтрация и сортировка по новым camelCase-именам работают. При записи (`create`\u002F`update`) по-прежнему принимаются оба регистра — прежние вызовы с верхним регистром в теле не ломаются.\n\n**Что делать интеграторам**\n\nЧитать поля ответа по camelCase-именам: `config.crmCreate` вместо `config.CRM_CREATE`. Соответствие имён — прямая транслитерация из верхнего регистра в camelCase (`WELCOME_BOT_ID` → `welcomeBotId`, `WORKTIME_TO` → `workTimeTo`, `LINE_NAME` → `name`). Полный перечень новых имён — в справке `\u002Ffields`.\n\n### NEW-0710-22: Добавлен GET \u002Fv1\u002Fwarehouses\u002Ffields — схема полей склада\n\nПоявился эндпоинт [GET \u002Fv1\u002Fwarehouses\u002Ffields](\u002Fdocs\u002Fentities\u002Fwarehouses\u002Flist), возвращающий схему 19 полей склада: для каждого поля — тип (`type`), признак «только для чтения» (`readonly`), человеческое название (`label`) и описание (`description`). Склады — кастомный роут (без сущностной схемы), поэтому раньше у них не было справки полей, которая есть у автогенерируемых сущностей. Ответ — `{ success: true, data: { fields: { … } } }`. Требуется скоуп `catalog`.\n\n### FIX-0710-23: публикация приложения восстанавливается при рассинхроне плейсмента\n\n**Было**\n\nПри публикации ([POST \u002Fv1\u002Fapps\u002F:id\u002Fpublish](\u002Fdocs\u002Fapps)) или обновлении плейсментов ([PATCH \u002Fv1\u002Fapps\u002F:id](\u002Fdocs\u002Fapps)), если плейсмент был зарегистрирован на стороне Bitrix24, но отсутствовал в приложении (дрейф после снятия с публикации), привязка падала с ошибкой «Handler already binded» и плейсмент оставался несинхронизированным.\n\n**Стало**\n\nПри такой ошибке платформа один раз снимает устаревшую привязку и повторяет её — плейсмент синхронизируется автоматически. Восстановление срабатывает только на подтверждённом конфликте, поэтому «живой» плейсмент никогда не снимается по ошибке; плейсменты, которым нужны непереносимые OPTIONS (чат-виджеты, фоновый обработчик), из авто-восстановления исключены и по-прежнему сообщают предупреждение.\n\n### FIX-0710-24: storage: sha256 объекта заполняется при прямой загрузке\n\n**Было**\n\nПоле `sha256` в ответе на загрузку объекта хранилища всегда было `null` для пользовательских объектов, хотя схема описывала его как «вычисляется при загрузке».\n\n**Стало**\n\nПри прямой загрузке ([POST \u002Fv1\u002Fstorage\u002Fobjects\u002Fupload](\u002Fdocs\u002Fstorage), файлы до 10 МБ) `sha256` теперь содержит SHA-256-хэш содержимого объекта. По нему можно проверять целостность и находить дубликаты (одинаковое содержимое — одинаковый хэш). Для presigned- и multipart-загрузок байты идут напрямую в хранилище мимо платформы, поэтому там `sha256` пока остаётся `null`.\n\n### FIX-0710-25: поле pacing.active в GET \u002Fv1\u002Fai\u002Fquota больше не сообщает об активном лимите при нулевой квоте\n\n**Было**\n\nПри включённом равномерном расходовании на портале с нулевой месячной квотой (жёсткая блокировка через переопределение `monthlyVibes = 0` или ещё не инициализированный расход) поле `data.pacing.active` возвращало `true` — хотя отказ `429 ai_pacing_limited` в этом состоянии структурно невозможен: запросы отклоняются месячным лимитом, а не оконным.\n\n**Стало**\n\n`data.pacing.active` возвращает `true` только когда превышение дневного или недельного окна действительно может привести к `429 ai_pacing_limited`. При нулевой квоте поле честно отдаёт `false`. Форма ответа не изменилась; клиентам, строившим backoff-логику по `active`, действий не требуется — сигнал стал точнее.\n\n## 2026-07-09\n\n### FIX-0709-1: транзиентная перегрузка БД теперь отдаёт 503 с Retry-After вместо 500\n\n**Было**\n\nВ редкой форме кратковременной перегрузки БД (исчерпание коннектов) часть запросов (включая `POST \u002Fv1\u002Finfra\u002Fservers`) возвращала `500`.\n\n**Стало**\n\nТакие запросы возвращают `503` с кодом `POOL_EXHAUSTED` и заголовком `Retry-After`. Ошибка транзиентная — повтори запрос с задержкой (backoff).\n\n**Влияние на интеграторов**\n\nКлиенты, повторяющие запросы при 5xx, теперь должны обрабатывать `503` и уважать `Retry-After`. `POST \u002Fv1\u002Finfra\u002Fservers` неидемпотентен — повтор может создать дубль сервера, поэтому повторяйте с backoff, а не немедленно.\n\n### FIX-0709-2: placements\u002Fbind честнее сообщает о необходимости подписки Маркетплейса\n\n**Было**\n\nПри привязке плейсмента через ключ приложения на портале без активной подписки «BitrixGPT + Маркетплейс» Bitrix24 отвечал отказом доступа, а `POST \u002Fv1\u002Fplacements\u002Fbind` возвращал непрозрачный `502 BITRIX_UNAVAILABLE` без указания причины. Подписка требуется на пути через ключ разработчика независимо от коммерческого тарифа, но `GET \u002Fv1\u002Fme` не сообщал об этом предусловии заранее.\n\n**Стало**\n\nОтказ по подписке теперь классифицируется: `POST \u002Fv1\u002Fplacements\u002Fbind` возвращает `403` с кодом `B24_MARKET_SUBSCRIPTION_REQUIRED` (или `B24_MARKET_TRIAL_USED`, если демо уже использован), понятным `userMessage` и ссылкой на оформление в `details.upgradeUrl`. У ключа приложения в `GET \u002Fv1\u002Fme` добавлен блок `placements.bindPrerequisite` — он заранее описывает предусловие Bitrix24 (путь через ключ разработчика требует активной подписки Маркетплейса, устаревший OAuth-путь — коммерческого тарифа) и перечисляет коды ошибок. Прочие отказы привязки (неизвестный clientId, устаревший embedding) по-прежнему возвращают `502 BITRIX_UNAVAILABLE`.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно, успешные вызовы не затронуты. Тем, кто обрабатывал `502 BITRIX_UNAVAILABLE` при привязке, стоит дополнительно ловить `403 B24_MARKET_SUBSCRIPTION_REQUIRED` \u002F `B24_MARKET_TRIAL_USED` и подсказывать пользователю оформить подписку на портале.\n\n### FIX-0709-3: bindPrerequisite в GET \u002Fv1\u002Fme учитывает регион портала\n\n**Было**\n\nБлок `placements.bindPrerequisite` у ключа приложения описывал предусловие привязки одинаково для всех порталов: `subscriptionRequired: true`, рецепт «попросите администратора активировать подписку Маркетплейса» и список кодов с `B24_MARKET_SUBSCRIPTION_REQUIRED` \u002F `B24_MARKET_TRIAL_USED`. На порталах с тарифной моделью доступа отказ привязки приходит с кодом `INT_TARIFF_REQUIRED`, а подписки там нет — предложенный рецепт был невыполним.\n\n**Стало**\n\nБлок зависит от региона портала. На порталах с подписочной моделью доступа он прежний. На порталах с тарифной моделью `subscriptionRequired` равен `false`, текст `note` описывает требование коммерческого тарифа Битрикс24, а `errorCodes` содержит `INT_TARIFF_REQUIRED` и `BITRIX_UNAVAILABLE` — только те коды, которые портал действительно может получить.\n\n**Влияние на интеграторов**\n\nУспешные вызовы не затронуты. Если вы читали `errorCodes` из `bindPrerequisite` как полный перечень, учтите, что теперь он сужен до достижимых на конкретном портале кодов. Сами коды и поведение `POST \u002Fv1\u002Fplacements\u002Fbind` не менялись.\n\n### BC-0709-4: Поля CRM, задач и лендингов приведены к реальному контракту Битрикс24\n\n> Поддержка старого формата до: 09.01.2027\n\n**Было**\n\n`GET \u002Fv1\u002Fdeal-categories` возвращал `isLocked` строкой `\"Y\"`\u002F`\"N\"`, а `GET \u002Fv1\u002Fpayments` — `paySystemIsCash` строкой `\"Y\"`\u002F`\"N\"`. `GET \u002Fv1\u002Fleads` в каждом ответе отдавал служебное поле `searchContent` (внутренний полнотекстовый индекс Битрикс24). Поля `paySystemXmlId`, `dateMarked`, `dateResponsibleId` у платежей приходили как есть, без нормализации даты.\n\n**Стало**\n\n`isLocked` (воронки) и `paySystemIsCash` (платежи) теперь имеют тип boolean (`true`\u002F`false`). `searchContent` в ответах `GET \u002Fv1\u002Fleads` больше не отдаётся. У платежей добавлены задекларированные поля `paySystemXmlId` (строка), `dateMarked` и `dateResponsibleId` (даты нормализованы в единый формат ISO с суффиксом `Z`). У задач добавлены `changedBy`\u002F`closedBy`\u002F`statusChangedBy` (только для чтения — попытка записать возвращает `400 READONLY_FIELD`). `GET \u002Fv1\u002Fcurrencies\u002Ffields` отдаёт человекочитаемые `label` для полей и поле `lang`; `GET \u002Fv1\u002Fdeal-categories\u002Ffields` — `label` для полей; `GET \u002Fv1\u002Fsites\u002Ffields` — флаг `nullable` у полей, которые могут прийти пустыми.\n\n**Что делать интеграторам**\n\nЧитать `isLocked` и `paySystemIsCash` как boolean, а не сравнивать со строкой `\"Y\"`. Если код опирался на поле `searchContent` у лидов — перестать: оно было служебным и не задокументированным.\n\n### FIX-0709-5: создание сущности с пустым телом отклоняется явной ошибкой\n\n**Было**\n\nСоздание сущности с пустым телом (или вовсе без тела, но с заголовком `Content-Type: application\u002Fjson`) доходило до Битрикс24 и молча создавало сущность со значениями по умолчанию — включая сделки, лиды, контакты, компании, счета и смарт-процессы. Повторный вызов или клиент без тела так плодил мусорные записи в CRM. Это касалось всех трёх поверхностей создания: одиночного `POST \u002Fv1\u002F{entity}`, пакетного `POST \u002Fv1\u002F{entity}\u002Fbatch` и общего `POST \u002Fv1\u002Fbatch`.\n\n**Стало**\n\nТакой запрос возвращает `400 EMPTY_CREATE_BODY` (в пакетных вызовах — ошибку элемента) до обращения к Битрикс24, на всех трёх поверхностях. Осмысленное создание всегда несёт хотя бы одно поле — передайте нужные поля в теле запроса. Сущности, у которых уже есть проверка обязательных полей, ведут себя как прежде.\n\n### FIX-0709-6: снятие блокировки сервера принимает пустое тело JSON\n\n**Было**\n\n[DELETE \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Flock](\u002Fdocs\u002Finfra) с заголовком `Content-Type: application\u002Fjson` и пустым телом возвращал `400` (пустое JSON-тело). Чтобы снять зависшую блокировку, приходилось слать явное `{}` — не зная этого, клиент упирался в тупик.\n\n**Стало**\n\nПустое тело при этом заголовке принимается как `{}`; запрос без тела отрабатывает штатно и снимает блокировку. Явное `{}` по-прежнему работает.\n\n### FIX-0709-7: события портала теперь будят спящее galaxy-приложение\n\n**Было**\n\nСобытие Битрикса, отправленное на подписку спящего galaxy-приложения, не будило его — доставка\nуходила в повторные попытки и после их исчерпания терялась.\n\n**Стало**\n\nПлатформа будит спящее galaxy-приложение при доставке события и доставляет его после подъёма —\nкак для обычного сервера.\n\n### NEW-0709-8: camelCase-ключи внутри communications при создании дела\n\n[POST \u002Fv1\u002Factivities](\u002Fdocs\u002Fentities\u002Factivities\u002Fcreate) теперь принимает вложенные ключи элементов `communications` в camelCase (`type`, `value`, `entityTypeId`, `entityId`) — единообразно с остальным API. Раньше вложенные ключи принимались только в ВЕРХНЕМ регистре Bitrix24 (`TYPE`, `VALUE`, `ENTITY_TYPE_ID`, `ENTITY_ID`), а camelCase-форма молча отбрасывалась — `communications: [{ \"type\": …, \"value\": … }]` возвращал `422` «COMMUNICATIONS is not defined or invalid», тогда как `[{ \"TYPE\": …, \"VALUE\": … }]` создавал дело. ВЕРХНИЙ регистр по-прежнему работает; если в одном объекте заданы обе формы одного ключа, приоритет у ВЕРХНЕГО регистра.\n\n### FIX-0709-9: снятие с публикации убирает вкладку по всем обработчикам приложения\n\n**Было**\n\n[POST \u002Fv1\u002Fapps\u002F:id\u002Funpublish](\u002Fdocs\u002Fapps\u002Funpublish) снимал placement только по обработчику, совпадающему с ожидаемым платформенным адресом. Если вкладка была привязана к техническому адресу самого приложения, Битрикс24 не находил её и не удалял — вкладка оставалась висеть в карточке CRM, и через API её уже нельзя было убрать.\n\n**Стало**\n\nСнятие с публикации теперь убирает placement по всем обработчикам приложения, включая привязанные к техническому адресу сервера — осиротевшая вкладка исчезает.\n\n### NEW-0709-10: Самоописание ключей в GET \u002Fv1\u002Fguide\n\nОтвет `GET \u002Fv1\u002Fguide` дополнен блоком `data.keysAuth`. Он описывает два эндпоинта самоописания — `GET \u002Fv1\u002Fme` и `GET \u002Fv1\u002Fguide` — и содержит ссылки на документацию: контракт ответа каждого из них, режим доступа ключа и общее описание типов ключей.\n\nОба эндпоинта работают по одному заголовку `X-Api-Key`, токен сессии для них не нужен.\n\nПоле аддитивное: существующие клиенты не затронуты.\n\n**Затронутые эндпоинты:** [GET \u002Fv1\u002Fguide](\u002Fdocs\u002Fkeys-auth\u002Fguide)\n\n### FIX-0709-11: GET \u002Fv1\u002F{entity}\u002F:id теперь учитывает ?select=\n\nПроекция полей `?select=` на чтении одной записи по id раньше игнорировалась: ответ всегда приходил со всеми полями, хотя `\u002Fv1\u002Fme` заявляет, что `?select=` работает «на списке, чтении по id и POST \u002Fsearch». Теперь чтение по id проецирует ответ так же, как список и поиск, — приводя поведение в соответствие с задокументированным.\n\n**Было**\n\n`GET \u002Fv1\u002Fleads\u002F42?select=id,title` возвращал полную запись (все поля).\n\n**Стало**\n\n`GET \u002Fv1\u002Fleads\u002F42?select=id,title` возвращает только `id` и `title`. Поддерживаются формы через запятую (`?select=id,title`), массивом (`?select[]=id&select[]=title`) и с индексами (`?select[0]=id&select[1]=title`); `id` включается в ответ всегда. Неизвестное имя поля молча пропускается — это не ошибка. При одновременном `?select=` и `?include=` связанная сущность в ответе сохраняется. Индексная форма (`?select[0]=…`) раньше возвращала 500 и на списке `GET \u002Fv1\u002F{entity}` — теперь тоже проецирует корректно.\n\n### FIX-0709-12: \u002Ffields сущностей заказов, позиций корзины, пресетов реквизитов и шаблонов документов приведены к реальному контракту B24\n\n**Было**\n\n`GET \u002F:entity\u002Ffields` (и генерируемая по нему OpenAPI-схема) объявлял поля, которые Bitrix24 не возвращает: `provider` у шаблонов документов; `reserved`, `sumPaid`, `dateBill`, `datePayBefore`, `datePaid`, `empPaidId`, `userEmail`, `userName` на верхнем уровне заказа; `module`, `fUserId`, `lid`, `dateRefresh`, `subscribe`, `reserved`, `reserveQuantity` у позиций корзины; `originatorId` у пресетов реквизитов. Фильтрация и сортировка по этим полям молча не срабатывали. При этом реально приходящие поля не были объявлены: `requisiteLink` у заказа, `type`\u002F`properties`\u002F`reservations` у позиции корзины. Пресеты реквизитов принимали `countryId`\u002F`entityTypeId` на обновление, где Bitrix24 их молча игнорирует.\n\n**Стало**\n\nНесуществующие поля убраны из `\u002Ffields` и OpenAPI. Реально приходящие поля объявлены: у заказа — `requisiteLink` (объект `requisiteId`\u002F`bankDetailId`\u002F`mcRequisiteId`\u002F`mcBankDetailId`, только чтение); у позиции корзины — `type`, `properties`, `reservations` (только чтение). У пресетов реквизитов `countryId` и `entityTypeId` помечены как задаваемые только при создании: обновление возвращает `400 READONLY_FIELD` вместо тихого игнорирования.\n\n**Влияние на интеграторов**\n\nОтветы list\u002Fget не меняются — убранные поля и так никогда не приходили. Если запрос фильтровал или сортировал по убранному полю, теперь он вернёт `400` — используйте реальные поля из `\u002Ffields` (например, даты и суммы оплаты у заказа лежат внутри массива `payments`, а не на верхнем уровне). Обновление `countryId`\u002F`entityTypeId` у пресета реквизитов теперь явно отклоняется — задавайте эти поля только при создании.\n\n**Затронутые эндпоинты:** [GET \u002Fv1\u002Forders\u002Ffields](\u002Fdocs\u002Fentities\u002Forders\u002Ffields), [GET \u002Fv1\u002Fbasket-items\u002Ffields](\u002Fdocs\u002Fentities\u002Fbasket-items\u002Ffields), [GET \u002Fv1\u002Frequisite-presets\u002Ffields](\u002Fdocs\u002Fentities\u002Frequisite-presets\u002Ffields), [GET \u002Fv1\u002Fdoc-templates\u002Ffields](\u002Fdocs\u002Fentities\u002Fdoc-templates\u002Ffields)\n\n### NEW-0709-13: GET \u002F:entity\u002Ffields отдаёт человекочитаемые label и описания для сделок, лидов, счетов, дел, справочников и комментариев таймлайна\n\nОтвет `GET \u002Fv1\u002Fdeals\u002Ffields`, `\u002Fv1\u002Fleads\u002Ffields`, `\u002Fv1\u002Finvoices\u002Ffields`, `\u002Fv1\u002Factivities\u002Ffields`, `\u002Fv1\u002Fstatuses\u002Ffields` и `\u002Fv1\u002Ftimelines\u002Ffields` теперь несёт по каждому полю человекочитаемые `label` и `description` (на `.tech` — по-русски, на `.com` — по-английски). У полей со служебными кодами добавлены словари `enum`: у сделок — `stageSemanticId` (P — в работе, S — успех, F — провал); у дел — `typeId`, `direction`, `priority`, `status`, `notifyType` и `descriptionType`. Прежние вызовы работают без изменений — это дополнительные поля метаданных, форма ответа не меняется.\n\n### FIX-0709-14: POST \u002Fv1\u002Fbatch — единый формат ошибок под-вызовов и totals только для list\u002Fsearch\n\n**Было**\n\nОшибка Bitrix24 внутри успешного 200-ответа [POST \u002Fv1\u002Fbatch](\u002Fdocs\u002Fbatch) (например, `get` несуществующего элемента) попадала в `data.errors[\u003Cid>]` в исходной форме Bitrix24 `{ error, error_description }` — не в общем для V1 конверте `{ code, message }`, который используют ошибки валидации и любой другой ответ API. Поле `data.totals[\u003Cid>]` при этом заполнялось для любого действия, включая `get`\u002F`create`\u002F`update`\u002F`delete`, где одиночное число рядом с единственной записью не имеет смысла.\n\n**Стало**\n\nОшибка под-вызова приводится к `{ code, message }` (`error` → `code`, `error_description` → `message`), как у остальных ошибок. `data.totals[\u003Cid>]` заполняется только для действий `list` и `search` — там, где счётчик совпадений реально имеет смысл.\n\n### FIX-0709-15: GET \u002Fv1\u002Fopenline-configs — нормализация пустых значений в ответе\n\n**Было**\n\nОтветы [GET \u002Fv1\u002Fopenline-configs](\u002Fdocs\u002Fopenlines\u002Fconfig\u002Flist) и [GET \u002Fv1\u002Fopenline-configs\u002F:id](\u002Fdocs\u002Fopenlines\u002Fconfig\u002Fget) отдавали служебные поля в неудобных для клиента формах: `KPI_FIRST_ANSWER_LIST`, `KPI_FURTHER_ANSWER_LIST`, `DEFAULT_OPERATOR_DATA` приходили как `null` (на них падал `.map`\u002F`.length`); `AUTO_CLOSE_TEXT` для незаданного значения приходил как `\"\"` в карточке и как `null` в списке; `WORKTIME_HOLIDAYS`\u002F`WORKTIME_DAYOFF` для пустого набора приходили как `[\"\"]` (массив с одной пустой строкой).\n\n**Стало**\n\nСписки нормализованы: `null` → `[]`. `AUTO_CLOSE_TEXT` приведён к единому `null` для пустого значения и в списке, и в карточке. `WORKTIME_HOLIDAYS`\u002F`WORKTIME_DAYOFF` для пустого набора приходят как `[]`. Список и карточка теперь отдают одинаковую форму этих полей.\n\n### FIX-0709-16: GET \u002Fv1\u002Fusers\u002Ffields отдаёт возможные значения (items) для UF-полей-перечислений\n\n**Было**\n\nПользовательское поле-перечисление (`UF_USR_*` типа «список») приходило в `GET \u002Fv1\u002Fusers\u002Ffields` как `{ \"type\": \"string\", \"label\": \"…\" }` — без списка возможных значений. Причина: метод `user.fields` возвращает у UF-полей только подпись, без типа и вариантов, поэтому перечисление было неотличимо от строки.\n\n**Стало**\n\nТакое поле приходит с настоящим типом и списком вариантов: `{ \"type\": \"enumeration\", \"label\": \"…\", \"items\": [ { \"ID\": \"…\", \"VALUE\": \"…\", \"DEF\": \"…\", \"XML_ID\": \"…\" }, … ] }`. Значения дочитываются из `user.userfield.list` — для этого у ключа должен быть скоуп `user.userfield`; если он не выдан, поле по-прежнему отдаётся с подписью, но без `items` (мягкая деградация). Заодно у остальных UF-полей (`money`, `date` и т. п.) в ответе появляется их настоящий тип вместо `string`.\n\n### FIX-0709-17: подсказка hint при пустой очереди событий появляется по факту устойчивой пустоты\n\n**Было**\n\n[GET \u002Fv1\u002Fbots\u002F:botId\u002Fevents](\u002Fdocs\u002Fbots\u002Fevents\u002Fpolling) увеличивал счётчик пустых ответов ровно на каждый запрос, и поле `hint` появлялось строго после пятого подряд пустого ответа. Число в тексте подсказки совпадало с количеством сделанных запросов.\n\n**Стало**\n\nСчётчик пустых ответов обновляется периодически, а не на каждый запрос, поэтому `hint` появляется после устойчивой пустоты очереди — при рекомендованном интервале опроса 2–5 секунд спустя примерно пару минут непрерывно пустого опроса. Число N в тексте отражает количество зафиксированных периодов пустоты, а не точное количество сделанных запросов. Правило «доставлено событие → счётчик и подсказка сбрасываются» не изменилось.\n\n**Влияние на интеграторов**\n\nМенять код не нужно. Если вы опирались на появление `hint` строго на пятом запросе или трактовали N как точное число запросов — используйте `persisted` и наличие событий как основной сигнал, а `hint` как диагностическую подсказку.\n\n## 2026-07-08\n\n### FIX-0708-1: GET \u002Fv1\u002Fdoc-templates и POST \u002Fsearch честят order и offset\n\n**Было**\n\nПараметры `order` и `offset` на `GET \u002Fv1\u002Fdoc-templates` и `POST \u002Fv1\u002Fdoc-templates\u002Fsearch` молча игнорировались: список всегда возвращался в порядке возрастания по `id`, а `offset` не смещал окно выборки. Причина — метод Bitrix24 отдаёт шаблоны объектом с ключами-`id`, и заданный порядок терялся при разворачивании ответа.\n\n**Стало**\n\nСортировка (`order[поле]=asc|desc`, в том числе по нескольким полям) и постраничная выборка (`offset`\u002F`limit`) применяются на стороне Vibecode: набор шаблонов вытягивается полностью, сортируется и режется по запрошенному окну. `total` и `hasMore` считаются от фактически собранного набора.\n\n**Влияние на интеграторов**\n\nТем, кто полагался на неявный порядок «по возрастанию `id`» при `offset=0` без сортировки, менять ничего не нужно — это остаётся поведением по умолчанию. `POST \u002Fv1\u002Fdoc-templates\u002Fbatch` (batch-list) не затронут. Строковый порядок (`name`, `region`) — побайтовый, без учёта локали.\n\n### NEW-0708-2: GET \u002Fv1\u002Fapps\u002F:id\u002Fsources — новое поле linkedServerSources\n\n[GET \u002Fv1\u002Fapps\u002F:id\u002Fsources](\u002Fdocs\u002Fsource-storage) теперь дополнительно возвращает поле `linkedServerSources` — версии исходников, сохранённые под сервером (через `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources` или авто-сохранение при деплое), сгруппированные по серверу, каждая со своим `serverContext`. Такие версии раньше не попадали в этот ответ, если сохранялись под личным ключом, — теперь они видны.\n\nПоле аддитивное: `versions`, `totalVersions`, `currentVersionId` и `totalSizeBytes` не изменились и по-прежнему перечисляют только версии, привязанные к приложению. Рядом приходят `linkedServerSourcesTruncated` (признак усечения при очень большом числе версий) и `linkedServerHint` с указателем на `GET \u002Fv1\u002Finfra\u002Fservers\u002F:serverId\u002Fsources` — авторитетный полный список и скачивание этих версий. Секция заполняется для автора приложения (личный ключ) и администратора портала; при вызове ключом OAuth-приложения она пуста, а при `?sha256=`-пробе не вычисляется.\n\n### NEW-0708-3: поле preemptible в ответе списка тарифов серверов\n\nОтвет [GET \u002Fv1\u002Finfra\u002Fproviders\u002F:id\u002Fplans](\u002Fdocs\u002Finfra\u002Fproviders\u002Fplans) теперь формально описывает поле `preemptible` у каждого тарифа. Вытесняемый тариф дешевле, но облако принудительно перезапускает такую машину примерно раз в сутки — он не подходит для непрерывных 24\u002F7-нагрузок. Для сервера, агента или бота, которые должны работать без перерывов, выбирайте невытесняемый тариф (`preemptible` равно `false` или отсутствует).\n\nПоле уже отдавалось в ответе рантаймом — эта запись фиксирует его в OpenAPI и документации; менять существующие интеграции не требуется.\n\n### NEW-0708-4: GET \u002Fv1\u002Fmodels\u002F:id теперь показывает цену преемника у снятых моделей и поле replaced_by\n\nДля модели, снятой с публикации, запрос детали по идентификатору теперь возвращает поле `replaced_by` с идентификатором модели-преемника, на которую фактически уходят вызовы, а поле `pricing` показывает цену этого преемника — ту, по которой запрос и тарифицируется. Раньше `pricing` показывал собственную нулевую цену снятой строки, из-за чего модель выглядела бесплатной, хотя вызовы обслуживал платный преемник. Обычные модели и прежние вызовы не меняются.\n\n### FIX-0708-5: автопагинация списков сохраняет порядок строк при limit выше 550\n\n**Было**\n\nСписочные запросы с автопагинацией — `GET \u002Fv1\u002F{entity}?limit=…` и `POST \u002Fv1\u002F{entity}\u002Fsearch` — при `limit` выше ~550 возвращали строки с нарушенным порядком: внутренние страницы выдачи склеивались не в том порядке, в котором их отдал Битрикс24, поэтому параметр `order` на итоговом массиве не соблюдался. Если записей было больше, чем `limit`, обрезка окна могла выбросить строки из середины отсортированной выборки, оставив более поздние.\n\n**Стало**\n\nСтраницы склеиваются строго в порядке выдачи Битрикс24: строки приходят в заказанной сортировке при любом `limit`, а обрезка по `limit` больше не выбрасывает строки из середины выборки из-за неверного порядка склейки.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно. Если вы пересортировывали большие выборки на своей стороне как обходной путь — это больше не требуется.\n\n### FIX-0708-6: автопагинация больше не теряет молча страницу при сбое пакетного подзапроса\n\n**Было**\n\nПри `limit > 50` список собирается пакетными подзапросами по 50 записей. Если Битрикс24 отклонял один подзапрос (чаще всего по лимиту запросов — `QUERY_LIMIT_EXCEEDED`), его страница молча выпадала из середины выборки: ответ оставался `200`, в данных образовывалась необнаружимая дыра в 50 записей (например, записи 1–200 и 251–600 без 201–250), а `meta.total` и `meta.hasMore` выглядели непротиворечиво.\n\n**Стало**\n\nДля списков, обычного поиска, пакетных подвызовов и агрегаций результат — всегда непрерывный префикс выборки: записи после сбойной страницы отбрасываются, `meta.hasMore` остаётся `true`, и в ответе появляется `meta.pageErrorSample { code, message }` с причиной сбоя — по образцу `meta.windowErrorSample` оконного поиска. Поле добавлено в ответы списков (например [GET \u002Fv1\u002Fdeals](\u002Fdocs\u002Fentities\u002Fdeals\u002Flist)), в `POST \u002Fv1\u002F{entity}\u002Fsearch` (например [сделки](\u002Fdocs\u002Fentities\u002Fdeals\u002Fsearch)), в `meta` подвызовов [POST \u002Fv1\u002Fbatch](\u002Fdocs\u002Fbatch) и в `data.meta` агрегаций `POST \u002Fv1\u002F{entity}\u002Faggregate` (там оно объясняет, почему `recordsProcessed` меньше `totalRecords`). В оконном поиске (широкий диапазон дат) набор собирается из окон, поэтому при потере страницы внутри одного окна ответ может недосчитаться хвоста этого окна — признак неполноты там именно `meta.pageErrorSample`, а не `meta.hasMore`. Во всех случаях поле появляется, только если итоговая страница действительно короче `limit`: полный ответ ложным сигналом не помечается. Неполный ответ не кэшируется: повторный запрос сразу идёт в Битрикс24.\n\n**Влияние на интеграторов**\n\nМенять клиентский код не нужно: выборки, которые раньше могли содержать незаметную дыру, теперь корректны, а причина недобора видна в `meta.pageErrorSample`. Дочитать остаток можно повторным запросом с `offset`, равным сумме исходного `offset` и числа полученных записей, — кроме оконного поиска по широкому диапазону дат (там `offset` не поддерживается: сузьте диапазон или повторите запрос позже).\n\n### BC-0708-7: структурированный вывод: обрезанный или пустой ответ теперь возвращает 422, а не пустой 200\n\n> Поддержка старого формата до: 08.07.2026\n\n**Было**\n\n[POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions) со `response_format` (`json_object` или `json_schema`) при обрыве генерации мог вернуть `200` с `content: null` (или обрезанной, непарсимой строкой JSON) и предупреждением, которое клиенты не замечали. Чаще всего это случалось на моделях рассуждения: фаза рассуждения расходовала весь бюджет `max_tokens` до того, как модель писала JSON. Ответ выглядел успешным, но разобрать его было нельзя.\n\n**Стало**\n\nТакой запрос возвращает `422` с `code: \"structured_output_truncated\"`, полями `finishReason`, `suggestedMaxTokens` (рекомендованный увеличенный `max_tokens` для повтора) и `param: \"max_tokens\"`. В потоковом режиме перед `data: [DONE]` приходит служебный кадр `{\"error\":{\"code\":\"structured_output_truncated\"}}` — читайте поток до `[DONE]`. Дополнительно: для бесплатных моделей рассуждения при слишком маленьком `max_tokens` платформа поднимает бюджет до безопасного минимума и помечает успешный ответ предупреждением `MAX_TOKENS_RAISED`. Обрезанная попытка по-прежнему расходует и тарифицирует токены.\n\n**Что делать интеграторам**\n\nОбрабатывайте `422 structured_output_truncated` в ветке ошибок и повторяйте запрос с бóльшим `max_tokens` (можно взять значение из `suggestedMaxTokens`). Для строго-детерминированного JSON задавайте `max_tokens` с запасом или используйте обычную (не «thinking») модель.\n\n## 2026-07-07\n\n### FIX-0707-1: smart-processes: linkedUserFields принимает Y\u002FN и булевы значения\n\n**Было**\n\n[POST \u002Fv1\u002Fsmart-processes](\u002Fdocs\u002Fentities\u002Fsmart-processes\u002Fcreate) и [PATCH \u002Fv1\u002Fsmart-processes\u002F:entityTypeId](\u002Fdocs\u002Fentities\u002Fsmart-processes\u002Fupdate) с `linkedUserFields` работали только когда значение флага было строго `\"true\"`\u002F`\"false\"`. Значение в конвенции `\"Y\"`\u002F`\"N\"` (как у всех остальных полей смарт-процесса) или булево `true`\u002F`false` молча игнорировалось: запрос возвращал `success: true`, но отображение в пользовательском поле не включалось.\n\n**Стало**\n\nЗначения `linkedUserFields` нормализуются так же, как вложенный флаг `relations[].isChildrenListEnabled`: `true`\u002F`\"Y\"`\u002F`\"yes\"`\u002F`1` → включено, `false`\u002F`\"N\"`\u002F`\"no\"`\u002F`0` → выключено. Прежние вызовы с `\"true\"`\u002F`\"false\"` продолжают работать без изменений.\n\n**Влияние на интеграторов**\n\nНичего менять не нужно — вызовы, которые раньше «молча не срабатывали» с `\"Y\"`, теперь применяются корректно.\n\n### BC-0707-2: Нормализация вложенных полей карточки заказа\n\n> Поддержка старого формата до: 06.01.2027\n\n**Было**\n\n`GET \u002Fv1\u002Forders\u002F{id}` отдавал вложенные массивы `clients`, `payments`, `basketItems` в сыром виде Bitrix24: булевы поля строками `\"Y\"` и `\"N\"` (`payments[].paid`, `clients[].isPrimary`, `basketItems[].vatIncluded` и другие), даты внутри `payments` и `basketItems` со смещением `+03:00`, а поле `companyId` со значением `0`, когда компания не задана. Поле `accountNumber` при создании и обновлении молча игнорировалось.\n\n**Стало**\n\nВложенные Y\u002FN-поля приходят как boolean (`true` или `false`); вложенные даты нормализованы к UTC (оканчиваются на `Z`); `companyId` при отсутствии компании приходит `null` вместо `0`; `accountNumber` стал полем только для чтения — попытка задать его при создании или обновлении возвращает `400` с кодом `READONLY_FIELD`.\n\n**Что делать интеграторам**\n\nЧитать вложенные Y\u002FN-поля как boolean, а не сравнивать со строкой `\"Y\"`; трактовать `null` вместо `0` как признак «компания не задана»; не передавать `accountNumber` в теле создания и обновления — номер присваивается автоматически.\n\n### FIX-0707-3: Спека \u002Fv1\u002Fopenapi.json приведена к фактическому рантайму\n\n**Было**\n\nМашинная OpenAPI-спека генерировалась из статической метаданной сущностей и расходилась с реальными ответами: ни одно поле не помечено nullable, вложенные массивы карточки заказа типизировались как строка, у списочных методов не описаны `filter` и `select`, у операций объявлены только успешные коды и `403`.\n\n**Стало**\n\nСпека теперь отражает контракт. Nullable-поля выводятся в форме `type: [\"\u003Cтип>\", \"null\"]`. Объектные и массив-объектов поля типизируются честно, включая вложенные `clients`, `payments`, `basketItems`, `propertyValues` у `GET \u002Fv1\u002Forders\u002F{id}`. На списочных методах описаны query-параметры `filter` и `select`. Операции несут стандартные коды ошибок `400`, `401`, `404`, `422` в едином конверте `{ success:false, error:{ code, message } }`. Схемы `*Input` объявляют обязательные при создании поля. Дополнительно `GET \u002Fv1\u002Forders\u002Ffields` отдаёт `clients` как массив вместо object. SDK, сгенерированный из спеки, получает корректную типизацию.\n\n### BC-0707-4: \u002Fsearch: авто-оконный поиск при полном отказе отдаёт настоящую ошибку Bitrix24\n\n> Поддержка старого формата до: 07.09.2026\n\n**Было**\n\nЛюбой неуспешный авто-оконный `POST \u002Fv1\u002F{entity}\u002Fsearch` возвращал `502 { \"error\": { \"code\": \"WINDOWED_SEARCH_FAILED\" } }` с общим советом «добавьте autoWindow:false».\n\n**Стало**\n\nОтвет совпадает с тем же запросом на узком диапазоне — реальный код и сообщение: отклонённое поле фильтра\u002Fсортировки → `400 UNKNOWN_FILTER_FIELD` \u002F `400 INVALID_PARAMS`; нет прав → `403`; лимит запросов \u002F перегрузка очереди → `429` + `Retry-After`; таймаут → `503`; недоступность Bitrix24 → `502 BITRIX_UNAVAILABLE`. Частичный отказ окон (статус `200`) теперь несёт `meta.windowErrorSample` `{ code, message }`.\n\n**Что делать интеграторам**\n\nЕсли вы ветвились на `error.code === \"WINDOWED_SEARCH_FAILED\"` (например, чтобы повторить с `autoWindow:false`) — ветвитесь на реальные коды. Обходной путь `autoWindow:false` остался; он полезен там, где действительно помогает (подсказка `429 QUEUE_TIMEOUT` называет его). Ответ полного отказа больше не несёт блок `meta` (`autoWindowed`\u002F`windowCount`\u002F`windowErrors`) — сигнал теперь в самом коде\u002Fсообщении ошибки; `meta.windowErrorSample` остаётся на частичном отказе (статус `200`).\n\n### FIX-0707-5: списки на портале без модуля стабильно отдают 409, а не 429\n\n**Было**\n\nНа портале, где модуль «Универсальные списки» не включён, вызовы [\u002Fv1\u002Flists](\u002Fdocs\u002Flists) отдавали понятный `409 LISTS_MODULE_NOT_ENABLED` только для первых нескольких запросов. После этого встроенная защита от циклов ошибок срабатывала и все последующие вызовы возвращали `429 ERROR_LOOP_DETECTED` — реальная причина (модуль не подключён) переставала быть видна.\n\n**Стало**\n\nСигнал «метод недоступен на портале» больше не учитывается защитой от циклов, поэтому вызовы `lists.*` на портале без модуля стабильно возвращают `409 LISTS_MODULE_NOT_ENABLED` при любом числе повторов. Ответ остаётся действенным: подключите модуль на портале и повторите запрос.\n\n### NEW-0707-6: Подсказка error.hint на 400 при создании сервера без source и без provider\u002Fplan\u002Fregion\n\n[POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) при `400 INVALID_REQUEST` из-за отсутствующих `provider`\u002F`plan`\u002F`region` (и отсутствующего `source`) на портале с galaxy-размещением теперь дополнительно возвращает объект `error.hint` с полями `reason` (почему запрос отклонён на этом портале), `recovery` (рекомендованный one-shot путь и рабочая двухшаговая альтернатива) и `example` (готовый скелет тела one-shot запроса). Поля `error.code` и `error.message` не изменились — подсказка строго аддитивна; на порталах без galaxy-размещения ответ прежний, без `hint`.\n\nПодсказка также возвращается в ветке `400 RUNTIME_PARAM_REMOVED` (создание с `runtime`, но без `source`, на портале с galaxy-размещением), а тело с `placement: \"dedicated\"` получает отдельный вариант подсказки — под выделенный сервер, с сохранением намерения и добавлением недостающего tuple `provider`\u002F`plan`\u002F`region`, без увода в galaxy-контейнер.\n\n### FIX-0707-7: Galaxy-чек-лист деплоя в \u002Fv1\u002Fme приведён к фактическому контракту\n\n**Было**: шаг 2 чек-листа `deployment.galaxyApp.checklist` предписывал [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) `{ name }` без `source` и без `provider`\u002F`plan`\u002F`region` — такой вызов всегда завершался `400 INVALID_REQUEST`. Правило CREATE не объясняло, что для двухшагового пути обязателен полный набор `provider`\u002F`plan`\u002F`region`, а `newAppPlacement.note` обещала выделенный standalone-VM там, где создание возвращает galaxy-слот с `next: \"deploy\"`. Favicon-гайд направлял в этот же неработающий порядок; окно уборки недеплоенного слота указывалось как «~12-20 мин» при фактических ~20-25.\n\n**Стало**: рекомендованный путь — один вызов `POST \u002Fv1\u002Finfra\u002Fservers { name, source, runtime, start }` (`provider`\u002F`plan`\u002F`region` опускаются). Двухшаговый путь описан правдиво: создание без `source` требует полный `provider`\u002F`plan`\u002F`region` (значения для galaxy информационны — приложение наследует хост), на galaxy-размещении возвращает слот с `next: \"deploy\"`; недеплоенный слот убирается в ERROR после ~20 мин (проверка каждые 5 мин). Favicon: основной путь — собственный `\u002Ficon.svg` в архиве (id не нужен); платформенный URL — альтернатива через two-step или re-deploy. Шаг опроса статуса получил ветку `status=error` → `provisionError`\u002F`buildLog` → re-deploy.\n\n**Влияние на интеграторов**: агенты, следующие чек-листу, деплоят с первого вызова. Поведение эндпоинтов не менялось — обновлены только тексты `\u002Fv1\u002Fme` и описание в `\u002Fv1\u002Fopenapi.json`; существующие интеграции продолжают работать без изменений.\n\n### NEW-0707-8: AI-квота компании доступна через API\n\nНовый эндпоинт [GET \u002Fv1\u002Fai\u002Fquota](\u002Fdocs\u002Fai\u002Fconsumption\u002Fquota) возвращает состояние месячной AI-квоты портала: процент израсходованного лимита (`pctUsed`, честное значение — при перерасходе больше 100), признак исчерпания (`exhausted`), дату сброса (`resetAt`, скользящее 30-дневное окно) и разбивку по моделям — количество запросов, токены и долю месячного лимита на каждую модель (`byModel[].pctOfLimit`). Абсолютные значения лимита в Вайбах не раскрываются — только проценты, как в кабинете. Требуется скоуп `vibe:ai`.\n\n## 2026-07-06\n\n### NEW-0706-1: Поля pricing.perCall и pricing.perMinute в каталоге моделей\n\nОтветы [GET \u002Fv1\u002Fmodels](\u002Fdocs\u002Fai\u002Fmodels\u002Flist) и [GET \u002Fv1\u002Fmodels\u002F{model}](\u002Fdocs\u002Fai\u002Fmodels\u002Fget) дополнены необязательными полями в объекте `pricing`: `perCall` — стоимость одного вызова в Вайбах, `perMinute` — стоимость одной минуты аудио в Вайбах (для моделей распознавания речи). Поля появляются только у моделей, для которых соответствующая базовая цена больше нуля; у остальных моделей объект `pricing` не меняется — существующие запросы работают без изменений.\n\n### NEW-0706-2: Новый код ошибки 402 ai_quota_exhausted на AI-эндпоинтах\n\nПри включённом контроле месячной AI-квоты портала запросы [POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions), [POST \u002Fv1\u002Fembeddings](\u002Fdocs\u002Fai\u002Fembeddings) и [POST \u002Fv1\u002Faudio\u002Ftranscriptions](\u002Fdocs\u002Fai\u002Faudio\u002Ftranscriptions) могут вернуть 402 с телом `{ success: false, error: { code: \"ai_quota_exhausted\", type: \"insufficient_quota\", reason, resetAt? } }`. Поле `reason` различает три случая: `breaker` — сработал часовой предохранитель расходов сверх квоты, `wallet_empty` — квота исчерпана и на балансе портала нет средств, `wallet_off` — расход сверх квоты для портала недоступен. `resetAt` — момент, когда запросы снова начнут проходить (для `wallet_off` при полном отключении может отсутствовать). Пока квота портала не исчерпана, поведение эндпоинтов не меняется.\n\n### FIX-0706-3: Расход сверх AI-квоты списывается по базовой цене модели из каталога\n\n**Было**\n\nПри активном контроле месячной AI-квоты портала запросы сверх квоты на [POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions), [POST \u002Fv1\u002Fembeddings](\u002Fdocs\u002Fai\u002Fembeddings) и [POST \u002Fv1\u002Faudio\u002Ftranscriptions](\u002Fdocs\u002Fai\u002Faudio\u002Ftranscriptions) списывались с баланса портала по внутренним ставкам программы квот — со скидками непикового времени; итоговую цену нельзя было увидеть в каталоге моделей.\n\n**Стало**\n\nРасход сверх квоты списывается по базовой цене модели из публичного каталога — той же, что возвращается в поле `pricing` ответа [GET \u002Fv1\u002Fmodels](\u002Fdocs\u002Fai\u002Fmodels\u002Flist), включая новые `perCall` и `perMinute` для не-токенных моделей. Скидки непикового времени применяются только к списанию квоты, а не к денежному балансу. Расход в рамках квоты по-прежнему не списывается с баланса портала.\n\n**Влияние на интеграторов**\n\nМенять ничего не требуется. Стоимость работы сверх квоты теперь можно рассчитать заранее по каталожной цене модели.\n\n### NEW-0706-4: Модель эмбеддингов bitrix\u002Fembeddings доступна в API\n\nЭндпоинт [POST \u002Fv1\u002Fembeddings](\u002Fdocs\u002Fai\u002Fembeddings) теперь обслуживается моделью `bitrix\u002Fembeddings` — преобразование текста в векторные представления для семантического поиска, кластеризации и retrieval (RAG). Модель бесплатная, тарификация только по входным токенам. Список моделей с поддержкой эмбеддингов — [GET \u002Fv1\u002Fmodels](\u002Fdocs\u002Fai\u002Fmodels\u002Flist).\n\n### FIX-0706-5: Деплой честно сообщает об ошибке, если новый билд не занял порт\n\nРазвёртывание через [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) теперь проверяет, что порт держит именно новый сервис. Если предыдущий процесс продолжает слушать порт, а новый билд крэш-луп'ит с EADDRINUSE, деплой честно завершается ошибкой вместо ложного успеха; порт, занятый оставшимся процессом того же приложения, при возможности освобождается автоматически.\n\n**Было**\n\nСтарая версия продолжала отвечать `200`, деплой рапортовал успех, а новый билд так и не поднимался — без ошибки и без подсказки.\n\n**Стало**\n\nШаг `healthcheck` возвращает ошибку с указанием EADDRINUSE и порта, а шаг `stop_existing` освобождает порт от оставшегося процесса приложения (или предупреждает и продолжает, если освободить нельзя).\n\n### NEW-0706-6: фильтр: оператор $nin (NOT IN) для исключения набора значений\n\n**Было**\n\nОтобрать записи, у которых поле НЕ входит в набор значений, было нельзя: оператор `$in` (IN) поддерживался, а обратного не было. Родные префиксы Битрикс24 `@` (IN) и `!@` (NOT IN) в имени поля (`{ \"!@categoryId\": [1, 3] }`) не транслировались — такой фильтр по сделкам возвращал `400 UNKNOWN_FILTER_FIELD`.\n\n**Стало**\n\nДобавлен оператор `$nin`: `{ \"filter\": { \"categoryId\": { \"$nin\": [1, 3] } } }` вернёт записи со всеми значениями, кроме перечисленных (NOT IN). Симметричен `$in`. Родные префиксы Битрикс24 `@` \u002F `!@` в имени поля по-прежнему не поддерживаются, но теперь дают понятный `400 INVALID_FILTER_FIELD` с подсказкой перейти на `$in` \u002F `$nin` — вместо невнятной ошибки или молча проигнорированного (и потому возвращавшего весь набор) фильтра.\n\n### BC-0706-7: отдельный код ошибки для слишком длинной команды exec\n\n> Поддержка старого формата до: 06.01.2027\n\n**Было**\n\nКоманда длиннее 10 000 символов у [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fexec](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fexec) отклонялась общим кодом `VALIDATION_ERROR` без указания причины и выхода.\n\n**Стало**\n\nТакой запрос возвращает 400 с отдельным кодом `COMMAND_TOO_LONG` и структурированным `hint`: большие данные и скрипты передаются через [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fupload](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fupload), затем выполняются `bash \u002Fпуть\u002Fскрипт.sh`. Остальные нарушения схемы по-прежнему возвращают `VALIDATION_ERROR`.\n\n**Что делать интеграторам**\n\nЕсли ваш клиент обрабатывает `VALIDATION_ERROR` этого эндпоинта как общий случай ошибки валидации — добавьте обработку кода `COMMAND_TOO_LONG` (или обрабатывайте любой 400 единообразно).\n\n### NEW-0706-8: подсказка в ошибке таймаута exec\n\nОшибка `EXEC_TIMEOUT` у [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fexec](\u002Fdocs\u002Finfra\u002Fdeploy\u002Fexec) теперь несёт структурированное поле `hint` (`reason` \u002F `recovery` \u002F `recoveryAction`): почему процесс был остановлен (по истечении `timeout` процесс-группа завершается принудительно, без grace-паузы) и что делать — запустить длинную операцию фоновой задачей и следить за ней через [GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Flogs](\u002Fdocs\u002Finfra\u002Fdeploy\u002Flogs), поднять `timeout` до 600 секунд или использовать режим `?stream=true`. Поле аддитивное: прежний формат `code` \u002F `message` не изменился, подсказка приходит и в JSON-режиме, и в SSE-событии `error`.\n\n## 2026-07-05\n\n### FIX-0705-1: Отправка сообщения в чат — понятная ошибка при пустом тексте\n\nТекст сообщения передаётся в поле `message`. Раньше вызов [POST \u002Fv1\u002Fchats\u002F{dialogId}\u002Fmessages](\u002Fdocs\u002Fchats\u002Fmessages\u002Fsend) с текстом под неизвестным именем поля (например `{\"text\": \"hi\"}`) молча отбрасывал это поле, и Битрикс24 возвращал `422 BITRIX_ERROR` о пустом сообщении — хотя контент был передан.\n\n**Было**\n\n`{\"text\": \"hi\"}` → `422 BITRIX_ERROR` о пустом сообщении, без указания причины.\n\n**Стало**\n\nТот же вызов сразу возвращает `400 MESSAGE_REQUIRED` и перечисляет нераспознанные поля, подсказывая поле `message`. Пустой текст по-прежнему допустим вместе с блоком `attach` (сообщение только с вложением).\n\n**Влияние на интеграторов**\n\nКорректные вызовы с полем `message` не меняются. Ошибка при неверном имени поля стала точной.\n\n### FIX-0705-2: Ключ в заголовке Authorization: Bearer — понятная ошибка вместо INVALID_SESSION\n\nAPI-ключ передаётся в заголовке `X-Api-Key`. Раньше, если ключ по ошибке клали в `Authorization: Bearer` (это место — для сессионного токена OAuth-приложения), сервер возвращал `401 INVALID_SESSION`, и интегратор искал проблему в OAuth-сессии, хотя причина была в неверном заголовке.\n\n**Было**\n\nКлюч `vibe_app_*` в `Authorization: Bearer` → `401 INVALID_SESSION`.\n\n**Стало**\n\nТот же запрос возвращает `401 WRONG_AUTH_SCHEME` с подсказкой: ключ OAuth-приложения (`vibe_app_*`) передаётся в `X-Api-Key`, а `Authorization: Bearer` несёт сессионный токен (`vibe_session_*`) из [POST \u002Fv1\u002Foauth\u002Ftoken](\u002Fdocs\u002Fkeys-auth); клиенту, который умеет только Bearer, подойдёт личный ключ (`vibe_api_*`). Сессионные токены и личные ключи в Bearer не затронуты.\n\n**Влияние на интеграторов**\n\nКорректные вызовы с ключом в `X-Api-Key` и сессией в `Authorization: Bearer` не меняются.\n\n### FIX-0705-3: multipart\u002Fcreate отклоняет XSS-опасные типы содержимого для PUBLIC-объектов\n\n**Было**\n\nДля `PUBLIC`-объектов типы содержимого `text\u002Fhtml`, `application\u002Fjavascript`, `application\u002Fx-javascript` и `image\u002Fsvg+xml` отклонялись при прямой и presigned-загрузке, но не при инициализации многочастевой (multipart) загрузки. Вызов [POST \u002Fv1\u002Fstorage\u002Fobjects\u002Fmultipart\u002Fcreate](\u002Fdocs\u002Fstorage) с `visibility` = `PUBLIC` и таким типом создавал сессию, и после завершения объект отдавался встроенно в браузере.\n\n**Стало**\n\n[POST \u002Fv1\u002Fstorage\u002Fobjects\u002Fmultipart\u002Fcreate](\u002Fdocs\u002Fstorage) с `visibility` = `PUBLIC` и XSS-опасным типом содержимого возвращает `415 STORAGE_FORBIDDEN_CONTENT_TYPE` — так же, как прямая и presigned-загрузка. Многочастевая сессия при этом не открывается. `PRIVATE`-объекты по-прежнему допускают любой тип содержимого.\n\n**Влияние на интеграторов**\n\nПоведение приведено к задокументированному в разделе «Хранилище»: XSS-опасные типы содержимого недопустимы для `PUBLIC`-объектов на всех путях загрузки. Чтобы загрузить такой файл многочастевой загрузкой, используйте `visibility` = `PRIVATE` либо безопасный тип содержимого.\n\n### FIX-0705-4: привязка плейсмента на технический адрес сервера теперь ведёт через платформенный обработчик\n\n**Было**\n\nPOST \u002Fv1\u002Fplacements\u002Fbind принимал `handler`, указывающий на технический Black Hole-адрес приложения (app-*.vibecode…), и регистрировал его в Битрикс24 как есть. Битрикс24 отправлял iframe плейсмента напрямую на этот адрес, минуя платформу: сессия не выпускалась, и на сервере с доступом «только для пользователей Битрикс24» открытие плейсмента зацикливало авторизацию (на публичном сервере приложение отдавало собственную ошибку 404).\n\n**Стало**\n\nТакой `handler` автоматически переписывается на платформенный обработчик приложения (\u002Fv1\u002Fbitrix-handler) — плейсмент открывается и авторизуется штатно. В ответе появляются `handlerRewritten: true` и `requestedHandler` с исходным значением. Внешние (не Black Hole) обработчики не изменяются. Если платформенный обработчик приложения не удаётся определить, привязка отклоняется с кодом `PLATFORM_HANDLER_UNRESOLVABLE` вместо регистрации нерабочего адреса. Дополнительно GET \u002Fv1\u002Fplacements помечает уже неправильно привязанный обработчик: `data.handlers[].misbound: true` плюс текстовый `warnings[]`.\n\nКроме того, если плейсмент уже зарегистрирован в Битрикс24, но отсутствует в списке приложения (рассинхрон — например, после снятия с публикации без отвязки в Битрикс24), привязка больше не завершается ошибкой «Handler already binded»: платформа снимает устаревшую привязку и повторяет запрос один раз, восстанавливая рассинхрон. Если же привязка не проходит по другой причине (например, требуется коммерческий тариф Битрикс24), рабочий плейсмент не снимается.\n\n### NEW-0705-5: Новый код ошибки 402 ai_quota_exhausted на AI-эндпоинтах\n\nПри включённом контроле месячной AI-квоты портала запросы [POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions), [POST \u002Fv1\u002Fembeddings](\u002Fdocs\u002Fai\u002Fembeddings) и [POST \u002Fv1\u002Faudio\u002Ftranscriptions](\u002Fdocs\u002Fai\u002Faudio\u002Ftranscriptions) могут вернуть 402 с телом `{ success: false, error: { code: \"ai_quota_exhausted\", type: \"insufficient_quota\", reason, resetAt? } }`. Поле `reason` различает три случая: `breaker` — сработал часовой предохранитель расходов сверх квоты, `wallet_empty` — квота исчерпана и на балансе портала нет средств, `wallet_off` — расход сверх квоты для портала недоступен. `resetAt` — момент, когда запросы снова начнут проходить (для `wallet_off` при полном отключении может отсутствовать). Пока квота портала не исчерпана, поведение эндпоинтов не меняется.\n\n## 2026-07-04\n\n### BC-0704-1: Перегрузочные отказы: 429\u002F503 вместо 504\n\n> Поддержка старого формата до: 31.07.2026\n\nПерегрузочные отказы сменили HTTP-статусы (коды в теле ответа НЕ изменились — меняется только статус). Правило: **429** — запрос НЕ был обработан, безопасно повторить через `Retry-After` (заголовок теперь ставится всегда); **503** — платформе или апстриму плохо, повторите позже, для write-операций сначала проверьте, применилось ли изменение. Прикладной 504 из API исключён.\n\n**Что изменилось:** `QUEUE_OVERFLOW` 503→429; `QUEUE_TIMEOUT` 504→429; `BITRIX_TIMEOUT` (Bitrix24 не ответил за 15 секунд — для этого кода write мог примениться, перечитайте сущность перед повтором) →503; `ai_provider_timeout` 504→503; `UPSTREAM_TIMEOUT` (веб-поиск `\u002Fv1\u002Fsearch` — апстрим-провайдер не ответил вовремя) 504→503; `RUNTIME_TIMEOUT` \u002F `GATEWAY_TIMEOUT` \u002F `WAKE_TIMEOUT` 504→503.\n\n### NEW-0704-2: Новый код перегрузки AI: 429 ai_congested\n\nAI-запросы к платформенному кластеру теперь проходят через admission-гейт: при перегрузке пула ответ — `429` с кодом `ai_congested` в теле и заголовком `Retry-After`. Повтор безопасен (запрос не выполнялся, списания нет). BYOK-ключи и внешние провайдеры гейтом не затрагиваются. По умолчанию гейт выключен — включается платформой.\n\n### BC-0704-3: ключи в режиме «только чтение» (READONLY) больше не выполняют запись на рукописных эндпоинтах\n\n> Поддержка старого формата до: 03.07.2026\n\n**Было**\n\nAPP-ключ с режимом доступа «только чтение» (`accessMode: READONLY`) доходил до записи в Битрикс24 на части рукописных эндпоинтов (реквизиты и пресеты, пользовательские поля, timeline pin\u002Fnote\u002Fbind, телефония, почта, диск, бизнес-процессы, приглашение и деактивация пользователя и другие) — гард проверял только скоуп, но не режим доступа ключа.\n\n**Стало**\n\nЛюбая попытка записи ключом в режиме «только чтение» возвращает `403` с кодом `WRITE_BLOCKED_READONLY_KEY`. Эндпоинты чтения не затронуты.\n\n**Что делать интеграторам**\n\nЕсли интеграция выполняла запись ключом «только чтение», переключите ключ в режим чтения и записи на странице управления ключами.\n\n### FIX-0704-4: деплой galaxy-приложения корректно распаковывает архивы с обёрткой и понятно сообщает о пустом source.content\n\n**Было**\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) для galaxy-приложения (`kind=GALAXY_APP`), где файлы проекта в `source.content` лежали внутри обёрточной папки (типичный zip, собранный в macOS), собирал приложение с пустым контекстом сборки и падал в рантайме с `npm error enoent Could not read package.json`. Если же `source.content` вообще не распаковывался в файлы (передан versionId, путь или пустой архив) — деплой давал ту же непонятную ошибку сборки.\n\n**Стало**\n\nТакие архивы деплоятся корректно: файлы приложения оказываются в корне контекста сборки. А если `source.content` распаковался в пустой контекст, деплой сразу возвращает `GALAXY_APP_BUILD_FAILED` с понятным сообщением о пустом контексте сборки и подсказкой, что `source.content` должен быть base64-архивом (tar.gz или zip) файлов вашего проекта, а не versionId и не путём.\n\n## 2026-07-03\n\n### FIX-0703-1: привязка размещения по ключу разработчика больше не возвращает 500 после цикла снятия и повторной публикации\n\n**Было**\n\n`POST \u002Fv1\u002Fplacements\u002Fbind` для приложения, управляемого ключом разработчика (тип `local.*`), мог стабильно возвращать `500` (`BITRIX_UNAVAILABLE`, `INTERNAL_SERVER_ERROR`) при привязке размещения (например `CRM_DEAL_DETAIL_TAB` или `CRM_CONTACT_DETAIL_TAB`) после того, как приложение снимали с публикации и публиковали заново. Прежняя регистрация размещения на стороне Битрикс24 сохранялась, и попытка зарегистрировать поверх неё новую завершалась внутренней ошибкой. Повторные вызовы давали ту же ошибку.\n\n**Стало**\n\nПеред регистрацией размещения запрос сначала снимает его прежнюю регистрацию на стороне Битрикс24, поэтому привязка проходит успешно и после цикла снятия с публикации и повторной публикации. Менять вызов не нужно.\n\n### FIX-0703-2: \u002Fv1\u002Fsites — фильтр по типу «база знаний» (KNOWLEDGE) и «группа» (GROUP) больше не игнорируется\n\n**Было**\n\n`POST \u002Fv1\u002Fsites\u002Fsearch` (а также `GET \u002Fv1\u002Fsites` и `POST \u002Fv1\u002Fsites\u002Faggregate`) с `filter[type]=KNOWLEDGE` или `filter[type]=GROUP` молча возвращал обычные сайты-лендинги (`PAGE` \u002F `STORE` \u002F `VIBE`) вместо баз знаний или страниц групп. Причина на стороне Битрикс24: метод `landing.site.getList` привязывает фильтр по `TYPE` к внутренней области (`scope`), и без параметра `scope` типы `KNOWLEDGE` \u002F `GROUP` не входят в область по умолчанию — фильтр по типу тихо отбрасывался. Обойти можно было только вручную, добавив `scope` (см. [Список сайтов](\u002Fdocs\u002Fentities\u002Fsites\u002Flist)).\n\n**Стало**\n\nЕсли в фильтре указан один такой тип и `scope` не передан явно, Вайбкод сам подставляет соответствующую область (`type=KNOWLEDGE` → `scope=KNOWLEDGE`, `type=GROUP` → `scope=GROUP`) — и запрос возвращает именно базы знаний \u002F страницы групп. Явно переданный `scope` всегда в приоритете и не переопределяется. Если тип задан списком или оператором (например `{\"type\":{\"$in\":[\"KNOWLEDGE\",\"PAGE\"]}}`), где одну область выбрать нельзя, в `meta.warnings` приходит подсказка с кодом `TYPE_REQUIRES_SCOPE`.\n\n**Влияние на интеграторов**\n\nДействий не требуется. Запросы с `filter[type]=PAGE` \u002F `STORE` \u002F `VIBE` и запросы без фильтра по типу работают как раньше. `MAINPAGE` — это область, а не тип сайта (её сайты имеют тип `VIBE`), поэтому из фильтра по типу область `MAINPAGE` не выводится. Для `\u002Fv1\u002Fpages` поведение не изменилось.\n\n### FIX-0703-3: агрегация страниц и сайтов снова возвращает count\n\n**Было**\n\n[POST \u002Fv1\u002Fpages\u002Faggregate](\u002Fdocs\u002Fentities\u002Fpages\u002Faggregate) и [POST \u002Fv1\u002Fsites\u002Faggregate](\u002Fdocs\u002Fentities\u002Fsites\u002Faggregate) возвращали `count: 0` и `meta.totalRecords: 0` даже при наличии страниц и сайтов — во всех формах: без фильтра, с фильтром, с выражением `count` и как верхний `count` при `groupBy`. Счётчики отдельных групп при `groupBy` при этом были корректными.\n\n**Стало**\n\n`count` и `meta.totalRecords` отражают фактическое число записей; верхний `count` при `groupBy` равен сумме счётчиков групп.\n\n**Влияние на интеграторов**\n\nДействий не требуется — ответ стал корректным.\n\n### NEW-0703-4: Параметры качества и таймстампов в расшифровке аудио\n\nРасшифровка аудио [POST \u002Fv1\u002Faudio\u002Ftranscriptions](\u002Fdocs\u002Fai\u002Faudio\u002Ftranscriptions) принимает пять новых необязательных полей. Качество распознавания: `prompt` — контекстная подсказка (тема разговора, стиль, правильное написание терминов, до 2000 символов), `hotwords` — список спец-слов через запятую (редкие термины, бренды, имена, до 500 символов), `vad_filter` — фильтр тишины перед распознаванием (меньше галлюцинаций на записях с паузами). Управление результатом: `temperature` — температура декодера от 0 до 1, `timestamp_granularities[]` — детализация таймстампов `word`\u002F`segment` (только с `response_format=verbose_json`; со значением `word` каждый сегмент дополняется массивом `words` с таймингом и вероятностью каждого слова). Поля передаются в `multipart\u002Fform-data` рядом с `file` и совместимы с OpenAI-контрактом. Невалидные значения отклоняются кодами `invalid_prompt`, `invalid_hotwords`, `invalid_temperature`, `invalid_vad_filter`, `invalid_timestamp_granularities`.\n\n### FIX-0703-5: Приложения, созданные через API, корректно открываются как плейсменты\n\n**Было**\n\nЧасть приложений, созданных через [POST \u002Fv1\u002Fapps](\u002Fdocs\u002Fapps\u002Fcreate), не открывалась при вызове плейсмента в Битрикс24 — вместо интерфейса приложения пользователь видел ошибку распознавания приложения.\n\n**Стало**\n\nСозданные приложения корректно резолвятся и открываются как плейсмент-виджеты в Битрикс24. Ответ создания не изменился — приложение сразу пригодно для публикации и привязки плейсментов.\n\n**Влияние на интеграторов**\n\nНичего менять не нужно. Ранее не открывавшиеся приложения нужно пересоздать (удалить и создать заново) — новое приложение открывается корректно.\n\n### NEW-0703-6: удаление ключа блокируется при привязанном агенте или боте\n\n[DELETE \u002Fv1\u002Fkeys\u002F:id](\u002Fdocs\u002Fkeys-auth) теперь возвращает `409` с кодом `KEY_HAS_LINKED_AGENT`, если ключ является управляющим ключом живого AI-агента или управляемого бота.\n\n**Было**\n\nУдаление такого ключа осиротляло агента и каскадно удаляло бота вместе с его токеном — идентичность бота в Bitrix24 терялась безвозвратно, без предупреждения.\n\n**Стало**\n\nТело ответа: `{ success: false, error: { code: \"KEY_HAS_LINKED_AGENT\", message, details: { linkedAgentCount, linkedBotCount, agents: [{ id, name, status }] } } }`. Перед удалением перепривяжите ресурсы к другому ключу либо удалите сам агент\u002Fбот; для восстановления доступа осиротевшему агенту используйте кабинетное действие «Восстановить доступ». Проверка идёт до синхронизации с Bitrix24 — при `409` учётные данные на стороне Bitrix24 не затрагиваются. Сиблинг существующего `KEY_HAS_ACTIVE_SERVERS`.\n\n### NEW-0703-7: История изменений задачи и стадии канбана\n\nДобавлены два метода только для чтения (скоуп `task`). `GET \u002Fv1\u002Ftasks\u002F:taskId\u002Fhistory` возвращает историю изменений задачи целиком за один вызов: смены стадий канбана, перемещения в спринт и бэклог, статусы и другие события. Фильтр по типу события — `?field=STAGE` (несколько типов через запятую, например `?field=STAGE,MOVE_TO_SPRINT`); сортировка — `?order=asc` или `?order=desc` (по умолчанию по возрастанию даты создания). Каждая запись содержит `id`, `createdDate`, `field`, объект `value` с прежним и новым значением и `user` с идентификатором автора. `GET \u002Fv1\u002Ftasks\u002Fstages\u002F:entityId` возвращает текущие колонки канбана рабочей группы (`N`) или личного плана (`0`).\n\n### FIX-0703-8: bizproc-activities: понятная ошибка вместо «Wrong handler URL» при отсутствии handler\n\n**Было**\n\nPOST \u002Fv1\u002Fbizproc-activities без поля `handler` (или с URL обработчика, ошибочно переданным в поле `handlerUrl`) возвращал непрозрачную ядровую ошибку `422 BITRIX_ERROR: Wrong handler URL`.\n\n**Стало**\n\nПоля `code`, `name`, `handler` проверяются до вызова Битрикс24: при отсутствии `handler` метод возвращает `400 MISSING_REQUIRED_FIELDS` с сообщением `Body field \"handler\" is required to create bizprocActivity`, подсказывая правильное имя поля. Успешные вызовы с корректным полем `handler` не затронуты.\n\n### FIX-0703-9: POST \u002Fv1\u002Fbatch — ответы update и delete теперь нормализованы, как create и get\n\n**Было**\n\nВ глобальном пакетном вызове [POST \u002Fv1\u002Fbatch](\u002Fdocs\u002Fbatch) подвызов update возвращал ответ в сырой обёртке (вложенный объект вместо плоской записи), а подвызов delete возвращал пустой массив без признака успеха. Это расходилось с create и get в том же эндпоинте и с одиночным PATCH \u002Fv1\u002F{entity}\u002F:id, которые отдают плоскую нормализованную запись.\n\n**Стало**\n\nПодвызов update возвращает плоскую нормализованную запись (camelCase-поля) — как create, get и одиночный PATCH. Подвызов delete возвращает признак успеха вида { id, deleted: true }.\n\n### FIX-0703-10: POST \u002Fv1\u002F{entity}\u002Fbatch — create, update и delete снова работают для сущностей CRM\n\n**Было**\n\nПо-сущностный пакетный вызов [POST \u002Fv1\u002F{entity}\u002Fbatch](\u002Fdocs\u002Fbatch) с действием create, update или delete для сделок, контактов, компаний, лидов, предложений и счетов возвращал по каждому элементу ошибку «Could not find value for parameter {entityTypeId}», и запись не создавалась, не менялась и не удалялась. Одиночные вызовы (POST \u002Fv1\u002F{entity}) и глобальный POST \u002Fv1\u002Fbatch на тех же сущностях при этом работали.\n\n**Стало**\n\nПо-сущностный пакетный create, update и delete для этих сущностей выполняется корректно — так же, как одиночные вызовы и глобальный пакетный эндпоинт.\n\n### FIX-0703-11: Пропуск поля model в чате снова подставляет модель по умолчанию\n\n**Было**\n\n[POST \u002Fv1\u002Fchat\u002Fcompletions](\u002Fdocs\u002Fai\u002Fchat\u002Fcompletions) без поля `model` возвращал `400 no_default_model` на порталах, где модель по умолчанию не была явно назначена, — даже когда на портале была доступная бесплатная модель. При этом `GET \u002Fv1\u002Fme` мог показывать в `defaultModel` модель, которую нельзя вызвать.\n\n**Стало**\n\nЕсли поле `model` не передано, запрос автоматически берёт первую доступную для вызова модель портала — как и описано в документации. `GET \u002Fv1\u002Fme` в поле `defaultModel` теперь всегда показывает вызываемую модель, ту же самую, которую подставит чат.\n\n### FIX-0703-12: include по связанным сущностям снова возвращает сами сущности, а не null\n\n**Было**\n\n`GET \u002Fv1\u002Fdeals\u002F:id?include=contact,company` возвращал `_included.company` = `null`, а `_included.contacts` — только метаданные связи (`sort`\u002F`isPrimary`\u002F`roleId`) без полей самой сущности, хотя сделка ссылалась на существующие компанию и контакты.\n\n**Стало**\n\n`_included.company` содержит полный объект компании, а `_included.contacts` — полные объекты контактов (`id`, `name`, …) вместе с метаданными связи. Исправление затрагивает `include` по всем CRM-сущностям (`deals`, `leads`, `quotes` и другим).\n\n### FIX-0703-13: поиск сделок отклоняет неизвестное поле фильтра вместо тихой отдачи всех строк\n\n**Было**\n\n[GET \u002Fv1\u002Fdeals](\u002Fdocs\u002Fentities\u002Fdeals\u002Flist) и [POST \u002Fv1\u002Fdeals\u002Fsearch](\u002Fdocs\u002Fentities\u002Fdeals\u002Fsearch) с неизвестным полем фильтра (опечатка в имени, поле не из схемы) молча пропускали его в Битрикс24, который игнорирует незнакомые ключи фильтра и возвращает весь набор сделок с ответом `200`. Клиент, отправивший фильтр с ошибкой в имени поля, получал не пустой результат и не `400`, а полную таблицу — как будто фильтр применился.\n\n**Стало**\n\nНеизвестное поле фильтра теперь отклоняется до вызова Битрикс24 ответом `400` с кодом `UNKNOWN_FILTER_FIELD` и списком доступных полей в сообщении — как уже делают `contacts`, `companies`, `leads`, `quotes`, `invoices` и `items`. Объявленные поля (включая псевдонимы вроде `amount`), пользовательские поля (`UF_CRM_*` и `ufCrm*`) и `id` работают как прежде.\n\n### FIX-0703-14: Каталог: список и поиск отдают чистый 400 при отсутствии iblockId\n\n**Было**\n\nСписок и поиск [GET \u002Fv1\u002Fcatalog-products](\u002Fdocs\u002Fentities\u002Fcatalog-products\u002Flist), [POST \u002Fv1\u002Fcatalog-products\u002Fsearch](\u002Fdocs\u002Fentities\u002Fcatalog-products\u002Fsearch), [GET \u002Fv1\u002Fcatalog-sections](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) и [POST \u002Fv1\u002Fcatalog-sections\u002Fsearch](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Fsearch) без `iblockId` в фильтре доходили до Битрикс24 и возвращали мутный `422 BITRIX_ERROR` («Field iblockId is not specified in the filter»).\n\n**Стало**\n\nКаталожные список и поиск требуют `iblockId` в фильтре — при отсутствии сразу возвращается `400 MISSING_REQUIRED_FILTER` с примером, запрос до Битрикс24 не доходит.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно — корректные запросы (с `filter[iblockId]`) работают как прежде. Изменились только код и ясность ошибки для запросов, которые и так не выполнялись.\n\n### FIX-0703-15: Контакты — реальные поля дат createdTime\u002FupdatedTime вместо фантомных createdAt\u002FupdatedAt\n\n**Было**\n\n[GET \u002Fv1\u002Fcontacts\u002Ffields](\u002Fdocs\u002Fentities\u002Fcontacts\u002Ffields) объявлял поля `createdAt` и `updatedAt`, но в ответах контактов они никогда не появлялись — Битрикс24 отдаёт даты под ключами `createdTime`\u002F`updatedTime`, и именно они были в теле контакта. При этом фильтр и `select` по `createdTime` (имя, которое клиент реально видит в ответе) отклонялись как неизвестное поле, а по фантомному `createdAt` — «работали», хотя само поле в ответе не читалось.\n\n**Стало**\n\nСхема объявляет реальные ключи `createdTime` и `updatedTime` (тип datetime, только чтение): они присутствуют в `\u002Ffields`, фильтр и `select` по ним работают, а значение нормализуется к ISO-8601 в UTC. Фантомные `createdAt`\u002F`updatedAt` больше не объявлены — фильтр или `select` по ним возвращает `400 UNKNOWN_FILTER_FIELD`.\n\n**Влияние на интеграторов**\n\nЧтение не меняется — ключи `createdTime`\u002F`updatedTime` и раньше были в теле ответа, теперь ещё и нормализованы. Если вы фильтровали или проецировали контакты по `createdAt`\u002F`updatedAt`, замените имена на `createdTime`\u002F`updatedTime`.\n\n### FIX-0703-16: Список открытых линий теперь учитывает параметр limit\n\n**Было**\n\n[GET \u002Fv1\u002Fopenline-configs](\u002Fdocs\u002Fopenlines\u002Fconfig\u002Flist) и одноимённый поиск игнорировали `limit`: нижележащий метод Битрикс24 возвращает весь набор конфигураций, а обёртка отдавала все строки. Поле `hasMore` при этом было неверным — `false`, даже когда за пределами запрошенного `limit` оставались ещё записи.\n\n**Стало**\n\nОтвет обрезается до `limit` на стороне обёртки. `hasMore: true`, когда Битрикс24 вернул больше записей, чем запрошенный `limit` (есть следующая страница), иначе `false`. `total` — число записей в текущем окне.\n\n**Влияние на интеграторов**\n\nОтвет на запрос с `limit` теперь содержит не больше `limit` записей. Клиенты, полагавшиеся на возврат всего набора без учёта `limit`, увидят усечённый список — используйте `offset` для следующей страницы.\n\n## 2026-07-02\n\n### NEW-0702-1: фильтр и сортировка задач по реальному статусу (realStatus)\n\n`GET \u002Fv1\u002Ftasks`, `POST \u002Fv1\u002Ftasks\u002Fsearch` и `POST \u002Fv1\u002Ftasks\u002Faggregate` теперь принимают поле `realStatus` в `filter` (а список и поиск — ещё и в `sort`) — фильтрация по фактически сохранённому статусу задачи: 1 — новая, 2 — ждёт выполнения, 3 — выполняется, 4 — ожидает контроля, 5 — завершена, 6 — отложена, 7 — отклонена. Раньше `filter[realStatus]` молча игнорировался и запрос возвращал весь набор.\n\nВ отличие от `filter[status]`, который на стороне Bitrix24 работает как виртуальный (мета-)фильтр (значения −1 просрочена, −2 не просмотрена, −3 почти просрочена) и не совпадает со значением поля `status` в ответе, `realStatus` фильтрует именно по хранимому статусу. Поле доступно только для чтения (статус меняется через `status`) и участвует только в `filter`\u002F`sort` — в ответе реальный статус задачи уже отдаётся в поле `status`.\n\n### FIX-0702-2: создание приложения переиспользует упавший одноимённый слот\n\n**Было**\n\nПовторный [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) с тем же `name` после неудачного деплоя создавал новый слот приложения. Упавшие слоты накапливались и удалялись автоочисткой только через 7 дней.\n\n**Стало**\n\nЕсли у владельца ключа на портале уже есть слот с тем же `name` в статусе `error` (или созданный, но так и не получивший ни одного деплоя), повторный вызов возвращает этот же слот: его `id` сохраняется, ошибка и лог сборки сбрасываются, статус возвращается в `provisioning` — деплойте в него. Слоты, в которые ни разу не отправляли код, теперь удаляются автоочисткой через 24 часа вместо 7 дней (слоты с упавшей сборкой по-прежнему хранятся 7 дней вместе с логом сборки).\n\n**Влияние на интеграторов**\n\nИзменений в запросах не требуется. Если ваш сценарий пересоздавал слот с тем же именем после ошибки, вы начнёте получать прежний `id` вместо нового — это ожидаемо: деплой в возвращённый слот работает как обычно. Слоты других пользователей портала и работающие приложения под переиспользование не попадают.\n\n### FIX-0702-3: ключи «только чтение» больше не пишут через \u002Fv1\u002Fbots\n\n**Было**\n\nКлюч API в режиме «только чтение» (`accessMode: READONLY`) мог выполнять операции записи через эндпоинты бота ([POST \u002Fv1\u002Fbots](\u002Fdocs\u002Fbots\u002Fmanagement\u002Fcreate), отправка и удаление сообщений, добавление участников в чат, регистрация и удаление бота и другие) — вызов возвращал 200 вместо 403. Остальные проксирующие поверхности Битрикс24 такие записи уже блокировали.\n\n**Стало**\n\nЗапись через `\u002Fv1\u002Fbots\u002F*` ключом «только чтение» возвращает 403 с кодом `WRITE_BLOCKED_READONLY_KEY`. Операции чтения не затронуты, включая получение контекста сообщения ([GET \u002Fv1\u002Fbots\u002F:botId\u002Fmessages\u002F:messageId\u002Fcontext](\u002Fdocs\u002Fbots\u002Fmessages\u002Fcontext)) и скачивание файла ([GET \u002Fv1\u002Fbots\u002F:botId\u002Ffiles\u002F:fileId](\u002Fdocs\u002Fbots\u002Ffiles\u002Fdownload)).\n\n**Влияние на интеграторов**\n\nЕсли бот-интеграции нужна запись — переключите ключ в режим «чтение и запись» в разделе \u002Fkeys.\n\n### FIX-0702-4: include=storage у папок теперь резолвится\n\n**Было**\n\n`GET \u002Fv1\u002Ffolders\u002F:id?include=storage` (и список `GET \u002Fv1\u002Ffolders?parentId=...&include=storage`) не добавляли `_included` в ответ, хотя `GET \u002Fv1\u002Ffolders\u002Ffields` объявляет для связи `storage` признак `includable: true`.\n\n**Стало**\n\nСвязанное хранилище резолвится: в ответе появляется `_included.storage` с карточкой хранилища, найденной по `storageId`. Связь описана в [GET \u002Fv1\u002Ffolders\u002Ffields](\u002Fdocs\u002Fentities\u002Ffolders\u002Ffields).\n\n### FIX-0702-5: PAGE_BACKGROUND_WORKER: привязка больше не падает с 500\n\n**Было**\n\n`POST \u002Fv1\u002Fplacements\u002Fbind` для placement `PAGE_BACKGROUND_WORKER` подставлял обязательный для Битрикс24 параметр `options.errorHandlerUrl` только когда вызов шёл через OAuth-сессию. Если приложение привязывалось по ключу разработчика или на коробочном портале, параметр не добавлялся и Битрикс24 отвечал 500 (`BITRIX_UNAVAILABLE`, «Field errorHandlerUrl is empty»), хотя остальные placement привязывались нормально.\n\n**Стало**\n\nДля `PAGE_BACKGROUND_WORKER` значение `options.errorHandlerUrl` по умолчанию подставляется равным `handler` независимо от способа привязки. Явно переданный `options.errorHandlerUrl` по-прежнему имеет приоритет. В ответе поле `options` теперь отражает применённое значение (с подставленным `errorHandlerUrl`).\n\n**Влияние на интеграторов**\n\nДействий не требуется — вызов, который раньше возвращал 500, теперь проходит.\n\n### FIX-0702-6: POST \u002Fsearch сообщает об отсутствии обязательных параметров чистой ошибкой\n\n**Было**\n\n`POST \u002Fv1\u002F{entity}\u002Fsearch` для сущностей, чей метод списка Битрикс24 требует обязательные параметры, при их отсутствии не проверял этого и передавал запрос в Битрикс24 как есть. Наружу утекала сырая ошибка Битрикс24 (`BITRIX_ERROR`, например «Invalid value of parameter [ `$id` ]» или «Не задан обязательный параметр `type`»), тогда как у эквивалентного `GET`-списка та же ситуация давала понятный `400 MISSING_REQUIRED_PARAMS`. Затрагивало [POST \u002Fv1\u002Fcalendar-events\u002Fsearch](\u002Fdocs\u002Fentities\u002Fcalendar-events\u002Fsearch) (нужен `type`), [POST \u002Fv1\u002Ffiles\u002Fsearch](\u002Fdocs\u002Fentities\u002Ffiles) (нужен `folderId`) и [POST \u002Fv1\u002Ffolders\u002Fsearch](\u002Fdocs\u002Fentities\u002Ffolders) (нужен `parentId`).\n\n**Стало**\n\n`POST \u002Fv1\u002F{entity}\u002Fsearch` проверяет обязательные параметры до вызова Битрикс24 — так же, как это давно делает `GET`-список. При отсутствии параметра приходит `400` с кодом `MISSING_REQUIRED_PARAMS` и перечнем недостающих полей, без обращения к Битрикс24. Обязательный параметр можно передать в `filter`, а параметр-родитель (`folderId` для файлов, `parentId` для папок) — также на верхнем уровне тела запроса.\n\n### NEW-0702-7: Цена research в самоописании ключа и сумма к пополнению в ответе 402\n\n[GET \u002Fv1\u002Fme](\u002Fdocs\u002Fkeys-auth) теперь отдаёт `cost` у каждого провайдера в блоке `webResearch.providers[]` — по образцу блока `webSearch`. Поле несёт цену режима research в Ꝟ (`cost.research`) и валюту (`cost.currency`), так что агент видит стоимость глубокого поиска прямо в самоописании ключа, без отдельного вызова.\n\nОтвет `402` при недостатке средств (`INSUFFICIENT_BALANCE`, а также `BILLING_FROZEN`) на [POST \u002Fv1\u002Fsearch](\u002Fdocs\u002Fsearch\u002Frun) и [POST \u002Fv1\u002Fresearch](\u002Fdocs\u002Fsearch\u002Fresearch) теперь содержит поле `required` — сумму в Ꝟ, необходимую для запроса. Прежние поля `userMessage` и `hint` не изменились.\n\nКлиентам со строгой валидацией схемы по `additionalProperties` нужно учесть новые поля ответа.\n\n### NEW-0702-8: POST \u002Fv1\u002Ftriggers\u002Ffire поддерживает счета (SmartInvoice)\n\nЭндпоинт [POST \u002Fv1\u002Ftriggers\u002Ffire](\u002Fdocs\u002Fautomation\u002Ftriggers\u002Ffire) принимает новое значение `entityType` — `invoice`. Передайте `entityType: \"invoice\"` и `entityId` счёта (его выдаёт [GET \u002Fv1\u002Finvoices](\u002Fdocs\u002Fentities\u002Finvoices)), чтобы запустить триггер автоматизации по смарт-счёту. Прежние значения (`deal`, `lead`, `contact`, `company`, `quote`, `item`) работают как раньше.\n\nРаньше запустить триггер по счёту было нельзя, а попытка через `entityType=\"item\"` с `entityTypeId=31` отклонялась сообщением, которое уводило в тупик. Теперь `item` с зарезервированным `entityTypeId` (в том числе 31) подсказывает перейти на соответствующий `entityType` — для счёта это `invoice`.\n\n### NEW-0702-9: GET \u002Fv1\u002Fai\u002Fusage отдаёт длительность аудио по моделям транскрибации\n\n[GET \u002Fv1\u002Fai\u002Fusage](\u002Fdocs\u002Fai\u002Fconsumption\u002Fusage) в блоке `byModel[]` теперь возвращает поле `audioSeconds` — суммарное количество секунд аудио, переданных на транскрибацию по каждой модели за выбранный период. Поле заполняется для вызовов speech-to-text (Whisper) и равно `0` для текстовых моделей, где длительность аудио неприменима.\n\nПоле аддитивное, существующие интеграции продолжают работать без изменений.\n\n### BC-0702-10: categories: code и isDefault помечены read-only (были phantom-writable)\n\n> Поддержка старого формата до: 01.10.2026\n\n**Было**\n\n`GET \u002Fv1\u002Fcategories\u002F:entityTypeId\u002Ffields` объявлял `code` и `isDefault` записываемыми (`readonly: false`), но запись этих полей в `crm.category.add`\u002F`update` молча игнорировалась (значения не сохранялись, ответ `200`).\n\n**Стало**\n\nОба поля помечены `readonly: true`. `\u002Ffields` теперь честно показывает их как read-only, а попытка записать `code` или `isDefault` возвращает `400 READONLY_FIELD` вместо тихой потери данных.\n\n**Что делать интеграторам**\n\nРаньше передача `code`\u002F`isDefault` в теле `POST`\u002F`PATCH` `\u002Fv1\u002Fcategories\u002F:entityTypeId` принималась (`200`, значения молча игнорировались). Теперь такой запрос возвращает `400 READONLY_FIELD`. Уберите `code` и `isDefault` из тела запросов create\u002Fupdate категорий — на запись эти поля больше не принимаются.\n\n### BC-0702-11: telephony-lines: поле crmAutoCreate нормализовано в boolean и появилось в \u002Ffields\n\n> Поддержка старого формата до: 01.10.2026\n\n**Было**\n\n`GET \u002Fv1\u002Ftelephony-lines\u002Ffields` отдавал только `number`, `serverName`, `name`. Поле автосоздания CRM протекало в ответах списка сырым UPPER-именем `CRM_AUTO_CREATE` строкой `\"Y\"`\u002F`\"N\"` — единственное UPPER-поле среди camelCase, и его не было в `\u002Ffields`. На запись camelCase `crmAutoCreate` молча отбрасывался.\n\n**Стало**\n\nПоле объявлено как `crmAutoCreate` (boolean). Теперь оно присутствует в `\u002Ffields`, в ответах `list` приходит нормализованным (`true`\u002F`false`) вместо сырого `\"Y\"`\u002F`\"N\"`, а на `create`\u002F`update` принимается camelCase boolean (сырое UPPER-имя ещё принимается на запись для совместимости). Клиенты, читавшие `data[].CRM_AUTO_CREATE`, должны перейти на `data[].crmAutoCreate` (boolean).\n\n### NEW-0702-12: workgroups: раскрыта операция aggregate и groupBy по полям\n\n**Было**\n\n`POST \u002Fv1\u002Fworkgroups\u002Faggregate` работал, но нигде не был заявлен: операции не было в машинном индексе `\u002Fv1\u002Fguide`, а `groupBy` возвращал `400` на любом поле (`Available: .`), потому что список агрегируемых полей был пуст.\n\n**Стало**\n\nОбъявлен список агрегируемых полей: `membersCount` (числовые sum\u002Favg\u002Fmin\u002Fmax) плюс категориальные `active`, `isProject`, `ownerId` для группировок. Теперь операция видна в `\u002Fv1\u002Fguide` и `\u002Ffields`, а `groupBy` по этим полям работает.\n\n### FIX-0702-13: \u002Ffields: полнота метаданных у doc-templates и bookings\n\n**Было**\n\n`GET \u002Fv1\u002Fdoc-templates\u002Ffields` не содержал полей `isDefault` и `productsTableVariant`, хотя они приходят в ответах списка. У `GET \u002Fv1\u002Fbookings\u002Ffields` обязательные `resourceIds` и `datePeriod` не были помечены `required`, поэтому их обязательность не была видна в схеме.\n\n**Стало**\n\n`doc-templates`: объявлены `isDefault` и `productsTableVariant` (только чтение) — теперь состав `\u002Ffields` совпадает с ответами. `bookings`: `resourceIds` и `datePeriod` помечены `required: true`, обязательность видна в `\u002Ffields`.\n\n### FIX-0702-14: orders: \u002Ffields синхронизирован с ответом, убран псевдо-ключ order, companyId фильтруется\n\n**Было**\n\n`GET \u002Fv1\u002Forders\u002Ffields` содержал лишний псевдо-ключ `order` (артефакт разбора `sale.order.getFields`) и не содержал полей, реально приходящих в ответах: `companyId`, `clients`, `dateMarked`, `personTypeXmlId`, `statusXmlId`, `version`. Из-за отсутствия `companyId` в схеме фильтр по нему падал с `UNKNOWN_FILTER_FIELD`.\n\n**Стало**\n\nСостав `\u002Ffields` теперь собирается из схемы: псевдо-ключ `order` убран, объявлены шесть недостающих полей (`companyId` — записываемое число; `clients` — объект, только чтение, приходит в `get`; `dateMarked`\u002F`personTypeXmlId`\u002F`statusXmlId`\u002F`version` — только чтение). Фильтр и сортировка по `companyId` теперь работают.\n\n### FIX-0702-15: users: limit > 50 теперь работает, meta.hasMore честный\n\n**Было**\n\n`GET \u002Fv1\u002Fusers?limit=500` возвращал только 50 записей, хотя `meta.total` показывал больше. `meta.hasMore` всегда был `false` — документированная пагинация по `hasMore` молча теряла данные за первой страницей.\n\n**Стало**\n\nСущность опирается на легаси-метод `user.get` (без суффикса `.list`), поэтому авто-паджинатор не включался. Добавлен флаг `paginateViaStart` (как у отделов): при `limit > 50` идёт многостраничная загрузка через `start`, а `meta.hasMore` отражает реальное наличие следующих записей.\n\n### FIX-0702-16: POST \u002Fv1\u002Fbatch отклоняет отключённые операции записи и прокидывает обязательные параметры списка\n\n**Было**\n\nГлобальный [POST \u002Fv1\u002Fbatch](\u002Fdocs\u002Fbatch) с действием create, update или delete для сущности, у которой эта операция отключена (например openline-configs — запись вынесена в отдельные роуты), выполнял вызов напрямую в Bitrix24 в обход нормализации и мог тихо создать или изменить запись. Отдельно: действие list или search для сущности с обязательными параметрами метода (например calendar-events — type и ownerId) возвращало AUTO_PAGINATION_FAILED «missing required parameter», хотя прямой запрос списка с теми же параметрами работал. Кроме того, batch-list для folders и files отправлял родительскую папку под именем parentId или folderId, которое метод disk.folder.getchildren игнорирует, поэтому список тихо возвращался не по той папке; а batch-list для calendar-events с лишним ключом filter молча прокидывал его в Bitrix24, и метод возвращал весь календарь без ошибки.\n\n**Стало**\n\nОтключённая операция записи в под-вызове отклоняется с кодом ACTION_NOT_SUPPORTED до обращения к Bitrix24 — так же, как в POST \u002Fv1\u002F{entity}\u002Fbatch. Обязательные параметры списка и параметры верхнего уровня метода прокидываются в Bitrix24 с исходными именами, поэтому batch-list работает так же, как прямой список, а при их отсутствии возвращается понятный MISSING_REQUIRED_PARAMS вместо сырой ошибки Bitrix24. Для folders и files родительская папка приводится к имени id, которого ждёт метод, поэтому batch-list возвращается по нужной папке. Для сущностей, у метода которых нет конверта filter (calendar-events), лишний ключ filter теперь отклоняется с кодом UNSUPPORTED_FILTER до обращения к Bitrix24 — так же, как в прямом списке.\n\n**Влияние на интеграторов**\n\nНичего менять не нужно. Если под-вызов batch раньше опирался на выполнение отключённой операции записи — переведите его на выделенный роут сущности. Для batch-list по сущностям с обязательными параметрами (calendar-events) передавайте type и ownerId в params под-вызова. Если batch-list для calendar-events использовал filter — уберите его или перенесите в параметры верхнего уровня, иначе под-вызов вернёт UNSUPPORTED_FILTER.\n\n### NEW-0702-17: GET \u002F:entity\u002Ffields отдаёт label и description полей\n\nОтвет `GET \u002Fv1\u002F{entity}\u002Ffields` теперь по каждому полю может нести человекочитаемое короткое имя `label` и пояснение `description` — раньше поле описывалось только парой `{type, readonly}`. Это позволяет ИИ-агенту и интерфейсу показывать название и назначение поля, не обращаясь к документации. Тексты локализованы по сегменту: русские на `.tech`, английские на `.com`. Поля добавлены для сущностей: Отделы, Смарт-процессы, Хранилища, Папки, Файлы, Рабочие группы, Шаблоны документов, Бронирования, События календаря, Задачи, Реквизиты, Сотрудники. Для Сотрудников дополнительно исправлена подстановка подписей: `GET \u002Fv1\u002Fusers\u002Ffields` теперь возвращает настоящие названия полей Битрикс24 из `user.fields` вместо технических кодов. Изменение аддитивное: новые ключи появляются дополнительно к прежним, существующие вызовы продолжают работать без изменений.\n\n### FIX-0702-18: ключ только со скоупами vibe:* теперь выписывается, а не падает\n\n**Было**\n\nСоздание ключа авторизации `POST \u002Fv1\u002Fkeys`, у которого все запрошенные права — внутренние права Вайбкода `vibe:*` (например только `vibe:infra`), на портале с режимом dev-key (коробочная Битрикс24 или подключённый облачный портал) отклонялось с `502 DEVKEY_MINT_FAILED`. Права `vibe:*` не передаются в Битрикс24, поэтому набор прав для вебхука Битрикс24 оказывался пустым, и Битрикс24 отклонял выписку, требуя хотя бы одно право.\n\n**Стало**\n\nКлюч только с правами `vibe:*` теперь выписывается успешно. Вебхук Битрикс24 для него не создаётся — он не нужен, такой ключ не обращается к REST Битрикс24, — а ключ работает с внутренними возможностями Вайбкода по своим правам.\n\n**Влияние на интеграторов**\n\nДействий не требуется: прежде падавший запрос теперь возвращает созданный ключ.\n\n### FIX-0702-19: пагинация \u002Fv1\u002Fstorages — limit больше 50 отдаёт все записи, meta.hasMore корректен\n\n**Было**\n\n`GET \u002Fv1\u002Fstorages` с `limit` больше 50 возвращал максимум 50 записей, а `meta.hasMore` был всегда `false` — даже когда в портале записей больше. Клиент с `?limit=50` при 489 хранилищах видел `hasMore: false` и не знал, что нужно запросить следующую страницу. То же на `POST \u002Fv1\u002Fstorages\u002Fsearch` и в `\u002Fv1\u002Fbatch`.\n\n**Стало**\n\n`limit` больше 50 проходит авто-пагинацию (как у остальных списков) и отдаёт запрошенное число записей, а `meta.hasMore` равен `(offset + число записей) \u003C meta.total` и для `limit` не больше 50. Изменение распространяется на `GET \u002Fv1\u002Fstorages`, `POST \u002Fv1\u002Fstorages\u002Fsearch` и список `storages` в `\u002Fv1\u002Fbatch`.\n\nПримечание: корректный расчёт `meta.hasMore` для ответов списка при `limit` не больше 50 теперь распространяется на все сущности (`GET \u002Fv1\u002F{entity}` и `POST \u002Fv1\u002F{entity}\u002Fsearch`), а не только на `storages` — ранее на этом пути `meta.hasMore` был всегда `false`.\n\n### FIX-0702-20: infra: кириллица в displayName при создании приложения\n\n**Было**\n\nПри [POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra) с кириллическим `displayName` название могло сохраниться как последовательность знаков вопроса (`??????`) — повреждение кодировки при передаче в Битрикс24.\n\n**Стало**\n\nНазвание передаётся в кодировке UTF-8, кириллица сохраняется корректно.\n\n**Влияние на интеграторов**\n\nДействий не требуется. Кириллические названия больше не искажаются.\n\n### FIX-0702-21: items: фильтрация по полям связи parentId\u003CN>\n\n**Было**\n\nФильтр по динамическому полю связи (например `parentId2` — связанная сделка) на [GET \u002Fv1\u002Fitems\u002F:entityTypeId](\u002Fdocs\u002Fentities\u002Fitems\u002Flist) и [POST \u002Fv1\u002Fitems\u002F:entityTypeId\u002Fsearch](\u002Fdocs\u002Fentities\u002Fitems\u002Fsearch) отклонялся с `400 UNKNOWN_FILTER_FIELD`, хотя поле присутствует в `GET \u002Fv1\u002Fitems\u002F:entityTypeId\u002Ffields` и возвращается в ответах.\n\n**Стало**\n\nПоля вида `parentId\u003CN>` принимаются в фильтре и передаются в запрос как есть. Найти смарт-процесс, связанный с конкретной родительской сущностью, теперь можно напрямую через обёртку items.\n\n**Влияние на интеграторов**\n\nДействий не требуется. Запросы, ранее получавшие `400`, теперь отрабатывают.\n\n### FIX-0702-22: смарт-процессы: сохранение списка значений и названия пользовательского поля\n\n**Было**\n\nНа [POST \u002Fv1\u002Fitems\u002F:entityTypeId\u002Fuserfields](\u002Fdocs\u002Fuserfields\u002Fsmart-processes) поле-список (`userTypeId: enumeration`) создавалось, но варианты значений не сохранялись (список приходил пустым), а название поля, переданное строкой, в интерфейсе оставалось пустым.\n\n**Стало**\n\nВарианты значений принимаются как в `enum`, так и в `list` и корректно сохраняются. Название, переданное строкой, автоматически оборачивается в языковую карту и заполняет подписи в форме, колонке и фильтре.\n\n**Влияние на интеграторов**\n\nДействий не требуется. Ранее «молча терявшиеся» значения списка и название теперь сохраняются.\n\n### FIX-0702-23: req-family: \u002Ffields у адресов и preset-fields отдают ключи в camelCase\n\n**Было**\n\n`GET \u002Fv1\u002Faddresses\u002Ffields` и `GET \u002Fv1\u002Frequisite-presets\u002F:presetId\u002Ffields\u002Fschema` возвращали описание полей с сырыми ключами в UPPER_SNAKE_CASE (`TYPE_ID`, `ADDRESS_1`, `FIELD_NAME`, `IN_SHORT_LIST`), хотя данные этих сущностей (`GET \u002Fv1\u002Faddresses`, список полей пресета) уже приходили в camelCase — схема не совпадала с реальными именами в данных.\n\n**Стало**\n\nОба эндпоинта нормализуют ключи описания в camelCase (`typeId`, `address1`, `fieldName`, `inShortList`), как и весь остальной V1. Внутренние дескрипторы поля (`type`, `isRequired`, `isReadOnly`, `title`) не меняются.\n\n**Влияние на интеграторов**\n\nКлючи в ответе `\u002Ffields` теперь совпадают с именами полей в данных. Клиент, читавший camelCase-имена из данных, получает согласованную схему; действий не требуется.\n\n## 2026-07-01\n\n### FIX-0701-1: Загрузка файла на Диск принимает файлы больше 1 МБ\n\n**Было**\n\n[POST \u002Fv1\u002Ffiles\u002Fupload](\u002Fdocs\u002Fentities\u002Ffiles\u002Fupload) отклонял тело запроса больше ~1 МБ ошибкой `FST_ERR_CTP_BODY_TOO_LARGE`. Файл передаётся в base64 в JSON-теле, а base64 раздувает размер примерно на треть — поэтому даже файл 1,1 МБ не проходил. Способа загрузить файл крупнее не было.\n\n**Стало**\n\nЛимит тела этого маршрута поднят до 70 МБ — этого хватает на файл около 50 МБ с учётом base64 и JSON-обёртки (запись звонка, типовые вложения). Битрикс24 по-прежнему применяет собственное ограничение на размер файла Диска: при его превышении ошибка приходит в стандартном конверте. Для файлов в сотни МБ нужен отдельный способ загрузки (multipart \u002F presigned) — он пока не реализован.\n\n### NEW-0701-2: иконка приложения сервера: загрузка SVG, анонимная отдача, фавикон\n\nПоявился способ задать иконку приложения для сервера. `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Ficon` (multipart\u002Fform-data, поле `file`, только SVG до 256 КБ, без скриптов, обработчиков событий и внешних ссылок) загружает иконку; она отдаётся анонимно по стабильному `GET \u002Fapi\u002Fserver-icons\u002F:id` и показывается в каталоге приложений Bitrix24. Чтобы иконка стала фавиконом во вкладке браузера, впишите `\u003Clink rel=\"icon\" type=\"image\u002Fsvg+xml\" href=\"\u003Cбазовый-URL>\u002Fapi\u002Fserver-icons\u002F:id\">` в HTML приложения во время сборки — после этого перезаливка иконки обновляет каталог и фавикон автоматически. Формат, требования и порядок — [Иконка приложения](\u002Fdocs\u002Finfra\u002Fapp-icon).\n\n### NEW-0701-3: Профиль текущего пользователя сообщает права администратора\n\nОтвет [GET \u002Fv1\u002Fusers\u002Fme](\u002Fdocs\u002Fentities\u002Fusers) теперь возвращает рабочее поле `isAdmin`: `true` — пользователь администратор портала, `false` — нет, `null` — определить не удалось (временный сбой; профиль при этом всё равно возвращается). Поле пригодно для серверной проверки прав в вашем бэкенде. На `GET \u002Fv1\u002Fusers\u002F:id` и в списке пользователей поле по-прежнему недоступно — вердикт отдаётся только для текущего пользователя сессии.\n\n### NEW-0701-4: Расширенный статический контракт полей в \u002Fv1\u002Fguide и указатель schema-discovery в \u002Fv1\u002Fme\n\nПо каждой сущности в ответе [GET \u002Fv1\u002Fguide](\u002Fdocs\u002Fkeys-auth) добавлено поле `data.entities[].fieldsDetailed` — расширенный статический контракт полей: `type`, `readonly`, `required`, `createOnly` и декодирование `enum` (например, значения `status` и `priority` у задач). Оно доступно по одному заголовку `X-Api-Key`, без сессии, и предназначено для маппингов и кодогенерации до появления пользовательской сессии. Компактное поле `fields` сохранено без изменений.\n\nОтвет `GET \u002Fv1\u002Fme` для ключа авторизации без пользовательской сессии (без заголовка `Authorization: Bearer`) теперь содержит блок `schemaDiscovery` — указатель, где брать статическую схему без сессии (`\u002Fv1\u002Fguide`) и как получить живые и пользовательские поля (Bearer-сессия или персональный ключ). Эндпоинты `GET \u002Fv1\u002F\u003Centity>\u002Ffields` и `GET \u002Fv1\u002Fuserfields\u002F*` не изменились.\n\n### BC-0701-5: categoryId в ответе постов теперь массив чисел\n\n> Поддержка старого формата до: 01.01.2027\n\n**Было**\n\n[GET \u002Fv1\u002Fposts](\u002Fdocs\u002Ffeed\u002Fposts\u002Flist) отдавал `categoryId` с типом, зависящим от количества категорий поста: `null` без категорий, число (`11`) для одной, строка со списком ID через запятую (`\"5,7,9\"`) для нескольких. Типизированный клиент с полем `categoryId: number | null` работал на постах с одной категорией, но ломался на постах с двумя и более.\n\n**Стало**\n\n`categoryId` — всегда массив чисел `number[]`: `[]` без категорий, `[11]` для одной, `[5, 7, 9]` для нескольких. Тип единый независимо от количества категорий.\n\n**Что делать интеграторам**\n\nЧитайте `categoryId` как массив: `post.categoryId.length` вместо проверки на `null`, `post.categoryId[0]` для первой категории. Прежнюю ветку «число или строка» можно убрать.\n\n### FIX-0701-6: единые коды ошибок токена и скоупа в разделе Диска\n\n**Было**\n\nПользовательские операции Диска — [POST \u002Fv1\u002Ffiles\u002F:id\u002Fmoveto](\u002Fdocs\u002Fentities\u002Ffiles\u002Fmoveto), `copyto`, [POST \u002Fv1\u002Ffiles\u002Fupload](\u002Fdocs\u002Fentities\u002Ffiles\u002Fupload), [GET \u002Fv1\u002Ffiles\u002F:id\u002Fdownload](\u002Fdocs\u002Fentities\u002Ffiles\u002Fdownload) и аналоги для папок — при отсутствии токенов портала возвращали `401 NO_TOKENS`, а при нехватке скоупа `disk` — `403 SCOPE_MISSING`. Сгенерированные CRUD-операции того же раздела (list\u002Fget\u002Fcreate\u002Fupdate\u002Fdelete) для тех же условий уже возвращали `401 TOKEN_MISSING` и `403 SCOPE_DENIED` — поэтому в пределах одного раздела клиент видел два разных кода для одной ошибки.\n\n**Стало**\n\nВсе операции Диска возвращают единые коды — `401 TOKEN_MISSING` и `403 SCOPE_DENIED`, как и остальной V1 API. HTTP-статусы (401 и 403) не изменились.\n\n**Влияние на интеграторов**\n\nЕсли код ветвился по строкам `NO_TOKENS` или `SCOPE_MISSING` на операциях moveto\u002Fcopyto\u002Fupload\u002Fdownload, переключитесь на `TOKEN_MISSING` \u002F `SCOPE_DENIED` (или проверяйте HTTP-статус). Остальным менять ничего не нужно.\n\n### FIX-0701-7: placement CALL_CARD убран из списка допустимых\n\n**Было**\n\nКод placement `CALL_CARD` числился допустимым: `POST \u002Fv1\u002Fplacements\u002Fbind` пропускал его через валидацию и передавал в Битрикс24, а `GET \u002Fv1\u002Fplacements\u002Favailable` выдавал его в списке. Но ни один модуль Битрикс24 не регистрирует этот placement, поэтому `placement.bind` завершался внутренней ошибкой, которую платформа отдавала как `INTERNAL_SERVER_ERROR`.\n\n**Стало**\n\n`CALL_CARD` удалён из списка допустимых: он больше не появляется в `GET \u002Fv1\u002Fplacements\u002Favailable`, а `POST \u002Fv1\u002Fplacements\u002Fbind` с ним отклоняется сразу понятной ошибкой `VALIDATION_ERROR`, без обращения к Битрикс24.\n\n**Влияние на интеграторов**\n\nПривязка `CALL_CARD` не работала и раньше (возвращала непонятную ошибку 500), поэтому рабочих интеграций изменение не ломает. Для панели приложений в карточке звонка используйте актуальные placement'ы из `GET \u002Fv1\u002Fplacements\u002Favailable`.\n\n### FIX-0701-8: workday open\u002Fclose\u002Fpause: поле userId теперь применяется\n\n**Было**\n\nДокументированное поле тела `userId` в [POST \u002Fv1\u002Fworkday\u002Fopen](\u002Fdocs\u002Fworkday\u002Fopen), [POST \u002Fv1\u002Fworkday\u002Fclose](\u002Fdocs\u002Fworkday\u002Fclose) и [POST \u002Fv1\u002Fworkday\u002Fpause](\u002Fdocs\u002Fworkday\u002Fpause) молча игнорировалось: операция всегда выполнялась над владельцем токенов ключа, даже если был передан другой сотрудник. Ответ приходил `success`, но действие затрагивало не того пользователя.\n\n**Стало**\n\n`userId` транслируется в параметр Битрикс24 `USER_ID`, поэтому операция выполняется над указанным сотрудником (при наличии прав администратора или руководителя). Несуществующий `userId` теперь возвращает ошибку Битрикс24, а не мнимый успех. Некорректный `userId` (не положительное целое) отклоняется как `400 INVALID_PARAMS`. Поведение совпадает с уже работавшим [GET \u002Fv1\u002Fworkday\u002Fstatus](\u002Fdocs\u002Fworkday\u002Fstatus).\n\n**Влияние на интеграторов**\n\nКто не передавал `userId`, изменений не заметит — операция по-прежнему применяется к владельцу токенов. Кто передавал `userId`, теперь получит корректное действие над указанным сотрудником.\n\n## 2026-06-30\n\n### NEW-0630-1: POST \u002Fv1\u002Fcowork\u002Fdeploy-key — получить проектный ключ для деплоя из Cowork\u002FCode\n\nКлюч Cowork\u002FCode (`vibe:cowork`) работает только с data-plane и блокируется на control-plane инфраструктуры с 403 `INFRA_FORBIDDEN_FOR_COWORK_KEY`. Новый эндпоинт [POST \u002Fv1\u002Fcowork\u002Fdeploy-key](\u002Fdocs\u002Fcowork) позволяет агенту самому получить отдельный проектный ключ с правом деплоя: вызовите его этим же Cowork-ключом, возьмите поле `key` из ответа (тело — плоский объект, без обёртки `data`) и используйте его как заголовок `X-Api-Key` для деплоя\u002Fprovision\u002Fexec под `\u002Fv1\u002Finfra\u002F*`.\n\nВозвращаемый ключ несёт скоупы `vibe:infra` + `vibe:storage` (без `vibe:cowork`), действует 7 дней и привязан к владельцу и порталу Cowork-ключа. На каждый вызов выдаётся свежий ключ, прежний проектный ключ при этом отзывается (активен всегда один). Требуется скоуп `vibe:cowork` и активная подписка Cowork\u002FCode; коды отказа — 403 `INSUFFICIENT_SCOPE` \u002F 403 `COWORK_NOT_ACTIVATED` \u002F 503 `DEPLOY_KEY_DISABLED` \u002F 503 `INFRA_DISABLED`.\n\n### NEW-0630-2: Self-hosted placement получает одноразовый код авторизации на appUrl\n\nДля приложения с собственным `appUrl` (вне Black Hole), открытого как placement, платформа теперь добавляет к редиректу на `appUrl` одноразовый код авторизации (`?code=...`) вместо внутреннего gateway-токена. Приложение обменивает этот код на `vibe_session` существующим запросом `POST \u002Fv1\u002Foauth\u002Ftoken` — `redirect_uri` должен точно совпадать с настроенным `appUrl`. Раньше такие приложения получали невостребуемый токен и не могли авторизовать пользователя.\n\n### NEW-0630-3: Новый эндпоинт POST \u002Fv1\u002Foauth\u002Fplacement-session для self-hosted приложений\n\nSelf-hosted приложение (на собственном сервере, не на Black Hole), открытое как placement (iframe) в Битрикс24, теперь может обменять токен пользователя Битрикс24, полученный в placement-колбэке на своём обработчике, на `vibe_session`. Запрос `POST \u002Fv1\u002Foauth\u002Fplacement-session` с телом `{ app_key, access_token, member_id, domain }` (необязательно `refresh_token`, `expires_in`) идёт сервер-к-серверу — токен сессии не попадает в браузер. Раздел авторизации документации описывает обе топологии placement.\n\n### FIX-0630-4: PATCH права OAuth-app-ключа: честный отказ вместо ложной выдачи\n\n**Было**\n\n`PATCH \u002Fv1\u002Fkeys\u002F:id` с добавлением права Bitrix24 к ключу OAuth-приложения (`vibe_app_*`), как и `PATCH \u002Fv1\u002Fapps\u002F:id` с расширением `scopes` приложения, возвращал `200` и сохранял новый набор прав. Но права OAuth-приложения фиксируются при выпуске и от такой правки на стороне Bitrix24 не меняются, поэтому `GET \u002Fv1\u002Fme` затем сообщал право, которого Bitrix24 не выдавал, а реальный вызов отклонялся.\n\n**Стало**\n\nДобавление права Bitrix24 к ключу OAuth-приложения или к приложению теперь отклоняется с `403 OAUTH_SCOPE_CHANGE_REQUIRES_REISSUE`. Снятие прав и изменение `vibe:*`-прав работают по-прежнему. Чтобы получить новое право, выпустите новый ключ авторизации с нужным набором.\n\n### FIX-0630-5: ключ со скоупом `tasks` теперь реально открывает методы задач\n\n**Было**\n\nКлюч, выписанный со скоупом `tasks` (множественное число — его предлагает UI-пикер), на dev-key-портале минтил вебхук, который Bitrix24 принимал, но к REST-методам модуля Задач не привязывал. `GET \u002Fv1\u002Fme` рапортовал `tasks`, но вызовы методов задач отклонялись на стороне Bitrix24. Скоуп `task` (единственное число) работал.\n\n**Стало**\n\nПри выписке и правке ключа набор прав канонизируется к написанию, которое Bitrix24 реально привязывает к методам (`tasks` → `task`), поэтому ключ открывает методы задач независимо от выбранного написания. На уже выписанные ключи изменение не распространяется ретроактивно — перевыпустите ключ.\n\n### FIX-0630-6: неверный формат FILES в комментарии таймлайна отклоняется с 400\n\n**Было**\n\n[POST \u002Fv1\u002Ftimelines](\u002Fdocs\u002Fentities\u002Ftimelines\u002Fcreate) и `PATCH \u002Fv1\u002Ftimelines\u002F:id` принимали поле `FILES` в любом виде и отвечали `200`\u002F`201`. Если форма отличалась от массива пар `[[имяФайла, base64Содержимое]]` — например плоский массив строк или одиночная пара без внешнего массива — комментарий создавался, но файл прикреплялся как мусорный (со случайным именем и нечитаемым содержимым) либо терялся молча, без признаков ошибки.\n\n**Стало**\n\nПоле `FILES`, переданное не в виде массива пар `[[имяФайла, base64Содержимое]]`, отклоняется до обращения к Битрикс24 ошибкой `400 INVALID_FILES_SHAPE` с подсказкой о правильной форме. Пустой `FILES` (`[]`) и отсутствие поля по-прежнему допустимы. Проверка действует на одиночных `POST`\u002F`PATCH` и в батче (`POST \u002Fv1\u002Fbatch`, `POST \u002Fv1\u002Ftimelines\u002Fbatch`).\n\n**Влияние на интеграторов**\n\nКто передаёт `FILES` в задокументированной форме `[[имяФайла, base64Содержимое]]` — изменений нет. Кто полагался на другие формы — теперь получит явную `400` вместо молча испорченного вложения, и сможет исправить запрос.\n\n### FIX-0630-7: пустые поля ботов и сотрудников приходят как null\u002F[], а не false\u002F{}\n\n**Было**\n\nВ ответах карточки бота ([GET \u002Fv1\u002Fbots\u002F:botId](\u002Fdocs\u002Fbots\u002Fmanagement\u002Fget), [POST \u002Fv1\u002Fbots](\u002Fdocs\u002Fbots\u002Fmanagement\u002Fcreate), [PATCH \u002Fv1\u002Fbots\u002F:botId](\u002Fdocs\u002Fbots\u002Fmanagement\u002Fupdate)) незаполненные поля в `users[]` отдавались неверным примитивом: даты `lastActivityDate`, `mobileLastDate`, `desktopLastDate` приходили как булево `false`, а пустой список `phones` — тоже как `false`. То же поле `lastActivityDate` в `GET \u002Fv1\u002Fusers` приходило как пустой объект `{}`. Из-за этого `new Date(lastActivityDate)` молча давал начало эпохи, а `phones.map(...)` падал с ошибкой типа.\n\n**Стало**\n\nНезаполненная дата на всех путях кодируется единообразно как `null`, а пустой список телефонов — как `[]`. Заполненная дата по-прежнему приходит ISO-строкой, заполненный список — массивом.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно — типы стали корректными. Код, который опирался на сравнение с `false` для пустых значений, перестанет срабатывать: проверяйте дату на `null`, а список телефонов — как массив.\n\n**Затронутые эндпоинты:** [GET \u002Fv1\u002Fbots\u002F:botId](\u002Fdocs\u002Fbots\u002Fmanagement\u002Fget), [POST \u002Fv1\u002Fbots](\u002Fdocs\u002Fbots\u002Fmanagement\u002Fcreate), [PATCH \u002Fv1\u002Fbots\u002F:botId](\u002Fdocs\u002Fbots\u002Fmanagement\u002Fupdate), `GET \u002Fv1\u002Fusers`\n\n### NEW-0630-8: Пересвязка OAuth-credentials приложения без удаления\n\nНовый эндпоинт [POST \u002Fv1\u002Fapps\u002F:id\u002Frelink-oauth](\u002Fdocs\u002Fapps) обновляет `bitrixClientId` и `bitrixClientSecret` у существующего приложения, не удаляя его. Это нужно, когда локальное OAuth-приложение пересоздали на портале Битрикс24 и у него сменился `client_id`: раньше единственным путём было удалить приложение (что рвало связанные бот, openline и привязки) и создать заново.\n\nТело запроса: `{ bitrixClientId, bitrixClientSecret }` (оба обязательны). Парный ключ, бот и привязки сохраняются. Если этот `client_id` уже привязан к другому приложению — `409 OAUTH_CLIENT_ID_IN_USE`. Ключом самого OAuth-приложения вызвать нельзя — `403 OAUTH_APP_KEY_CANNOT_RELINK` (нужен личный ключ или кабинет). После пересвязки переустановите приложение на портале — это восстановит подписку на события.\n\n### NEW-0630-9: Веб-поиск: полный текст страниц, изображения, режим новостей и фильтры по доменам в research\n\n**Было**\n\n`POST \u002Fv1\u002Fsearch` принимал `include_raw_content` как булев флаг, но полный текст найденных страниц в ответ не попадал. Не было параметров для режима новостей и для запроса изображений. `POST \u002Fv1\u002Fresearch` принимал `include_domains` и `exclude_domains`, но молча их отбрасывал.\n\n**Стало**\n\n`POST \u002Fv1\u002Fsearch` получил два новых необязательных параметра: `topic` (`general` или `news`, по умолчанию `general`) и `include_images` (булев, по умолчанию `false`). Булев `include_raw_content` теперь действительно возвращает полный текст: у каждого результата появилось поле `rawContent` (полный текст страницы, ограниченный по размеру). В ответе добавились верхнеуровневые `images` (массив объектов с полем `url`) и `ignored_filters` (массив строк — переданные фильтры, которые провайдер не смог применить). Эти поля приходят и в синхронном теле ответа, и в кадре `done` потоковой передачи. Заголовок `X-Search-Filters-Ignored` сохранён и теперь может перечислять `topic` и `include_images`. `POST \u002Fv1\u002Fresearch` теперь применяет `include_domains` и `exclude_domains` (до 20 каждый) у провайдеров с поддержкой и сообщает о превышении через `ignored_filters` в кадре `done`. Какие именно возможности доступны у выбранного движка — возвращает `GET \u002Fv1\u002Fsearch\u002Fproviders`. Клиентам со строгой валидацией схемы по `additionalProperties` нужно учесть новые поля ответа.\n\n### NEW-0630-10: Чтение AI-расшифровок звонков клиентов через API\n\nНовый эндпоинт [GET \u002Fv1\u002Factivities\u002F:activityId\u002Ftranscript](\u002Fdocs\u002Fentities\u002Factivities\u002Ftranscript) возвращает готовую AI-расшифровку звонка клиента по идентификатору CRM-активности «Звонок». Метод только читает уже готовую расшифровку — генерацию не запускает. Требует скоуп `crm`. Если расшифровки для звонка ещё нет, поле `data.transcription` равно `null` — это штатный ответ, а не ошибка.\n\n## 2026-06-29\n\n### FIX-0629-1: поиск узлов оргструктуры теперь ищет по названию\n\n**Было**\n\n[POST \u002Fv1\u002Fhumanresources\u002Fnodes\u002Fsearch](\u002Fdocs\u002Fhumanresources\u002Fnodes\u002Fsearch) проксировал в `humanresources.node.list`: тип узла задавался внутри `filter`, поиска по названию не было вовсе, а тело `{ \"type\": ..., \"name\": ... }` на верхнем уровне (или запрос без тела) возвращало `400` или `500`.\n\n**Стало**\n\nЭндпоинт обёрнут на `humanresources.node.search`. Обязательны два поля на верхнем уровне тела — `type` (`DEPARTMENT` или `TEAM`) и `name` (подстрока названия). Необязательны `parentId` и `pagination.limit` (по умолчанию 50, максимум 200). Возвращаются узлы, чьё название содержит `name`, в плоском `data` с `meta` (`total`, `hasMore`). Поля `filter`, `order` и `select` больше не принимаются.\n\n**Влияние на интеграторов**\n\nПрисылайте `{ \"type\": \"TEAM\", \"name\": \"\u003Cподстрока>\" }` на верхнем уровне вместо прежнего `{ \"filter\": { \"type\": \"TEAM\" } }`. Чтобы перечислить все узлы типа без поиска по названию, используйте [GET \u002Fv1\u002Fhumanresources\u002Fnodes](\u002Fdocs\u002Fhumanresources\u002Fnodes\u002Flist) с `?type=...`.\n\n### NEW-0629-2: Расписание выгодных часов AI-квоты (off-peak)\n\nДобавлен эндпоинт `GET \u002Fv1\u002Foff-peak` — расписание скидок «выгодных часов» (Time-of-Use) для AI-квоты. Ответ содержит множитель цены прямо сейчас (`currentMultiplier`), ближайшее окно, когда станет дешевле (`nextWindow`), сетку 24×7 по часам и дням недели (`grid`), текущую ячейку сетки (`nowCell`) и часовой пояс расписания (`timezone`). Скидка применяется только к квотируемому расходу — в эти часы квота расходуется медленнее; оплата за токены по кошельку не затрагивается. Необязательный параметр `model=\u003Cидентификатор>` возвращает расписание конкретной модели вместо общего по умолчанию. Требуется скоуп `vibe:ai`. Пока выгодные часы не включены, ответ — `{ \"enabled\": false }`.\n\n### BC-0629-3: удалён слаг провайдера vibe-search\n\n> Поддержка старого формата до: 26.12.2026\n\n**Было**\n\nПоле `provider` в [POST \u002Fv1\u002Fsearch](\u002Fdocs\u002Fsearch\u002Frun) и [POST \u002Fv1\u002Fresearch](\u002Fdocs\u002Fsearch\u002Fresearch) принимало слаг `vibe-search` — отдельный платформенный движок, добавленный 06.06.2026. Он также присутствовал в перечне слагов [GET \u002Fv1\u002Fsearch\u002Fproviders](\u002Fdocs\u002Fsearch\u002Fproviders).\n\n**Стало**\n\nСлаг `vibe-search` удалён. Платформенный поисковый движок на всех инстансах называется `bitrix-search` — конкретный апстрим за ним зависит от инстанса. Запрос с `provider: \"vibe-search\"` теперь возвращает `400 INVALID_REQUEST` (значение не проходит валидацию). Поддержка research для `bitrix-search` тоже зависит от инстанса — читайте [GET \u002Fv1\u002Fsearch\u002Fproviders](\u002Fdocs\u002Fsearch\u002Fproviders).\n\n**Что делать интеграторам**\n\nЕсли в запросе явно передавался `provider: \"vibe-search\"`, замените его на `bitrix-search` либо опустите поле `provider`, чтобы использовать движок по умолчанию инстанса (его показывает поле `defaultProvider` в [GET \u002Fv1\u002Fme](\u002Fdocs\u002Fkeys-auth)). Слаг `vibe-search` не был движком по умолчанию ни на одном проде, поэтому затронуты только интеграции, прописавшие его явно.\n\n### FIX-0629-4: Значение null в поле через POST \u002Fv1\u002Fbatch больше не пишет в поле строку «null»\n\n**Было**\n\nВ составном `POST \u002Fv1\u002Fbatch` создание или обновление со значением поля `null` (например `{\"entity\":\"deals\",\"action\":\"update\",\"entityId\":123,\"params\":{\"comments\":null}}`) записывало в поле **литеральную строку `\"null\"`**.\n\n**Стало**\n\nПоле получает пустое значение, которое Bitrix24 трактует по типу поля: текстовое — очищается, числовое — становится `0`, датовое — остаётся без изменений. Литеральная строка `\"null\"` больше не пишется, ошибки не возникает. Это совпадает с поведением одиночного `PATCH \u002Fv1\u002F{entity}\u002F:id` с `null`. Постраничный путь `\u002Fv1\u002F{entity}\u002Fbatch` по-прежнему пропускает `null`-поле целиком (оставляет значение без изменений у всех типов).\n\n### FIX-0629-5: Поиск и список с фильтром по null теперь отдают больше 50 строк\n\n**Было**\n\nЗапрос `POST \u002Fv1\u002F{entity}\u002Fsearch` или `GET \u002Fv1\u002F{entity}` с фильтром по пустому значению (например `{\"filter\": {\"closedDate\": null}}`) и `limit` больше 50 возвращал максимум 50 записей, хотя `meta.total` показывал реальное число совпадений и `meta.hasMore` был `true`. Автопагинация молча обрывалась после первой страницы, и типовой обход «читать, пока строк ровно `limit`» получал неполный результат без единой ошибки.\n\n**Стало**\n\nТакой запрос отдаёт до `limit` записей, как и с любым другим фильтром. Значение `null` в фильтре трактуется как «поле пусто» одинаково на всех страницах выборки.\n\n**Влияние на интеграторов**\n\nКлиентам, которые из-за обрыва листали вручную через `offset` шагом 50, ручной обход больше не нужен — можно запросить до 5000 записей одним вызовом.\n\n### FIX-0629-6: смена порта и автомаршрутизация деплоя работают на новых серверах-приложениях из коробки\n\n**Было**\n\nОбычный сервер «Опубликовать приложение» поднимался с фиксированным портом агента, поэтому `PATCH \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fport` и шаг автомаршрутизации деплоя отвечали `409 PORT_NOT_APPLIED` (`NO_SCANNER`), а публичный URL отдавал служебную страницу Black Hole, пока сервис слушал не порт по умолчанию.\n\n**Стало**\n\nНовые обычные серверы-приложения поднимаются с авто-определением порта: сервис на любом порту доступен через туннель сразу, а смена порта и автомаршрутизация деплоя проходят успешно. Серверы агентов и galaxy-хостов поведение не меняют.\n\n### FIX-0629-7: установка приложения через \u002Fv1\u002Fapps возвращает понятный код ошибки вместо общего BOX_APP_INSTALL_FAILED\n\n**Было**\n\nПри сбое установки OAuth-приложения [POST \u002Fv1\u002Fapps](\u002Fdocs\u002Fapps\u002Fcreate) всегда возвращал `502 BOX_APP_INSTALL_FAILED`, а в `error.message` подставлялся сырой ответ Битрикс24 целиком.\n\n**Стало**\n\nОтвет при сбое классифицируется: `403 B24_INSUFFICIENT_SCOPE` (служебная интеграция потеряла права на портале), `410 STALE_DEVELOPER_KEY` (доступ изменили или удалили — восстановить автоматически нельзя), `502 RECOVERY_FAILED` (временный сбой, можно повторить) или `502 DEVKEY_MINT_FAILED` (прочее). `error.message` больше не содержит сырой ответ Битрикс24 — диагностика переехала в очищенное поле `error.details.b24Body`.\n\n**Влияние на интеграторов**\n\nОбработка «ответ не 201 — установка не удалась» продолжает работать без изменений. Если код различал именно `BOX_APP_INSTALL_FAILED`, добавьте обработку новых кодов выше.\n\n### FIX-0629-8: поиск по широкому диапазону дат больше не возвращает пусто\n\n**Было**\n\n`POST \u002Fv1\u002Fdeals\u002Fsearch` (и аналогично для leads, contacts, companies, quotes, invoices, items) с фильтром по дате и нижней границей (`>=` \u002F `>`) за период шире 14 дней возвращал `200` с пустым `data` и `meta.total: 0`, хотя записи за этот период существовали.\n\n**Стало**\n\nТакой запрос возвращает все совпадающие записи. Поведение `GET \u002Fv1\u002Fdeals`, узких диапазонов (≤ 14 дней) и параметра `autoWindow: false` не менялось.\n\n### NEW-0629-9: Новый код ошибки CONNECTOR_APP_INSTALL_FORBIDDEN при установке приложения\n\n[POST \u002Fv1\u002Fapps](\u002Fdocs\u002Fapps) на коробочном портале теперь возвращает `403` с кодом `CONNECTOR_APP_INSTALL_FORBIDDEN`, когда администратор портала Битрикс24 запретил пользователю устанавливать приложения. Поле `error.message` содержит понятное локализованное объяснение с подсказкой обратиться к администратору портала. Прежде такой отказ отдавался как общий `502 CONNECTOR_APP_INSTALL_FAILED` без объяснения причины; этот код по-прежнему используется для прочих сбоев установки.\n\n### NEW-0629-10: Перенос владения ботом на другой ключ\n\nДобавлен эндпоинт [POST \u002Fv1\u002Fbots\u002F:botId\u002Ftransfer](\u002Fdocs\u002Fbots\u002Fmanagement\u002Ftransfer) — переносит владение ботом на другой API-ключ того же аккаунта Битрикс24 и того же пользователя (или администратора аккаунта). Решает ситуацию, когда бот «осиротел» после пересоздания приложения: ключ-владелец отозван, и рантайм бота переставал работать. Тело: `{ \"targetApiKeyId\": \"\u003Cid>\" }`. Целевой ключ должен быть активен, в том же аккаунте, с областью `imbot`. После переноса проверьте B24-привязку нового ключа через `POST \u002Fv1\u002Fbots\u002F:botId\u002Freauth`.\n\n## 2026-06-28\n\n### FIX-0628-1: редактирование scopes ключа применяется к вебхуку Битрикс24\n\n**Было**\n\n`PATCH \u002Fv1\u002Fkeys\u002F:id` со списком `scopes` сохранял новый набор в Вайбкоде, но на self-hosted (коробочных) порталах Битрикс24 не переносил его на вебхук портала. Вебхук оставался со старым набором scopes, и вызов метода из только что добавленного scope отклонялся Битрикс24 (403), хотя по данным Вайбкода ключ этот scope уже имел.\n\n**Стало**\n\nИзменение `scopes` теперь применяется к вебхуку Битрикс24 в том же запросе. Если синхронизацию выполнить не удалось, ключ не обновляется (Вайбкод и Битрикс24 остаются на прежнем наборе), а ответ несёт код ошибки: `INVALID_SCOPES` (400) — портал не выдаёт один из запрошенных scope, `STALE_DEVELOPER_KEY` (410) или `RECOVERY_FAILED` (502) — ключ доступа портала недействителен, `BOX_NO_DEVELOPER_KEY` (400) — у владельца ключа нет ключа доступа портала, `DEVKEY_SCOPE_SYNC_FAILED` (502) — прочий отказ Битрикс24.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно — scopes, добавленные через `PATCH \u002Fv1\u002Fkeys\u002F:id`, теперь работают сразу. Если в ответ пришёл один из кодов выше, набор scopes остался прежним: устраните причину и повторите запрос.\n\n## 2026-06-27\n\n### NEW-0627-1: Свойства товаров каталога — чтение и управление схемой свойств\n\nДобавлен раздел [\u002Fv1\u002Fcatalog-product-properties](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties) — определения пользовательских свойств торгового каталога (идентификатор, название, тип). Поддерживаются список, получение, создание, изменение, удаление, поиск и справочник полей. Свойства-списки товара приходят в [\u002Fv1\u002Fcatalog-products](\u002Fdocs\u002Fentities\u002Fcatalog-products) полями вида `propertyNNN`, где `NNN` — идентификатор свойства. Новый раздел сопоставляет этот идентификатор с названием и типом свойства. Фильтр `filter[iblockId]` ограничивает выборку одним каталогом, идентификатор берётся из [\u002Fv1\u002Fcatalogs](\u002Fdocs\u002Fentities\u002Fcatalogs). Требуется скоуп `catalog`.\n\n## 2026-06-26\n\n### FIX-0626-1: catalog-prices: системные поля priceScale, extraId, timestampX объявлены в схеме\n\n**Было**\n\n[GET \u002Fv1\u002Fcatalog-prices](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Flist) и [GET \u002Fv1\u002Fcatalog-prices\u002F:id](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Fget) возвращали поля `priceScale`, `extraId` и `timestampX`, но они не были объявлены в схеме: проходили без нормализации (поле `timestampX` приходило в формате со смещением, например `2024-06-17T16:53:24+03:00`) и отсутствовали в ответе [GET \u002Fv1\u002Fcatalog-prices\u002Ffields](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Ffields).\n\n**Стало**\n\nТри поля объявлены как доступные только для чтения. Теперь они перечислены в [GET \u002Fv1\u002Fcatalog-prices\u002Ffields](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Ffields), а `timestampX` нормализуется в ISO 8601 UTC (`2024-06-17T13:53:24.000Z`) — единообразно с остальными полями типа datetime.\n\n**Влияние на интеграторов**\n\nМомент времени в `timestampX` не меняется — меняется только его строковое представление (UTC вместо локального смещения). Клиенты, разбирающие значение стандартным парсером дат, продолжают работать без изменений.\n\n### FIX-0626-2: Скачивание исходников сервера и приложения: подписанный URL больше не отдаёт 403\n\n**Было**\n\n`GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fsources\u002F:versionId\u002Fdownload` и `GET \u002Fv1\u002Fapps\u002F:id\u002Fsources\u002F:versionId\u002Fdownload` возвращали `200` с подписанным URL, но скачивание по этому URL падало с `403 AccessDenied`, если запрос шёл под личным ключом (`vibe_api_*`). Листинг версий при этом работал, и файл физически присутствовал в хранилище.\n\n**Стало**\n\nПодписанный URL теперь привязан к фактическому расположению объекта в хранилище, поэтому скачивание возвращает содержимое архива. Исправление покрывает и снапшоты, у которых ключ хранения принадлежит другому семейству (legacy-снапшоты приложения с привязкой к серверу).\n\n**Влияние на интеграторов**\n\nКонтракт эндпоинтов не меняется — это восстановление задокументированного поведения «`200` + рабочий подписанный URL». Никаких изменений на стороне клиента не требуется.\n\n### NEW-0626-3: Обновление api-bearer токена и причина отказа Gateway\n\nНовый эндпоинт [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Faccess-tokens\u002F:tokenId\u002Frefresh](\u002Fdocs\u002Finfra\u002Faccess-tokens\u002Frefresh) выпускает свежий JWT (до 10 минут) для уже существующего токена режима `api-bearer` — без создания новой записи, не расходуя лимит активных токенов и лимит выпусков в час. Долгоживущий клиент (CI, AI-агент) обновляет токен перед истечением `jwtExpiresAt` вместо повторного выпуска. Один отзыв гасит и исходный, и все обновлённые JWT одного токена.\n\nОтвет `401 BH_LOGIN_REQUIRED`, который Gateway возвращает на субдомене приложения при отклонении заголовка `Authorization: Bearer`, теперь содержит поле `reason` с конкретной причиной: `expired`, `signature`, `subdomain`, `type`, `revoked`, `malformed` или `invalid`. Поле аддитивное — прежние клиенты его игнорируют.\n\n## 2026-06-25\n\n### FIX-0625-1: нейтральные идентификаторы провайдера, плана и региона в инфраструктурном API\n\n**Было**\n\nНа международной (`.com`) поверхности `GET \u002Fv1\u002Finfra\u002Fproviders`, `GET \u002Fv1\u002Finfra\u002Fproviders\u002F:id\u002Fplans` и `GET \u002Fv1\u002Finfra\u002Fproviders\u002F:id\u002Fregions` отдавали идентификаторы провайдера, планов, типа диска и регионов в сыром виде нижележащей инфраструктуры, а не в нейтральном пространстве имён бренда.\n\n**Стало**\n\nТе же поля приведены к нейтральному пространству имён бренда Bitrix Cloud, как в российском сегменте: провайдер `bitrix-cloud`, планы `bc-small`\u002F`bc-medium`\u002F`bc-large`\u002F`bc-xlarge`, `diskType: \"network-ssd\"`, регионы `bc-eu-central`\u002F`bc-eu-west`\u002F`bc-us-east`\u002F`bc-us-west`\u002F`bc-ap-southeast`. Поля `name` и `country` остаются человекочитаемыми (например, «Frankfurt (EU Central)», `DE`). Те же значения возвращаются в полях `provider`\u002F`plan`\u002F`region` ответов `GET \u002Fv1\u002Finfra\u002Fservers` и `GET \u002Fv1\u002Finfra\u002Fservers\u002F:id`.\n\n**Влияние на интеграторов**\n\nСтандартный сценарий не требует изменений: идентификатор, полученный из каталога, по-прежнему передаётся в `POST \u002Fv1\u002Finfra\u002Fservers` без правок. При создании сервера принимаются и новые идентификаторы (`bitrix-cloud`\u002F`bc-small`\u002F`bc-eu-central`), и идентификаторы из прежнего каталога, поэтому существующие интеграции продолжают работать. Поправьте только код, который сверяет идентификаторы ответа с захардкоженными строками из прежнего каталога провайдера, планов или регионов.\n\n### FIX-0625-2: offset внутри подвызовов \u002Fv1\u002Fbatch теперь листает страницы\n\n**Было**\n\nВ [POST \u002Fv1\u002Fbatch](\u002Fdocs\u002Fbatch) подвызовы `list` и `search` молча игнорировали `offset` в `params`: B24 получал неизвестный ключ `offset` вместо `start`, поэтому каждая страница возвращала один и тот же первый набор записей. Три подвызова `contacts.search` с `offset` 0, 50 и 100 отдавали идентичную первую страницу.\n\n**Стало**\n\n`offset` в подвызове `list`\u002F`search` переводится в `start` Bitrix24 (как в одиночном `POST \u002Fv1\u002F{entity}\u002Fsearch`). Те же три подвызова теперь возвращают три разные непересекающиеся страницы. Поведение одиночного эндпоинта не изменилось.\n\n### FIX-0625-3: Файлы и папки: deletedBy у не удалённых объектов теперь null\n\n**Было**\n\n[GET \u002Fv1\u002Ffiles\u002F:id](\u002Fdocs\u002Fentities\u002Ffiles\u002Fget), [GET \u002Fv1\u002Ffiles](\u002Fdocs\u002Fentities\u002Ffiles\u002Flist) и эндпоинты папок возвращали `deletedBy: 0` у не удалённого объекта, хотя поле объявлено как `number | null` с «`null` — объект не удалён». Поле-сосед `deletedAt` при этом корректно отдавало `null`, так что два поля с одинаковым контрактом вели себя по-разному, и проверка `deletedBy !== null` ошибочно считала каждый активный объект удалённым.\n\n**Стало**\n\n`deletedBy` нормализуется в `null` для не удалённых объектов (Битрикс24 хранит в колонке `DELETED_BY` ноль как «нет пользователя»; пользователя с id 0 не существует). У удалённых объектов поле по-прежнему содержит id пользователя, выполнившего удаление.\n\n**Влияние на интеграторов**\n\nПоведение приведено к задокументированному контракту `number | null`. Клиенты, проверявшие `deletedBy === null` \u002F `deletedBy !== null`, теперь получают корректный результат для активных объектов.\n\n### NEW-0625-4: чтение Базы знаний 2.0 — список баз, документы, дерево, поиск\n\nRead-доступ к Базе знаний 2.0: список доступных баз знаний (курсорная пагинация), получение базы знаний и документа по идентификатору (документ — с Markdown-содержимым), дерево документов базы знаний и полнотекстовый поиск по документам. Скоуп `note`.\n\n**Затронутые эндпоинты:** [GET \u002Fv1\u002Fnote\u002Fcollections](\u002Fdocs\u002Fnote\u002Fcollections) (список), [GET \u002Fv1\u002Fnote\u002Fcollections\u002F:id](\u002Fdocs\u002Fnote\u002Fcollections) (одна база), [GET \u002Fv1\u002Fnote\u002Fcollections\u002F:collectionId\u002Fdocuments](\u002Fdocs\u002Fnote\u002Fdocuments) (дерево), [GET \u002Fv1\u002Fnote\u002Fdocuments\u002F:id](\u002Fdocs\u002Fnote\u002Fdocuments) (документ с Markdown), [GET \u002Fv1\u002Fnote\u002Fdocuments\u002Fsearch](\u002Fdocs\u002Fnote\u002Fdocuments) (поиск по `query`).\n\n### FIX-0625-5: методы Базы знаний 2.0 (создание, изменение, загрузка файлов) теперь работают\n\n**Было**\n\nСоздание и изменение баз знаний и документов ([POST \u002Fv1\u002Fnote\u002Fcollections](\u002Fdocs\u002Fnote\u002Fcollections), [PATCH \u002Fv1\u002Fnote\u002Fcollections\u002F:id](\u002Fdocs\u002Fnote\u002Fcollections), [POST \u002Fv1\u002Fnote\u002Fdocuments](\u002Fdocs\u002Fnote\u002Fdocuments), [PATCH \u002Fv1\u002Fnote\u002Fdocuments\u002F:id](\u002Fdocs\u002Fnote\u002Fdocuments)) возвращали `400` с ошибкой валидации Битрикс24, а загрузка вложения ([POST \u002Fv1\u002Fnote\u002Fdocuments\u002F:documentId\u002Ffiles](\u002Fdocs\u002Fnote\u002Ffiles)) сохраняла файл, но не возвращала его идентификатор в `data.id`.\n\n**Стало**\n\nМетоды работают: создание и изменение возвращают `200`, а ответы создания баз знаний, документов и файлов содержат идентификатор в `data.id`. Архивирование, удаление и получение файла работали и раньше.\n\n### FIX-0625-6: \u002Fv1\u002Fme: supportedVisibilities хранилища теперь в верхнем регистре\n\n**Было**\n\n[GET \u002Fv1\u002Fme](\u002Fdocs\u002Fkeys-auth) в блоке `storage` отдавал `supportedVisibilities: [\"private\",\"public\"]` в нижнем регистре, а эндпоинты загрузки принимают только `PRIVATE`\u002F`PUBLIC` (верхний регистр). Агент, скопировавший значение из манифеста, получал `STORAGE_INVALID_VISIBILITY`.\n\n**Стало**\n\n`supportedVisibilities` отдаётся как `[\"PRIVATE\",\"PUBLIC\"]` — ровно те значения, которые принимает параметр `visibility` при загрузке.\n\n**Влияние на интеграторов**\n\nЕсли клиент брал значение `visibility` из `\u002Fv1\u002Fme` и приводил его к верхнему регистру сам — ничего не меняется. Если передавал как есть — теперь загрузка проходит без ошибки.\n\n## 2026-06-24\n\n### FIX-0624-1: логи galaxy-приложения отдают вывод контейнера\n\n**Было**\n\n[GET \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Flogs](\u002Fdocs\u002Finfra\u002Fdeploy\u002Flogs) для galaxy-приложения (`kind=GALAXY_APP`) возвращал только системный журнал хоста (`journalctl`), а не логи самого контейнера приложения — увидеть stdout\u002Fstderr упавшего приложения было нельзя.\n\n**Стало**\n\nДля galaxy-приложения эндпоинт читает stdout\u002Fstderr контейнера (`docker logs`). Чтение read-only: если хост-галактика спит или недоступен, ответ — пустой `data.logs` плюс `data.hint`, хост не будится. Параметр `since` для galaxy-приложений принимает только длительность (`10m`) или метку RFC3339 — человекочитаемые формы `journalctl` («1 hour ago») допустимы лишь для Black Hole-серверов.\n\n### NEW-0624-2: код ошибки GALAXY_APP_START_FAILED при деплое\n\n[POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) для galaxy-приложения возвращает `502 GALAXY_APP_START_FAILED`, когда приложение успешно собралось, но упало или ушло в OOM-перезапуск сразу после старта. Это отдельный код от `GALAXY_APP_BUILD_FAILED` (ошибка сборки): по нему видно, что сборка прошла, а проблема в рантайме (например, превышение лимита памяти). Хвост логов контейнера приходит в поле `buildLog`.\n\n### FIX-0624-3: окно блокирующего пробуждения сервера увеличено до ~5 минут\n\n**Было**\n\nБлокирующее пробуждение — [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fwake](\u002Fdocs\u002Finfra\u002Flifecycle\u002Fwake) с `?wait=true` и автопробуждение спящего сервера при [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) — ждало готовности (статус RUNNING плюс подключённый туннель) примерно до 3 минут, после чего возвращало `504 WAKE_TIMEOUT`.\n\n**Стало**\n\nОсновная фаза ожидания увеличена с ~3 до ~5 минут, а с учётом фазы перезагрузки полный потолок до `504` — около 6.5 минуты. Глубоко «остывший» хост (например, спавшая несколько дней галактика) успевает загрузиться и подключиться, а не получает ложный таймаут. Код ошибки, форма ответа и потолок со стороны прокси прежние.\n\n**Влияние на интеграторов**\n\nЕсли клиент задаёт собственный таймаут на эти вызовы, заложите около 6.5 минуты вместо 3. Прочее поведение прежнее, переписывать интеграцию не нужно.\n\n### NEW-0624-4: параметры placement и graduateFrom при создании сервера\n\n[POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) получил два необязательных параметра. `placement` — `auto` (по умолчанию, поведение прежнее) или `dedicated`: на портале с моделью размещения `galaxies-only` значение `dedicated` создаёт не приложение Galaxy, а отдельную виртуальную машину, проходя те же проверки, что и обычное создание сервера — политику `serverCreation` и квоту серверов на пользователя. `graduateFrom` принимает идентификатор вашего приложения Galaxy (`kind=GALAXY_APP`): после создания выделенного сервера это приложение удаляется. `graduateFrom` ограничен владельцем — чужой или не-Galaxy идентификатор вернёт `404`, ничего не удаляя.\n\nЭто аддитивно: без `placement` или с `placement: \"auto\"` запрос ведёт себя в точности как раньше.\n\nКроме того, при OOM-падении приложения Galaxy ответ [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) с кодом `502 GALAXY_APP_START_FAILED` теперь содержит структурную подсказку `error.hint` с `recoveryAction: \"graduate-to-dedicated-vm\"` — как пересоздать приложение на выделенном сервере через `placement: \"dedicated\"` и `graduateFrom`. Подсказка добавляется только когда причина падения — OOM (превышение лимита памяти контейнера), а не обычный краш.\n\n### FIX-0624-5: создание сервера с кодом в source.content принимает архивы до 500 МБ\n\n**Было**\n\n[POST \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Fcreate) с inline-архивом в `source.content` (одношаговое создание приложения Galaxy) возвращал `413 FST_ERR_CTP_BODY_TOO_LARGE` уже на архивах больше ~750 КБ, хотя в документации поля `source.content` заявлен лимит 500 МБ на тело запроса. Маршрут наследовал глобальный лимит тела 1 МБ.\n\n**Стало**\n\nМаршрут принимает тело до 500 МБ — как и `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy` и `POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fupload`. Лимит из документации теперь действует на самом деле.\n\n### NEW-0624-6: группировка реквизитов по ИНН\u002FОГРН\u002FКПП в aggregate\n\n[POST \u002Fv1\u002Frequisites\u002Faggregate](\u002Fdocs\u002Fentities\u002Frequisites\u002Faggregate) теперь принимает `groupBy` по строковым идентификаторам реквизита: `rqInn`, `rqKpp`, `rqOgrn`, `rqOgrnip`, `rqOkpo`, `rqVatId`, `rqResidenceCountry`, `rqCompanyName`, а также `presetId`, `entityTypeId`, `active`. Раньше группировка по этим полям возвращала `400 INVALID_PARAMS` с пустым списком доступных полей.\n\nГруппировка по `rqInn` — самый быстрый способ найти дубли реквизитов одним вызовом: группы с `count > 1` содержат повторяющиеся ИНН. Прежние вызовы (`groupBy` по `entityTypeId`\u002F`presetId`) работают без изменений. Числовые функции (`sum`\u002F`avg`\u002F`min`\u002F`max`) по этим строковым полям по-прежнему недоступны — они только для группировки.\n\n### FIX-0624-7: availableActions спящего сервера показывает wake\u002Fstart; repair-status сразу `running`\n\n**Было**\n\nДля спящего сервера без туннеля (`SLEEPING` + `blackholeStatus: DISCONNECTED` — обычное состояние остановленного сервера) поле `availableActions` в ответе `422 SERVER_WRONG_STATE` и в `GET \u002Fv1\u002Fme` (`infra.unhealthyServers`) содержало только `[\"repair\",\"delete\"]` — без очевидного способа поднять сервер. Отдельно: сразу после [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Frepair](\u002Fdocs\u002Finfra\u002Flifecycle\u002Frepair) опрос [repair-status](\u002Fdocs\u002Finfra\u002Flifecycle\u002Frepair-status) в первые миллисекунды мог вернуть `{status:\"idle\"}`, и цикл опроса завершался преждевременно.\n\n**Стало**\n\n`availableActions` для любого спящего сервера теперь содержит `[\"wake\",\"start\",\"repair\",\"delete\"]` — оба действия реально принимаются эндпоинтами `\u002Fwake` и `\u002Fstart`. А `repair-status` выставляет `running` синхронно при старте ремонта, поэтому первый же опрос видит `running`, а не `idle`. Прежние вызовы продолжают работать без изменений.\n\n### FIX-0624-8: stage-history?entityType=invoice теперь отдаёт историю смарт-счёта (31)\n\n**Было**\n\n[GET \u002Fv1\u002Fstage-history](\u002Fdocs\u002Fentities-index)`?entityType=invoice` возвращал историю упразднённого старого счёта (entityTypeId 5, status-based: `statusId`\u002F`statusSemanticId`). Текущий смарт-счёт (31) был доступен только под ключом `entityType=new-invoice`. Клиент, работающий со счетами через `\u002Fv1\u002Finvoices` (тип 31), запросив историю под `invoice`, получал чужой упразднённый тип.\n\n**Стало**\n\n`entityType=invoice` отдаёт историю текущего смарт-счёта (entityTypeId 31, stage-based: `stageId`\u002F`stageSemanticId`\u002F`categoryId`) — в одном ряду с `\u002Fv1\u002Finvoices`. Ключ `new-invoice` сохранён как алиас на 31 для обратной совместимости, ломать ничего не нужно.\n\n### NEW-0624-9: эндпоинт эмбеддингов bitrix\u002Fembeddings\n\nПоявился OpenAI-совместимый эндпоинт [POST \u002Fv1\u002Fembeddings](\u002Fdocs\u002Fai\u002Fembeddings) — преобразование текста в векторные представления (эмбеддинги) для семантического поиска, кластеризации, дедупликации и поиска похожих карточек CRM. Модель `bitrix\u002Fembeddings` бесплатная и платформенная, свой ключ провайдера не требуется. В поле `input` принимается строка или массив строк, ответ возвращается в сыром OpenAI-формате: поле `object` со значением `list`, массив `data` с объектами вида `{ object: \"embedding\", embedding, index }` и блок `usage`. Поддерживаются необязательные параметры `encoding_format` (`float` или `base64`) и `dimensions`. Список доступных моделей и их возможностей — [GET \u002Fv1\u002Fmodels](\u002Fdocs\u002Fai\u002Fmodels\u002Flist), у модели эмбеддингов выставлена возможность `embeddings`.\n\n### FIX-0624-10: типы полей календарных событий приведены к реальным ответам\n\n**Было**\n\n[GET \u002Fv1\u002Fcalendar-events\u002Ffields](\u002Fdocs\u002Fentities\u002Fcalendar-events\u002Ffields) объявлял `rrule` как `string`, а `dateCreate` и `updatedAt` — как `datetime`, хотя на чтение `rrule` приходит объектом, а `dateCreate` и `updatedAt` — строкой в формате региональных настроек портала (не ISO 8601). В схеме числилось поле `ownerType`, которого в ответах нет. В объекте `rrule` приходили служебные ключи `~UNTIL` и `UNTIL_TS`, а в элементах списка повторяющихся событий — служебный ключ `RINDEX`.\n\n**Стало**\n\n`\u002Ffields` объявляет `rrule` как `object`, а `dateCreate` и `updatedAt` — как `string`. Фантомное поле `ownerType` убрано из схемы. Служебные ключи `~UNTIL`, `UNTIL_TS` и `RINDEX` больше не попадают в ответы.\n\n**Влияние на интеграторов**\n\nДокументированные поля не изменились — клиент, читавший только их, продолжает работать. Для абсолютной метки времени используйте `from` и `to` (ISO 8601). Значения `dateCreate` и `updatedAt` не разбирайте фиксированным парсером — их формат зависит от региональных настроек портала.\n\n### NEW-0624-11: поле provisionReason в ответе серверов\n\n[GET \u002Fv1\u002Finfra\u002Fservers](\u002Fdocs\u002Finfra\u002Fservers\u002Flist) и [GET \u002Fv1\u002Finfra\u002Fservers\u002F:id](\u002Fdocs\u002Finfra\u002Fservers\u002Fget) теперь возвращают поле provisionReason со значением oom, crash или null — структурный признак причины ошибки galaxy-приложения. Раньше его отдавали только сессионные роуты кабинета, и в Vibecode API приходилось разбирать свободный текст provisionError. Значение oom — сигнал к «выпуску» приложения на выделенный сервер: создайте сервер с параметрами placement равным dedicated и graduateFrom. Поле необязательное и аддитивное — прежние интеграции работают без изменений.\n\n### FIX-0624-12: graduation-сигнал срабатывает при любой нехватке памяти galaxy-приложения\n\n**Было**\n\nПриложение Galaxy, которому не хватило памяти контейнера — и упёршееся в лимит с перезапусками у предела, и исчерпавшее память сразу на старте (например, грузит большую модель), — классифицировалось как обычный крэш: [GET \u002Fv1\u002Finfra\u002Fservers\u002F:id](\u002Fdocs\u002Finfra\u002Fservers\u002Fget) возвращал provisionReason crash, а ответ [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fdeploy](\u002Fdocs\u002Finfra\u002Fdeploy) с 502 GALAXY_APP_START_FAILED шёл без graduation-подсказки. И наоборот, не-OOM краш-луп (необработанное исключение) мог ошибочно помечаться oom.\n\n**Стало**\n\nРеальная нехватка памяти в любой форме — и мгновенная на старте, и постепенный рост до лимита — надёжно даёт provisionReason oom и error.hint с recoveryAction graduate-to-dedicated-vm. Сборочные ошибки и приложения, которые вообще не стартовали (битая команда запуска), остаются crash без graduation.\n\n**Влияние на интеграторов**\n\nМенять ничего не нужно: значение поля и подсказка теперь точнее отражают нехватку памяти. Агент может надёжно ловить provisionReason oom для любого исчерпания памяти и «выпускать» приложение на выделенный сервер.\n\n## 2026-06-23\n\n### NEW-0623-10: подсказка по настройке push-доставки в ошибках подписки на события\n\nОтветы об ошибке `400 NOT_OAUTH_APP` и `400 NO_USER_TOKEN` у [POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fevent-subscriptions](\u002Fdocs\u002Finfra\u002Fevent-subscriptions) теперь содержат поле `error.hint` — текстовую инструкцию, как получить сервер под ключом авторизации (`vibe_app_`) для push-доставки: создать ключ авторизации через `POST \u002Fv1\u002Fapps`, авторизовать приложение на портале, создать новый сервер под этим ключом. Отдельной «миграции» существующего сервера с обычного ключа нет. Поле аддитивное — прежние клиенты не затронуты.\n\n### FIX-0623-1: список действий бизнес-процессов\n\n**Было**\n\n[GET \u002Fv1\u002Fbizproc-activities](\u002Fdocs\u002Fentities\u002Fbizproc-activities) возвращал каждый код действия как объект с числовыми ключами по символам — например, `{\"0\":\"D\",\"1\":\"i\", …}` вместо строки `\"DiskRead\"`. Проверка `Array.includes(code)` не работала.\n\n**Стало**\n\nЭндпоинт возвращает коды действий массивом строк, как и задокументировано.\n\n### FIX-0623-2: ключи ответа поиска дубликатов в camelCase\n\n**Было**\n\n`POST \u002Fv1\u002Fduplicates\u002Ffind` возвращал ключи объекта `data` в верхнем регистре (`LEAD`, `CONTACT`, `COMPANY`), в отличие от остального API в camelCase.\n\n**Стало**\n\nКлючи приходят в camelCase (`lead`, `contact`, `company`); значения (массивы идентификаторов) не меняются.\n\n### FIX-0623-3: поле files эпика Scrum массивом идентификаторов\n\n**Было**\n\n[GET \u002Fv1\u002Fscrum\u002Fepics\u002F:id](\u002Fdocs\u002Fscrum) отдавал поле `files` сырым UF-объектом Битрикс24 (с `VALUE_RAW`, `USER_TYPE_ID` и прочими внутренними метаданными).\n\n**Стало**\n\n`files` — массив идентификаторов вложений (`[417]`) или пустой массив, в едином стиле с остальным API.\n\n### BC-0623-4: создание ключа авторизации только для администраторов\n\n> Поддержка старого формата до: не предусмотрена, ограничение действует сразу\n\n**Было**\n\nСоздать ключ авторизации ([POST \u002Fv1\u002Fkeys](\u002Fdocs\u002Fkeys-auth)) мог любой пользователь портала.\n\n**Стало**\n\nСоздание ключа доступно только администраторам портала, остальным запрос отклоняется.\n\n**Что делать интеграторам**\n\nСоздавайте ключи под учётной записью с правами администратора Битрикс24.\n\n### FIX-0623-5: заголовок Retry-After при ограничении частоты\n\n**Было**\n\nПри ответе `429` (превышение лимита частоты) заголовок `Retry-After` не возвращался, и интегратор не знал, через сколько повторить запрос.\n\n**Стало**\n\nОтвет `429` несёт `Retry-After` с интервалом в секундах. Используйте его как паузу перед повтором.\n\n### FIX-0623-6: удалённый сервер снова отдаёт 404\n\n**Было**\n\n[GET \u002Fv1\u002Finfra\u002Fservers\u002F:id](\u002Fdocs\u002Finfra\u002Fservers\u002Fget) для мягко удалённого сервера возвращал `200` с полным телом и `status: \"deleted\"`, хотя документация обещает `404`. Клиент, опрашивающий эндпоинт и ожидающий `404` как подтверждение удаления, его не получал.\n\n**Стало**\n\nЭндпоинт возвращает `404 NOT_FOUND` для удалённого сервера — так же, как [список](\u002Fdocs\u002Finfra\u002Fservers\u002Flist) и [удаление](\u002Fdocs\u002Finfra\u002Fservers\u002Fdelete), и как описано в документации.\n\n**Влияние на интеграторов**\n\nЕсли ваш код полагался на `200` с `status: \"deleted\"`, переключитесь на проверку `404` (либо на отсутствие сервера в [списке](\u002Fdocs\u002Finfra\u002Fservers\u002Flist)) как на признак удаления.\n\n### NEW-0623-7: Универсальные списки — полный REST API\n\nПоявился раздел [Списки](\u002Fdocs\u002Flists) (scope `lists`): программное управление универсальными списками Битрикс24 — самими списками, их полями, разделами и элементами. Это 24 эндпоинта под `\u002Fv1\u002Flists` поверх методов `lists.*`.\n\nСписок адресуется типом инфоблока (`iblockTypeId` — `lists`, `lists_socnet` или `bitrix_processes`, по умолчанию `lists`) и идентификатором: числовой сегмент пути трактуется как `IBLOCK_ID`, строковый — как символьный `IBLOCK_CODE`. Поля, разделы и элементы доступны вложенными путями.\n\nЕсли модуль «Универсальные списки» не подключён на портале, вызов возвращает `409 LISTS_MODULE_NOT_ENABLED` — это признак выключенного модуля, а не ошибка интеграции.\n\n**Затронутые эндпоинты:** `\u002Fv1\u002Flists`, `\u002Fv1\u002Flists\u002F:iblockId`, `\u002Fv1\u002Flists\u002F:iblockId\u002Ffields`, `\u002Fv1\u002Flists\u002F:iblockId\u002Fsections`, `\u002Fv1\u002Flists\u002F:iblockId\u002Felements`\n\n### FIX-0623-8: флаг isChildrenListEnabled у связей смарт-процессов\n\n**Было**\n\nВложенный флаг связи `isChildrenListEnabled` принимался только как `true`\u002F`false`. Значение `Y`\u002F`N`, как у остальных флагов смарт-процесса, молча сохранялось как выключенное.\n\n**Стало**\n\n[POST \u002Fv1\u002Fsmart-processes](\u002Fdocs\u002Fentities\u002Fsmart-processes\u002Fcreate) и [PATCH \u002Fv1\u002Fsmart-processes\u002F:entityTypeId](\u002Fdocs\u002Fentities\u002Fsmart-processes\u002Fupdate) приводят `Y`\u002F`N` (а также `1`\u002F`0`, `yes`\u002F`no`) к `true`\u002F`false` для `isChildrenListEnabled` в связях.\n\n### FIX-0623-9: фильтр и выбор пользовательских полей сделок\n\n**Было**\n\nПри фильтрации и выборе пользовательских (UF) полей сделки в форме `UF_CRM_*` поле отклонялось с `UNKNOWN_FILTER_FIELD` в фильтре и молча пропускалось из `select`.\n\n**Стало**\n\nПользовательские поля сделок указываются в camelCase (`ufCrmCheckOut`) и работают без изменений в фильтре и выборе.\n\n**Затронутые эндпоинты:** [GET \u002Fv1\u002Fdeals](\u002Fdocs\u002Fentities\u002Fdeals\u002Flist), [POST \u002Fv1\u002Fdeals\u002Fsearch](\u002Fdocs\u002Fentities\u002Fdeals\u002Fsearch)\n\n## 2026-06-22\n\n### NEW-0622-1: связь сделки с контактами\n\nУправление набором контактов сделки: чтение, добавление, замена всего набора, удаление. PUT заменяет весь набор разом. Скоуп `crm`.\n\n**Затронутые эндпоинты:** `GET\u002FPOST\u002FPUT\u002FDELETE \u002Fv1\u002Fdeals\u002F:id\u002Fcontacts` — [Контакты сделки](\u002Fdocs\u002Fentities\u002Fdeals\u002Fcontacts)\n\n### BC-0622-2: список моделей содержит только GA-модели\n\n> Поддержка старого формата до: не предусмотрена, экспериментальные модели не входили в стабильный контракт\n\n**Было**\n\n[GET \u002Fv1\u002Fmodels](\u002Fdocs\u002Fai\u002Fmodels\u002Flist) и список моделей в `\u002Fv1\u002Fme` включали экспериментальные не-GA модели.\n\n**Стало**\n\nПубличный список содержит только GA-модели. Экспериментальные исключены из списка и отклоняются при вызове.\n\n**Что делать интеграторам**\n\nБерите модель из актуального ответа `GET \u002Fv1\u002Fmodels`, не зашивайте идентификаторы экспериментальных моделей.\n\n### FIX-0622-3: авто-пагинация подразделений\n\n**Было**\n\n[GET \u002Fv1\u002Fdepartments](\u002Fdocs\u002Fentities\u002Fdepartments\u002Flist) возвращал только первую страницу при `limit > 50`.\n\n**Стало**\n\nАвто-пагинация собирает все подразделения в один ответ.\n\n## 2026-06-19\n\n### FIX-0619-1: создание документа\n\n**Было**\n\n[POST \u002Fv1\u002Fdocuments](\u002Fdocs\u002Fentities\u002Fdocuments\u002Fcreate) возвращал `422` и не создавал документ.\n\n**Стало**\n\nЭндпоинт создаёт документ из шаблона и возвращает запись.\n\n### FIX-0619-2: авто-пагинация складов\n\n**Было**\n\n`GET \u002Fv1\u002Fwarehouses` и остатки по складу возвращали только первую страницу при `limit > 50`.\n\n**Стало**\n\nАвто-пагинация собирает все записи в один ответ.\n\n**Затронутые эндпоинты:** [GET \u002Fv1\u002Fwarehouses](\u002Fdocs\u002Fentities\u002Fwarehouses\u002Flist), [GET \u002Fv1\u002Fwarehouses\u002F:id\u002Fstock](\u002Fdocs\u002Fentities\u002Fwarehouses\u002Fstock)\n\n### FIX-0619-3: сохранение значений списочных пользовательских полей\n\n**Было**\n\nПри создании и обновлении пользовательского поля типа «список» значения списка терялись.\n\n**Стало**\n\nЗначения списка сохраняются при создании и обновлении.\n\n**Затронутые эндпоинты:** [создание](\u002Fdocs\u002Fuserfields\u002Fcrm\u002Fcreate), [обновление](\u002Fdocs\u002Fuserfields\u002Fcrm\u002Fupdate) пользовательского поля\n\n### FIX-0619-4: частичное обновление позиции корзины\n\n**Было**\n\n[PATCH \u002Fv1\u002Fbasket-items\u002F:id](\u002Fdocs\u002Fentities\u002Fbasket-items\u002Fupdate) не выполнял частичное обновление позиции.\n\n**Стало**\n\nЧастичное обновление работает, в теле обязательно поле `quantity`.\n\n### FIX-0619-5: фильтр и сортировка настроек открытых линий\n\n**Было**\n\nУ [настроек открытых линий](\u002Fdocs\u002Fopenlines\u002Fconfig\u002Flist) фильтр и сортировка работали не для всех полей, а значения при записи не нормализовались.\n\n**Стало**\n\nФильтр и сортировка учитывают схему полей, булевы значения при записи приводятся к формату Битрикс24 (`Y`\u002F`N`).\n\n## 2026-06-18\n\n### NEW-0618-1: группировка в агрегации сделок\n\n[POST \u002Fv1\u002Fdeals\u002Faggregate](\u002Fdocs\u002Fentities\u002Fdeals\u002Faggregate) принимает `groupBy: \"stageSemanticId\"` — разбивка по семантике стадии (в работе, успех, провал) для аналитики воронки.\n\n### NEW-0618-2: пагинация и фильтр истории стадий\n\n`GET \u002Fv1\u002Fstage-history` поддерживает пагинацию (`meta.total`, `meta.hasMore`) и фильтр по типу сущности `entityTypeId`.\n\n### FIX-0618-3: учёт времени задачи не переназначает автора\n\n**Было**\n\n[PATCH \u002Fv1\u002Ftasks\u002F:taskId\u002Ftime\u002F:id](\u002Fdocs\u002Fentities\u002Ftasks\u002Ftime) принимал поле `userId`, но Битрикс24 не переназначает автора записи — значение молча игнорировалось.\n\n**Стало**\n\nПоле `userId` отклоняется с `400` — автора записи учёта времени сменить нельзя.\n\n**Влияние на интеграторов**\n\nНе передавайте `userId` при обновлении записи учёта времени.\n\n### FIX-0618-4: таймзона события календаря\n\n**Было**\n\n[PATCH \u002Fv1\u002Fcalendar-events\u002F:id](\u002Fdocs\u002Fentities\u002Fcalendar-events\u002Fupdate) мог сохранять время в таймзоне пользователя Битрикс24, а не самого события.\n\n**Стало**\n\nТаймзона события сохраняется при обновлении.\n\n## 2026-06-17\n\n### NEW-0617-1: База знаний 2.0 (note.*)\n\nКоллекции, документы и вложения базы знаний: создание, изменение, архивирование и удаление баз знаний и документов, загрузка вложений. Скоуп `note`.\n\n**Затронутые эндпоинты (методы записи):** [POST \u002Fv1\u002Fnote\u002Fcollections](\u002Fdocs\u002Fnote\u002Fcollections), [POST \u002Fv1\u002Fnote\u002Fdocuments](\u002Fdocs\u002Fnote\u002Fdocuments), [POST \u002Fv1\u002Fnote\u002Fdocuments\u002F:documentId\u002Ffiles](\u002Fdocs\u002Fnote\u002Ffiles), а также парные `PATCH` и `DELETE`. Методы чтения добавлены отдельной записью.\n\n## 2026-06-16\n\n### NEW-0616-1: AI follow-up завершённых звонков\n\nAI follow-up по завершённым звонкам. Скоуп `call`.\n\n**В процессе раскатки** — методы выходят в обновлении Битрикс24 `call 26.600.0` и доступны не на всех порталах. Пока обновление не приехало на портал, метод возвращает `422 METHOD_NOT_YET_AVAILABLE` с целевой версией в ответе — это признак раскатки, а не ошибка интеграции.\n\n**Затронутые эндпоинты:** [POST \u002Fv1\u002Fcalls\u002Ffollowups\u002Flist](\u002Fdocs\u002Fcalls\u002Ffollowup\u002Flist), [GET \u002Fv1\u002Fcalls\u002Ffollowups\u002F:callId](\u002Fdocs\u002Fcalls\u002Ffollowup\u002Fget)\n\n### FIX-0616-2: формат ответа транскрипции\n\n**Было**\n\n[POST \u002Fv1\u002Faudio\u002Ftranscriptions](\u002Fdocs\u002Fai\u002Faudio\u002Ftranscriptions) всегда возвращал JSON-объект, даже при `response_format=text`, `srt` или `vtt`.\n\n**Стало**\n\n`text`, `srt`, `vtt` отдают сырое тело в формате `text\u002Fplain`, SubRip или WebVTT. `json` и `verbose_json` отдают JSON-объект.\n\n## 2026-06-12\n\n### BC-0612-1: поле payed заказа только для чтения\n\n> Поддержка старого формата до: не предусмотрена, поле стало read-only\n\n**Было**\n\n`payed` принимался в теле создания и обновления заказа.\n\n**Стало**\n\n`payed` доступно только для чтения — при записи отклоняется.\n\n**Что делать интеграторам**\n\nУберите `payed` из тела `POST \u002Fv1\u002Forders` и `PATCH \u002Fv1\u002Forders\u002F:id`.\n\n**Затронутые эндпоинты:** [POST \u002Fv1\u002Forders](\u002Fdocs\u002Fentities\u002Forders\u002Fcreate), [PATCH \u002Fv1\u002Forders\u002F:id](\u002Fdocs\u002Fentities\u002Forders\u002Fupdate)\n\n## 2026-06-11\n\n### NEW-0611-1: Scrum API\n\nЭпики, привязка задач к эпикам и чтение чата задачи. Скоуп `tasks`.\n\n**Затронутые эндпоинты:** `\u002Fv1\u002Fscrum\u002Fepics`, `\u002Fv1\u002Fscrum\u002Fepics\u002F:id`, `\u002Fv1\u002Fscrum\u002Ftasks\u002F:taskId` — раздел [Scrum](\u002Fdocs\u002Fscrum)\n\n### NEW-0611-2: оценка звонка при завершении\n\n[POST \u002Fv1\u002Fcalls\u002F:callId\u002Ffinish](\u002Fdocs\u002Ftelephony\u002Fcrm\u002Ffinish) принимает оценку завершённого звонка и передаёт её в Битрикс24.\n\n## Смотрите также\n\n- [Обзор API](\u002Fdocs\u002Fquickstart)\n- [Коды ошибок](\u002Fdocs\u002Ferrors)\n- [Batch-запросы](\u002Fdocs\u002Fbatch)\n","2026-07-27",{}]