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

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

### FIX-0803-1: переоткрытие архивированного тикета тоже очищает штамп решения

**Было**

Переоткрытие тикета из статуса `ARCHIVED` в активный (`NEW`/`REVIEWING`/`AWAITING_USER`/`NEEDS_REVIEW`) — через `PATCH /v1/feedback/:id` или `POST /v1/feedback/:id/comments` — не сбрасывало `resolvedAt`/`resolvedBy`, если тикет ранее был решён и затем заархивирован. На чтении (`GET /v1/feedback/:id`) такой тикет выглядел активным и решённым разом. Сброс срабатывал только для источников `RESOLVED`/`WITHDRAWN`.

**Стало**

`ARCHIVED` присоединён к `RESOLVED`/`WITHDRAWN`: переоткрытие из любого закрытого статуса в активный очищает `resolvedAt`/`resolvedBy`. На `PATCH`-пути очищается и `resolution` (в том числе причина архива) — активный тикет решения не несёт; явный `resolution` в том же запросе имеет приоритет. На пути комментария `resolution` равен телу коммента. Переход **в** `ARCHIVED` штамп по-прежнему сохраняет (архивирование хранит историю решения).

### FIX-0803-2: пропавшие байты вложения отдаются как 404, а не как оборванный ответ

`GET /v1/feedback/{id}/attachments/{attId}/file` и `.../thumb` теперь проверяют наличие байтов до отправки заголовков. Если запись о вложении есть, а самих байтов в хранилище нет (последствие ручной уборки или каскадного удаления), ответом будет обычный `404 NOT_FOUND`.

**Было**

Ответ начинался как `200`, а затем обрывался посреди тела: клиент получал усечённую картинку или пустой поток с уже отправленным успешным статусом, и отличить это от медленной сети было нечем.

**Стало**

`404 { "success": false, "error": { "code": "NOT_FOUND", "message": "Not found" } }` — тот же код, что и для чужого или удалённого вложения. Клиентам, которые уже обрабатывают `404` на этих маршрутах, менять ничего не нужно.

Формат ответов и адреса маршрутов не изменились.

### FIX-0803-3: создание сервера честно сообщает, что вернуло уже существующий сервер приложения

Если ключ вызова принадлежит приложению, у которого слот сервера уже занят, [POST /v1/infra/servers](/docs/infra/servers/create) возвращает этот сервер вместо создания нового. Так было и раньше, но узнать об этом из ответа было почти нечем: единственным признаком было недокументированное поле `reused`, а имя в ответе принадлежало существующему серверу, а не запрошенному.

Теперь такой ответ несёт полное раскрытие: `data.reusedReason` со значением `APPLICATION_ALREADY_HAS_SERVER`, `data.requestedName` с эхом переданного вами `name` (всегда, даже если оно совпадает с именем существующего сервера) и массив `warnings` рядом с `data` минимум с одной записью — она называет существующий сервер и предупреждает, что деплой заменит работающий на нём код. Поля `reused` и `deploying` теперь описаны в схеме и в документации.

Точно так же раскрывается и вторая молчаливая потеря: переданные `displayName` и `description` переиспользование **не применяет** — сервер сохраняет собственные имя и описание. Теперь это видно по `data.metaIgnored` и отдельному предупреждению; переименовать сервер можно осознанно через [PATCH /v1/infra/servers/:id](/docs/infra/servers/update).

Если в запросе был передан `source`, а развернуть его в переиспользуемый сервер нельзя (у galaxy-приложения уже есть работающий контейнер, либо это отдельная виртуальная машина), архив отбрасывается — и теперь ответ говорит об этом полем `data.sourceIgnored` и отдельным предупреждением. Раньше архив отбрасывался молча.

**`data.next` в reuse-ответе теперь приходит только тогда, когда перезаписывать нечего.** Раньше поле приходило на любом двухшаговом reuse — в том числе когда возвращённый сервер уже выполнял чужой код, и машинное «следующий шаг — деплой» противоречило подсказке в том же теле. Если сервер может выполнять код, `next` отсутствует намеренно: сначала убедитесь, что сервер тот. Отсутствие `next` — не ошибка.

Два прежних поля изменились по смыслу, хотя менять клиентский код не нужно: `data.hint` на reuse-ответе переписан целиком — вместо «задеплойте сюда» он теперь начинается с `REUSED — no new server was created` и объясняет, чем это чревато; а описание `data.next` в схеме исправлено — раньше оно называло единственной причиной появления поля пустой galaxy-слот, хотя `next` приходит и на reuse-ответе. Прежние поля и коды не изменились, менять ничего не нужно. Но перед вызовом [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy) читайте `reused`: деплой заменяет то, что уже работает на сервере.

### NEW-0803-4: справочник значений списочных свойств каталога

Появилась сущность `catalog-product-property-enums` — все возможные варианты свойства-списка торгового каталога: `GET /v1/catalog-product-property-enums`, `GET /v1/catalog-product-property-enums/:id`, `POST /v1/catalog-product-property-enums/search` и `GET /v1/catalog-product-property-enums/fields`. Сущность только для чтения: записывающие операции и агрегация не зарегистрированы и отвечают `404`, а `data.batch` приходит пустым массивом.

Раньше значение списочного свойства у товара можно было получить только как идентификатор варианта: семейство `/v1/products` отдаёт в `PROPERTY_<N>` объект с числовым `value`, а `GET /v1/products/fields` описывает свойство только именем — развернуть идентификатор в текст было нечем. Теперь список вариантов запрашивается напрямую: `GET /v1/catalog-product-property-enums?filter[propertyId]=166&limit=1000` возвращает пары «идентификатор — читаемый текст», по которым строится карта `String(id)` → `value`.

Фильтр `filter[propertyId]` обязателен: справочник читается по одному свойству за раз, запрос без него отклоняется с `400 MISSING_REQUIRED_FILTER` до обращения к Битрикс24 — на списке и поиске. Проверка смотрит на наличие ключа и не распространяется на подвызовы пакетного запроса. Перечисление есть только у свойств с `propertyType: "L"` — у свойства любого другого типа ответ будет пустым списком, а не ошибкой. Пагинация обычная: `limit` + `offset`, конец выборки — по `meta.hasMore`. Потолок — 5000 записей за вызов. Нужен скоуп `catalog`.

Заодно уточнены существующие страницы. `GET /v1/catalog-products/:id` теперь показывает захваченный ответ со списочным свойством и разбор тройки `value` / `valueEnum` / `valueId`, а также форму значения при `listType: "C"` — голый скаляр `"Y"`/`"N"`, то есть состояние галочки, а не id варианта. `GET /v1/products/:id` объясняет, что пустое значение свойства означает либо незаполненное свойство, либо свойство, обслуживаемое только каталожным семейством, и как различить эти случаи одним перекрёстным вызовом. `GET /v1/products/fields` прямо говорит, что дескриптор `PROPERTY_<N>` несёт только название и куда идти за значениями. В справочнике ошибок исправлен перечень сущностей с обязательным фильтром.

### FIX-0803-5: Обращения в поддержку работают при заморозке баланса

**Было**

На аккаунте, замороженном из-за баланса, все ручки `/v1/feedback` возвращали `402 ACCOUNT_FROZEN` — сообщить о проблеме из продукта было нельзя ровно в тот момент, когда это нужнее всего.

**Стало**

По ключу доступна вся переписка: `POST /v1/feedback` (создать), `GET /v1/feedback` (список), `GET /v1/feedback/{id}` (открыть обращение) и `POST /v1/feedback/{id}/comments` (ответить команде). Все четыре читают и пишут только ваши же данные. Запись вложений (`POST /v1/feedback/attachments`), их скачивание и `PATCH /v1/feedback/{id}` остаются под гейтом заморозки, как и остальные `/v1`-эндпоинты.

### FIX-0803-6: include возвращает полную связанную запись, а не только метаданные

**Было**

При `?include=<relation>` во вложенном объекте приходили только метаданные связи — без `id` и полей связанной записи.

**Стало**

`include` возвращает полную связанную запись (`id` + поля), как и описано в контракте — отдельный запрос за связанной сущностью больше не нужен.

### FIX-0803-7: методы дашборда Открытых линий в раскатке возвращают METHOD_NOT_YET_AVAILABLE

**Было**

На портале, куда обновление ещё не приехало, методы дашборда Открытых линий отдавали сырой `422 BITRIX_ERROR` — по нему нельзя было отличить «метод раскатывается» от реальной ошибки интеграции.

**Стало**

Такой ответ распознаётся и возвращается как `422 METHOD_NOT_YET_AVAILABLE` с версией релиза — понятный сигнал, что метод пока не доступен на этом портале, а не сбой интеграции. Ответ не меняется и при регулярном опросе: такие вызовы больше не засчитываются в защиту от петли ошибок, поэтому вместо понятного `422` не приходит `429 ERROR_LOOP_DETECTED` (для методов, которые не раскатываются, защита работает как раньше).

### FIX-0803-8: запись исходников в хранилище возвращает точные коды ошибок вместо общего 500

**Было**

Клиентские сбои записи в хранилище исходников маскировались общим `500 SOURCE_STORAGE_ERROR` — по нему нельзя было понять, что делать.

**Стало**

Причина различима: при недостатке средств на балансе — `402 BILLING_INSUFFICIENT`, при временном сбое выдачи ключей доступа к хранилищу — `503 STORAGE_STS_UNAVAILABLE` (можно повторить запрос). Таблица кодов на странице source-storage дополнена.

### FIX-0803-9: список значений в фильтре statuses отклоняется чистым 400, а не 500

**Было**

`GET /v1/statuses` со списком значений в фильтре (`{поле: {$in: [...]}}` или массив) уходил в Bitrix24, и метод справочника отвечал по-разному в зависимости от поля: по `id` и `name` — внутренней ошибкой, которая доезжала до клиента как `502 BITRIX_UNAVAILABLE`, по `entityId`, `statusId`, `semantics` и `sort` — ошибкой «значение должно быть строкой», а по `categoryId` — успешным ответом с записями чужой воронки.

**Стало**

Список значений отклоняется до вызова Bitrix24 с `400 UNSUPPORTED_FILTER` по любому полю фильтра: метод справочника не поддерживает его нигде. Принимается одно точное значение (`{поле: значение}`); несколько значений запрашивайте отдельными вызовами или через `POST /v1/batch`.

### FIX-0803-10: /v1/tasks/:taskId/time принимает ключ со скоупом task

**Было**

Эндпоинт учёта времени задачи возвращал `403 INSUFFICIENT_SCOPE` ключу со скоупом `task` — работал только `tasks`, хотя это алиасы одного разрешения.

**Стало**

`task` и `tasks` трактуются как алиасы (как на всех остальных task-эндпоинтах) — ключ с любым из них проходит.

### NEW-0803-11: одношаговое создание galaxy-приложения принимает `healthPath`

Тело [POST /v1/infra/servers](/docs/infra/servers/create) с полем `source` теперь принимает необязательное поле `healthPath` — путь, по которому проверяется готовность приложения внутри контейнера. Валидация та же, что у [POST /v1/infra/servers/{id}/deploy](/docs/infra/deploy/deploy): строка до 500 символов, начинается со `/`. Значение по умолчанию — `/`.

Раньше `healthPath` был объявлен только в теле деплоя, поэтому одношаговый вызов, который платформа сама рекомендует в `GET /v1/me`, отвечал `400 UNKNOWN_PARAM` — а описание в `/v1/me` при этом называло `healthPath` поддерживаемым на галактическом пути. Поле принято именно там, где рекомендация его обещает; на создании отдельного сервера оно игнорируется.

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

Ничего менять не нужно — поле необязательное. Если раньше приходилось разбивать вызов на два шага только ради `healthPath`, теперь достаточно одного.

### FIX-0803-12: `/start` и `/wake` у galaxy-приложения объясняют, почему они не применимы

**Было**

[POST /v1/infra/servers/{id}/start](/docs/infra/lifecycle/start) и [POST /v1/infra/servers/{id}/wake](/docs/infra/lifecycle/wake) проверяли статус раньше типа сервера, поэтому galaxy-приложение вне списка разрешённых статусов (например `RUNNING`, `ERROR` или `STOPPED`) получало текст про standalone-сервер: «Server is RUNNING; /start requires one of SLEEPING, ERROR, PROVISIONING». Формально верно и бесполезно: перечисленные статусы тоже не помогли бы, а о том, что у приложения-контейнера вообще нет облачной машины, не говорилось ничего.

**Стало**

Проверка типа идёт первой — как в `/reboot`. Для galaxy-приложения в таком статусе `message` называет причину («это galaxy-приложение, у него нет облачной машины»), а `userMessage` называет операции, которые действительно работают: `POST /v1/infra/servers/{id}/deploy` (годится в любом из этих статусов) и `POST /v1/infra/servers/{id}/reboot` (только для запущенного или упавшего приложения). Код ошибки не меняется — по-прежнему `SERVER_WRONG_STATE` (422) с полями `currentState` и `availableActions`.

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

Ничего менять не нужно: HTTP-статус и код те же, изменился только текст. Ответ для статусов ИЗ списка разрешённых остался прежним побайтово — в частности, `/start` и `/wake` для спящего galaxy-приложения по-прежнему отвечают задокументированным `VM_MISSING`.

### FIX-0803-13: причина падения galaxy-приложения называется прямо в `provisionError`

**Было**

Когда приложение собиралось, запускалось и тут же падало, `provisionError` содержал только обобщённый вывод: «приложение не удержалось, смотрите логи» или, если сработал признак нехватки памяти, «вероятно, превышен лимит памяти галактики». Настоящая причина — например `TypeError: webidl.util.markAsUncloneable is not a function` из-за несовместимой версии среды — лежала в `buildLog`, а в списке серверов виден только `provisionError`. Поэтому по короткому тексту нельзя было понять, дело в памяти или в коде, и совет «перейдите на отдельный сервер» отправлял по ложному пути.

**Стало**

К той же формулировке дописывается строка из логов контейнера: `… Actual cause from the container logs: <строка>`. Строка выбирается из хвоста, который платформа снимает при отказе: сначала типизированное исключение или код ошибки (`TypeError: …`, `EADDRINUSE`, `FATAL ERROR: … heap out of memory`), затем известные причины сборки, затем последняя строка с признаком ошибки. Хвост проходит ту же очистку, что и `buildLog` — внутренние пути хоста заменяются на `<build-context>`. Если полезной строки в хвосте нет, текст остаётся прежним. Формулировка про память сохраняется и дополняется причиной: признак `oom` иногда срабатывает и там, где память ни при чём, и тогда приклеенная строка — единственная правда, которую видит читатель.

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

Ничего менять не нужно. Прежние подстроки в тексте сохранены, поэтому клиент, который сопоставлял их, продолжает работать; появился только дописанный хвост. Полный лог по-прежнему доступен в `buildLog` (`GET /v1/infra/servers/{id}`).

### FIX-0803-14: `/reboot` спящего galaxy-приложения запускает починку хоста и говорит об этом

**Было**

У спящего galaxy-приложения, чей хост потерял туннель, не было пути обратно. Задокументированный способ разбудить приложение — деплой, а деплой на недостижимый хост падает. Починка туннеля хоста при этом уже запускалась из перезапуска приложения, но запуск лежал ЗА проверкой статуса, которая спящее приложение отклоняет, — то есть до починки дело не доходило.

**Стало**

Перед тем же отказом `422 SERVER_WRONG_STATE` платформа запускает фоновую починку туннеля хоста и, если починка действительно началась, добавляет в ответ необязательный объект `hint` с полями `reason`, `recovery` (какую операцию повторить) и `retryAfterSeconds` (нижняя граница ожидания, не обещание). Если починка не началась — сработал рубильник, туннель на самом деле жив, ремонт уже идёт или машина заблокирована для пробуждения, — `hint` отсутствует: сообщать о запуске, которого не было, нельзя.

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

Ничего менять не нужно: код и HTTP-статус те же, `hint` аддитивен. Клиенту, который читает `hint`, достаточно повторить `POST /v1/infra/servers/{id}/deploy` через названное время.

### FIX-0803-15: zip-архив в исходниках galaxy-приложения больше не падает на распаковке

**Было**

Платформа определяет формат архива по его первым байтам и заявляет `.zip` поддерживаемым, но на галактическом пути (`POST /v1/infra/servers` с полем `source` и `POST /v1/infra/servers/{id}/deploy` для galaxy-приложения) архив передавался на распаковку без подготовки хоста. Если распаковщик на хосте отсутствовал, деплой падал с текстом вида `exec: "unzip": executable file not found in $PATH` — из него не следовало ни что делать, ни что тот же архив в `.tar.gz` прошёл бы.

**Стало**

Перед загрузкой zip-архива платформа доустанавливает распаковщик на хосте (тот же шаг уже выполнялся на отдельном сервере). Шаг идемпотентен: если распаковщик уже есть, он ничего не делает, и на повторных деплоях времени не занимает. Если установить не удалось, архив не загружается вообще, а деплой завершается отказом с честной причиной и подсказкой прислать тот же исходник как `.tar.gz`; текст доступен в `buildLog` и в `provisionError`.

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

Ничего менять не нужно. Деплои с `.tar.gz` идут прежним путём без изменений.

### NEW-0803-16: Версия исходников принимает до 500 МБ, а деплой по нашей ссылке линкуется к версии на любом ключе

**Было**

Архив можно было сохранить версией только до 200 МБ, при том что тот же архив разрешалось передать прямо в теле деплоя до 500 МБ. Крупный проект деплоился единственным способом — целиком в теле запроса.

Отдельно: деплой формы `{"source": {"url": "…"}}` по ссылке, полученной из [GET /v1/infra/servers/:id/sources/:versionId/download](/docs/source-storage), не связывался с версией, если сервер принадлежит персональному ключу `vibe_api_*`. В ответе приходило `data.source.autoSaved: false` и `skippedReason: "external-url-or-toggles-off"`, а у версии оставались пустыми `linkedDeployId` и `deployStatus`.

**Стало**

Потолок версии исходников — 500 МБ на обоих приёмных эндпоинтах: [POST /v1/infra/servers/:id/sources](/docs/source-storage) и [POST /v1/apps/:id/sources](/docs/source-storage). Тело по-прежнему принимается потоком, поэтому размер архива не влияет на скорость приёма. Заявленная в `Content-Length` длина сверх потолка отбивается кодом `413` до чтения тела. Значение публикуется в `capabilities.apps.sourceStorage.limits.maxBlobBytes` у `GET /v1/me` — теперь `524288000`.

Деплой по нашей ссылке связывается с версией независимо от типа ключа-владельца сервера: ответ несёт `data.source.autoSaved: true` и `savedVersionId`, а у версии заполняются `linkedDeployId` и `deployStatus`.

**Затронутые эндпоинты:** [POST /v1/infra/servers/:id/sources](/docs/source-storage), [POST /v1/apps/:id/sources](/docs/source-storage), [POST /v1/infra/servers/:id/deploy](/docs/infra/deploy/deploy), `GET /v1/me`

### FIX-0803-17: отзыв доступа гасит все ключи связки, а не только последний

**Было**

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

**Стало**

Отзыв доступа гасит все ключи, которые пользователь выдал приложению для этого портала, включая ключи прежних авторизаций. Ключ, переживавший отзыв, теперь отвечает `401 KEY_INACTIVE`. Ключи других сотрудников того же портала не затрагиваются. Партнёру достаточно штатной обработки `401 KEY_INACTIVE` — предложить пользователю повторно авторизоваться.
