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

Сохранить снапшот

Явное сохранение архива исходников: когда нужно пометить версию тегом, зафиксировать промежуточный результат без развёртывания или подготовить откат. При деплое снапшот создаётся автоматически.

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

Сохранение снапшота

POST /v1/apps/:id/sources

Принимает сырые байты архива исходного кода в теле запроса. Формат определяется заголовком Content-Type. Метаданные (теги, комментарий, имя файла) передаются через дополнительные заголовки.

Только бинарная загрузка (контракт v2). Эндпоинт принимает сырые байты архива в теле запроса (--data-binary). multipart/form-data больше не поддерживается — такой запрос возвращает 400 INVALID_CONTENT_TYPE. Ранние версии клиентов, которые слали multipart/form-data с полями формы (-F "tag=manual"), нужно обновить: тело — это сами байты архива, а теги, комментарий и имя файла передаются заголовками X-Tags / X-Note / X-Filename (не полями формы и не query-параметрами). MCP-клиенты используют инструмент save_sources, который сам упаковывает и отправляет архив в правильном формате.

Параметры пути

Параметр Тип Описание
id (path) UUID Идентификатор приложения. Получить — GET /v1/apps.

Заголовки запроса

Заголовок Обязательный Описание
Content-Type да Формат архива. Допустимые значения: application/gzip, application/x-tar, application/zip, application/octet-stream. Любое другое значение → 400 INVALID_CONTENT_TYPE. Заявленный формат сверяется с первыми байтами архива: прямое противоречие (application/gzip против zip-байт и наоборот) → 415 UNSUPPORTED_ARCHIVE_FORMAT.
Content-Length да Размер тела в байтах. Загрузка без него (Transfer-Encoding: chunked) не принимается → 411 MISSING_CONTENT_LENGTH.
X-Filename нет Произвольное имя файла для отображения (например, app-v1.tar.gz). Если не указан — имя выводится из Content-Type (например, source.tar.gz для application/gzip). Символы вне набора a-zA-Z0-9._-400 INVALID_FILENAME.
X-Tags нет Теги через запятую. Распознаются manual и published — они защищают версию от автоматической очистки.
X-Note нет Произвольный комментарий, который сохраняется в записи о версии.
X-AI-Session-Id нет Идентификатор AI-сессии. Группирует снапшоты в манифесте по сессии.

Тело запроса

Сырые байты архива, не multipart/form-data. Максимальный размер — 500 МБ.

Тело принимается потоком, поэтому длину нужно объявить заранее: запрос обязан нести Content-Length. Загрузка чанками (Transfer-Encoding: chunked, без длины) отклоняется кодом 411 MISSING_CONTENT_LENGTH — платформа не станет копить архив в памяти, чтобы измерить его за клиента. Заявленная длина сверх потолка отбивается 413 ещё до чтения тела.

В curl длина проставляется сама, если тело берётся из файла (--data-binary @app.tar.gz). Ручной поток из пайпа (... | curl --data-binary @-) длины не даёт — сохраните архив во временный файл или задайте Content-Length явно.

Ответ при успешном сохранении

HTTP 201:

JSON
{
  "success": true,
  "data": {
    "versionId": "v3",
    "id": "cmszu9y12190j4bmj3hhsno39",
    "filename": "2026-05-21T10-15-30-000Z-v3.tar.gz",
    "contentType": "application/gzip",
    "sha256": "a3f5d8b2c1e9f4...",
    "size": 184320,
    "timestamp": "2026-05-21T10:15:30.000Z",
    "tags": [],
    "note": null,
    "deduplicated": false
  }
}

Поля ответа

Поле Тип Описание
data.versionId string Идентификатор версии вида v<N>.
data.id string Внутренний идентификатор записи снапшота. Приходит только в ответе на сохранение: список версий и метаданные одной версии этого поля не отдают.
data.filename string Имя файла архива в хранилище.
data.contentType string Тип архива: application/gzip, application/zip, application/x-tar или application/octet-stream.
data.sha256 string SHA-256 содержимого архива. Используется для дедупликации в пределах владельца.
data.size number Размер архива в байтах.
data.timestamp string Время сохранения (ISO 8601, UTC).
data.tags string[] Активные теги версии: manual, published.
data.note string | null Комментарий из заголовка X-Note или null.
data.deduplicated boolean true, если архив с тем же содержимым уже был сохранён у этого владельца — возвращена существующая версия.

Идемпотентность

Повторное сохранение архива с тем же содержимым даёт тот же sha256 и не создаёт новую версию. Эндпоинт возвращает HTTP 201 с deduplicated: true и versionId уже существующей версии. Если при повторном сохранении переданы новые теги или комментарий — они добавляются к существующей версии (теги объединяются, комментарий перезаписывается).

Область действия — один владелец. Совпадение ищется среди версий того же приложения для POST /v1/apps/:id/sources и того же сервера для POST /v1/infra/servers/:id/sources. Один и тот же архив, сохранённый на двух разных серверах или под двумя приложениями, даёт две версии и два объекта в хранилище — у каждого владельца своя история версий.

Совпадение определяется по содержимому, а значит — только после того, как тело целиком принято: платформа считает sha256 на лету поверх приходящего потока. Поэтому повторное сохранение того же архива идёт столько же времени и передаёт столько же байт, сколько первое. Результат при этом прежний: новая версия не создаётся, в ответе deduplicated: true.

Дедуплицированное сохранение проверка перед POST /v1/apps/:id/publish принимает наравне с настоящим: сохранение отмечает версию как заново предъявленную, и возраст снапшота отсчитывается от этой отметки. Поле data.timestamp при этом остаётся временем создания версии — оно не сдвигается, поэтому имя файла в депо и место версии в политике хранения не меняются. Если после 409 SNAPSHOT_REQUIRED вы сохранили те же самые исходники, повторный publish пройдёт — менять байты, чтобы обновить снапшот, не нужно.

Ответ при отключённом сохранении

HTTP 200 — снапшот не создан:

JSON
{
  "success": true,
  "data": {
    "skipped": true,
    "reason": "DISABLED_GLOBALLY"
  }
}

reason"DISABLED_GLOBALLY" (платформа не включила функцию) или "DISABLED_FOR_PORTAL" (владелец портала отключил для своего портала). При любом из вариантов POST /v1/apps/:id/publish не проверяет наличие снапшота.

Примеры

curl — tar.gz

Terminal
curl -X POST https://vibecode.bitrix24.tech/v1/apps/<APP_ID>/sources \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Content-Type: application/gzip" \
  -H "X-Note: Добавил OAuth-флоу" \
  -H "X-Tags: manual" \
  --data-binary @app-sources.tar.gz

curl — zip

Terminal
curl -X POST https://vibecode.bitrix24.tech/v1/apps/<APP_ID>/sources \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Content-Type: application/zip" \
  --data-binary @app-sources.zip

JavaScript

javascript
const archive = await fs.readFile('app-sources.tar.gz')

const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/apps/${appId}/sources`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': process.env.VIBE_APP_KEY,
      'Content-Type': 'application/gzip',
      'X-Note': 'Добавил OAuth-флоу',
    },
    body: archive,
  },
)
const json = await res.json()
console.log(json.data.versionId, 'deduplicated:', json.data.deduplicated)

Коды ошибок

HTTP Код Когда возвращается
400 INVALID_CONTENT_TYPE Content-Type не входит в список допустимых форматов архива. Формат multipart/form-data не принимается — отправляйте сырые байты архива.
400 INVALID_BLOB Тело запроса не является бинарным буфером (например, передан текст).
400 INVALID_FILENAME Заголовок X-Filename содержит символы вне набора a-zA-Z0-9._-.
400 INVALID_TAGS Тег из заголовка X-Tags содержит недопустимые символы (разрешены a-zA-Z0-9_-).
400 STORAGE_UPLOAD_LENGTH_MISMATCH Объявленный Content-Length не совпал с фактическим размером тела. Проверяется в конце потока, поэтому отказ приходит после передачи байтов. Пересчитайте длину и повторите.
402 BILLING_INSUFFICIENT Недостаточно средств на балансе — запись в хранилище приостановлена. Пополните баланс и повторите.
403 SOURCE_APP_ID_MISMATCH Вызов сделан ключом авторизации vibe_app_…, выписанным на другое приложение. Такой ключ обращается только к снапшотам своего приложения, даже если оба приложения создал один автор.
403 NOT_AUTHORIZED Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами.
403 INFRA_FORBIDDEN_FOR_COWORK_KEY Вызов сделан ключом Cowork/Code — такой ключ работает только с данными, изменяющие операции ему закрыты. Что делать — Проектный ключ для деплоя.
404 APP_NOT_FOUND Приложение не существует, удалено или принадлежит другому порталу.
411 MISSING_CONTENT_LENGTH Запрос пришёл без Content-Length (загрузка чанками). Объявите длину тела и повторите.
413 Размер архива превышает лимит в 500 МБ.
415 UNSUPPORTED_ARCHIVE_FORMAT Первые байты архива прямо противоречат заявленному Content-Type: объявлен application/gzip, а прислан zip, или наоборот. Тело ответа несёт error.hint.declared и error.hint.detected. Типы application/x-tar и application/octet-stream под этот отказ не попадают — их сигнатура не читается в первых байтах.
500 SOURCE_STORAGE_ERROR Внутренняя ошибка хранилища.
503 STORAGE_STS_UNAVAILABLE Хранилище временно недоступно (сбой выдачи временных ключей доступа) — повторите запрос.

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

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