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

Снапшот перед публикацией

Публикация приложения требует сохранённого снимка исходников. Здесь — что именно проверяется, что приходит в отказе 409 SNAPSHOT_REQUIRED и как опубликовать исходники, которые автосейв деплоя сохранил у сервера, а не у приложения.

Обзор хранилища и справочник эндпоинтов — Хранилище исходного кода.

Гарантия наличия снапшота перед публикацией

POST /v1/apps/:id/publish проверяет, что у приложения есть сохранённый снапшот. Возраст снапшота на исход проверки не влияет — версия, сохранённая давно, подходит наравне с только что созданной. Отказ 409 SNAPSHOT_REQUIRED приходит, когда пригодного снапшота нет: его не сохраняли либо файлы сохранённой версии удалены. В подсказке сказано, какой вызов нужно сделать перед повторной публикацией.

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

Пример отказа:

JSON
{
  "success": false,
  "error": {
    "code": "SNAPSHOT_REQUIRED",
    "message": "Publish requires a saved source snapshot for this app. Call POST /v1/apps/:id/sources, or publish with sourceServerId set to the server you deployed to — a deploy auto-save is stored on that server, not on the app.",
    "hint": {
      "requiredAction": "POST /v1/apps/:id/sources",
      "reason": "app_snapshot_missing",
      "serverKeyedSources": {
        "requiredAction": "POST /v1/apps/:id/publish with sourceServerId (the server you deployed to)",
        "bodyField": "sourceServerId",
        "header": "x-source-server",
        "listEndpoint": "GET /v1/infra/servers/:serverId/sources",
        "registryEndpoint": "GET /v1/me/sources"
      },
      "toolName": "save_sources",
      "lastSnapshot": null
    }
  }
}

Блок hint.lastSnapshot приходит непустым, только когда платформа проверяет возраст снапшота. В нём timestamp — время создания версии, presentedAt — время, когда её последний раз предъявили сохранением, а ageMinutes считается от presentedAt: у дедуплицированного сохранения эти два значения расходятся. Поле hint.freshnessWindowMinutes приходит по тому же условию — это длина окна свежести в минутах.

Поле hint.lastSnapshot равно null, когда пригодного снапшота нет — его не сохраняли либо файлы сохранённой версии удалены. Пока возраст снапшота не проверяется, отказ приходит только по этой причине, поэтому поле в нём всегда null.

hint.reason называет причину отказа одним из двух значений: app_snapshot_missing, server_snapshot_missing. Ещё два значения — app_snapshot_stale и server_snapshot_stale — приходят, только когда платформа проверяет возраст снапшота. Блок hint.serverKeyedSources приходит только когда сервер в запросе не указан, — он объясняет, что делать, если исходники сохранил автосейв деплоя.

Публикация исходников с сервера

Автосейв деплоя сохраняет исходники у того сервера, на который вы деплоили. Если сервер принадлежит не OAuth-ключу публикуемого приложения — например, деплой шёл под личным ключом vibe_api_ — снапшот хранится у сервера (appId пустой), и проверка публикации, которая по умолчанию смотрит снапшоты приложения, его не находит. Деплой при этом честно отвечает autoSaved: true: байты сохранены.

Чтобы опубликовать именно их, назовите сервер:

Terminal
curl -X POST "https://vibecode.bitrix24.tech/v1/apps/$APP_ID/publish" \
  -H "X-API-Key: $VIBE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sourceServerId": "'"$SERVER_ID"'"}'

Тот же смысл у заголовка X-Source-Server: <serverId>.

Что важно знать:

  • Сервер платформа не подбирает. Идентификатор передаёте вы — это тот же :id, по которому вы вызывали деплой. Автоподбор «ближайшего» сервера опубликовал бы исходники, которых никто не называл.

  • Права на сервер проверяются отдельно от прав на приложение. Публиковать исходники сервера может ключ-владелец сервера, личный ключ того же пользователя или администратор портала. Сервера нет, он из другого портала или удалён — 404 SERVER_NOT_FOUND. Прав нет — 403 NOT_AUTHORIZED.

  • Нумерация версий у сервера своя. Вместе с sourceServerId поле sourceVersionId означает версию ЭТОГО сервера: v3 сервера и v3 приложения — разные версии.

  • Версию сервера публикация не помечает. Своей версии приложения публикация ставит тег published и пишет отметку о публикации, а версии сервера — нет. У серверной версии те же поля хранят историю ДЕПЛОЯ (GET /v1/infra/servers/:serverId/sources их и показывает), а тег published — это бессрочное хранение и запрет на удаление. Публикация одного приложения не меняет ни то, ни другое у версии, которая ему не принадлежит. Что именно опубликовано, видно в журнале действий записью о публикации.

  • У опубликованной версии сервера нет бессрочного хранения. Раз тега на ней нет, она подчиняется обычным правилам очистки: платформа всегда бережёт пять последних версий, плюс по одной версии на день за последние 14 дней и по одной на неделю за последние 4 недели. Новые деплои на тот же сервер вытесняют опубликованную версию из этих правил, и она может быть удалена, пока приложение остаётся опубликованным. Об этом же напоминает строка в warnings в ответе на публикацию. Чтобы сохранить версию навсегда, пометьте её сами — это ваше решение, потому что бессрочное хранение занимает место и запрещает удаление:

    Terminal
    curl -X PATCH "https://vibecode.bitrix24.tech/v1/infra/servers/$SERVER_ID/sources/v3" \
      -H "X-API-Key: $VIBE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"tags": ["manual"]}'

    Теги заменяются целиком: если у версии уже есть свои метки, перечислите их в том же массиве рядом с manual. Готовый список приходит в той самой строке warnings.

  • Без sourceServerId всё работает как раньше — проверка ищет сохранённый снапшот приложения, ставит тег и отметку о публикации.

  • Заголовок проверяется так же, как поле, — но только когда поля нет. Если sourceServerId пришёл в теле, он и решает: заголовок не читается, и что бы в нём ни лежало, на ответ это не влияет. Если поля в теле нет, заголовок проверяется строго: не в форме UUID или переданный дважды — 400 VALIDATION_ERROR. Молча игнорировать его в этом случае нельзя, иначе публикация взяла бы снапшот приложения, пока вы думаете, что назвали сервер.

  • Хранилище исходников выключено для портала — селектор не используется вовсе, публикация проходит, а в warnings приходит строка о том, что переданный sourceServerId не пригодился.

Где взять идентификатор сервера, если он не сохранился: GET /v1/infra/servers/:serverId/sources перечисляет версии конкретного сервера, а GET /v1/me/sources — сводный реестр всех источников, доступных ключу, по серверам и приложениям.

Параметры для повторной публикации той же версии (не самой свежей):

  • sourceVersionId в теле — формат v<N>, например "sourceVersionId": "v3".
  • Заголовок x-source-version: v3 — альтернатива телу.

Коды ошибок

Отказы POST /v1/apps/:id/publish, специфичные для проверки снапшота:

HTTP Код Когда возвращается
409 SNAPSHOT_REQUIRED Сохранённого снапшота нет либо файлы сохранённой версии удалены (только при включённом сохранении). В ответе — поле hint с описанием действия.

Полный справочник кодов — Коды ошибок.

Смотрите также