For AI agents: markdown of this page — /docs-content-en/source-storage.md documentation index — /llms.txt

Source code storage

Source code storage automatically saves snapshots of your application's code on every deploy. If another person or a new AI session starts working on the application, the latest version can be downloaded so they can continue from the same point — the code is not lost.

Why it matters

When you iterate on an application, it is important not to lose a working version of the code. Storage does this automatically: on every successful deploy the platform saves a snapshot of the sources. If later another person or a new AI session starts working on the application, they download the latest version and continue from the same point, without manually sending archives back and forth.

A snapshot is tied to the application, not to a specific developer: the code stays with the application even when the team changes.

How it differs from Git and GitHub

This is not a version control system and not a replacement for Git. Storage solves a narrower task — a safety net and project handoff.

Source code storage Git / GitHub
What it stores whole archive snapshots of code change history line by line
When it saves automatically on every deploy manually, by a developer's command
Are Git skills required no yes
Branches, merges, version comparison no yes

Need team development with branches and history — use Git. Just need to avoid losing working code and be able to return to a previous version — storage is enough.

How it works

  1. You iterate on the application and run a deploy.
  2. The platform saves a snapshot of the sources by itself — the next version: v1, v2, v3, …
  3. At any time, view the list of versions and download the one you need.
  4. Before publishing the application, the platform checks that a fresh snapshot exists.
code edit  →  deploy  →  snapshot vN saved automatically
                                  ↓
        list of versions  →  download any  →  continue work

Core concepts

  • Snapshot — an archived copy of the application's source code at the moment of saving.
  • Version vN — the sequential number of a snapshot (v1, v2, …). The most recent one is considered current.
  • Tag — a manual or published label: marks a version as important so that automatic cleanup does not delete it.
  • Deduplication — if the code has not changed, no new snapshot is created; the already existing version is returned.

Access and endpoints

Base URL: https://vibecode.bitrix24.com/v1
Authorization: the X-Api-Key header. Snapshots can be managed by the application authorization key (vibe_app_*), the application author's personal key (vibe_api_*), or a Bitrix24 account administrator.
Allowed archive formats: application/gzip, application/x-tar, application/zip, application/octet-stream.
Body limit: 200 MB per request.

Endpoints:

Method Path Action
POST /v1/apps/:id/sources Save a snapshot
GET /v1/apps/:id/sources List versions
GET /v1/apps/:id/sources/:versionId Single-version metadata
GET /v1/apps/:id/sources/:versionId/download Signed download link
PATCH /v1/apps/:id/sources/:versionId Update tags / note
POST /v1/apps/:id/sources/:versionId/tag Add or remove a tag
DELETE /v1/apps/:id/sources/:versionId Delete a version
POST /v1/apps/:id/sources/cleanup Bulk cleanup of old versions

Saving sources

Since version 2026-05-23 the platform automatically saves the source bytes to storage on every successful deployment. This applies to:

  • POST /v1/infra/servers/:id/deploy { source: { content: <base64> } } — the inline bytes are saved as a new version
  • POST /v1/infra/servers/:id/deploy { source: { url: <signed URL from storage> } } — the existing version is linked to the deployment identifier
  • POST /v1/infra/servers/:id/deploy { source: { versionId: 'vN' } } — the same

Galaxy apps accept only source.content. The source.url and source.versionId variants from the list above work on separate virtual machines, whose kind is STANDALONE. For a Galaxy app, whose kind is GALAXY_APP, the code source is passed only as inline source.content — a request with source.url or source.versionId is rejected with 400 GALAXY_DEPLOY_CONTENT_ONLY. See Galaxy apps for details.

Exception: deployment with an external URL (not from Vibecode storage) returns 409 SNAPSHOT_REQUIRED. To work around it — either first upload an archive via POST /v1/apps/:id/sources and deploy via {source: {versionId: 'vN'}}, or pass the X-Skip-Source-Snapshot: <reason> header to explicitly opt out.

Deploy response: the `source` block

The POST /v1/infra/servers/:id/deploy response includes a data.source block — the auto-save outcome. An AI agent or client reads it to tell whether the code reached storage:

JSON
{
  "success": true,
  "data": {
    "status": "running",
    "appUrl": "https://app-b7c1e2a4.vibecode.bitrix24.com",
    "source": {
      "autoSaved": true,
      "savedVersionId": "v4",
      "sha256": "a3f5d8b2c1e9f4...",
      "linkedDeployId": "deploy:2026-05-21T10:15:30.000Z",
      "skippedReason": null
    }
  }
}
Field Type Description
source.autoSaved boolean true — snapshot saved; false — skipped (reason in skippedReason)
source.savedVersionId string | absent Identifier of the created version (vN) when autoSaved: true
source.sha256 string | absent SHA-256 of the saved archive
source.linkedDeployId string | absent Identifier of the deployment the snapshot is linked to
source.skippedReason string | null null on success; otherwise the skip reason (see below)

The deployment completes successfully (200) regardless of the source block — auto-save is best-effort and never fails the deployment. Values of skippedReason:

skippedReason When
feature-disabled-platform Source deposit is disabled at the platform level
feature-disabled-portal The Bitrix24 account owner disabled source deposit for their Bitrix24 account
external-url-or-toggles-off The source is an external URL (not from Vibecode storage)
save-failed Transient storage error — retry the deploy or save manually via POST /v1/apps/:id/sources
<header value> X-Skip-Source-Snapshot: <reason> was passed — the snapshot was skipped intentionally

To confirm the code was deposited, check data.source.autoSaved === true and store savedVersionId for later rollback or handoff.

When a snapshot is not created

If source saving is disabled in the Bitrix24 account or the source is an external URL (not Vibecode storage), no snapshot is created and the deployment completes normally. To save a version explicitly, upload an archive via POST /v1/apps/:id/sources.

When `POST /sources` is still needed explicitly

Only three scenarios:

  1. Mark a version — set a manual or published tag so the version is stored indefinitely.
  2. Save without deploying — save an intermediate result for handoff to another developer.
  3. Preparing for a rollback — a snapshot of the initial state before a risky change.

When to call

Typical scenario for an AI agent (since 2026-05-23 an explicit POST /sources call before deploy is not required — the platform saves automatically):

  1. Changed the code → POST /v1/infra/servers/:id/deploy — sources are saved automatically.
  2. POST /v1/apps/:id/publish — publishes the application in the Bitrix24 catalog. The published tag is added to the snapshot automatically.
  3. Repeated the cycle on the next change.

Scenario for handing the project to a new developer or a new AI session:

  1. GET /v1/apps/:id/sources — list of available versions.
  2. GET /v1/apps/:id/sources/:versionId/download — signed link to the archive.
  3. Download the archive, unpack it, and continue work.

For MCP clients: the save_sources tool packs the file tree into tar.gz on the client side and sends it in a single request. The load_sources tool downloads the latest version and unpacks it back into a file tree. A direct HTTP endpoint call is available for clients that pack the archive themselves.

Snapshot presence guarantee before publishing

POST /v1/apps/:id/publish checks for a snapshot no older than 10 minutes. If there is no snapshot or it is stale — 409 SNAPSHOT_REQUIRED is returned with a hint about which call to make before retrying publishing.

The check works only if source saving is enabled in the Bitrix24 account (enabled by default; the Bitrix24 account owner can disable it — see the "Disabling for a Bitrix24 account" section).

Rejection example:

JSON
{
  "success": false,
  "error": {
    "code": "SNAPSHOT_REQUIRED",
    "message": "Deploy requires a recent source snapshot. Call POST /v1/apps/:id/sources first.",
    "hint": {
      "requiredAction": "POST /v1/apps/:id/sources",
      "toolName": "save_sources",
      "freshnessWindowMinutes": 10,
      "lastSnapshot": {
        "versionId": "v3",
        "timestamp": "2026-05-21T09:42:11.000Z",
        "ageMinutes": 23
      }
    }
  }
}

The hint.lastSnapshot field is null if there is not yet a single snapshot for the application.

Parameters for re-publishing the same version (not the most recent one):

  • sourceVersionId in the body — format v<N>, for example "sourceVersionId": "v3".
  • The x-source-version: v3 header — an alternative to the body.

Version lifetime (retention policy)

After saving, a version goes through automatic cleanup on a Grandfather-Father-Son schedule:

  1. The last 5 versions are always kept, regardless of tags.
  2. A daily grid over 14 days — one version for each calendar day (UTC).
  3. A weekly grid over 4 weeks — one version per every 7 days.
  4. The published and manual tags — kept indefinitely, never deleted automatically.

For on-demand custom cleanup — POST /v1/apps/:id/sources/cleanup. Versions with the published and manual tags are excluded from it as well.

Saving a snapshot

POST /v1/apps/:id/sources

Accepts the raw bytes of the source code archive in the request body. The format is determined by the Content-Type header. Metadata (tags, note, filename) is passed via additional headers.

Binary upload only (contract v2). The endpoint accepts the raw archive bytes in the request body (--data-binary). multipart/form-data is no longer supported — such a request returns 400 INVALID_CONTENT_TYPE. Early client versions that sent multipart/form-data with form fields (-F "tag=manual") must be updated: the body is the archive bytes themselves, and the tags, note, and filename are passed via the X-Tags / X-Note / X-Filename headers (not as form fields and not as query parameters). MCP clients use the save_sources tool, which packs and sends the archive in the correct format itself.

Path parameters

Parameter Type Description
id (path) UUID Application identifier. Get it via GET /v1/apps.

Request headers

Header Required Description
Content-Type yes Archive format. Allowed values: application/gzip, application/x-tar, application/zip, application/octet-stream. Any other value → 400 INVALID_CONTENT_TYPE.
X-Filename no An arbitrary display filename (for example, app-v1.tar.gz). If not provided — the name is derived from Content-Type (for example, source.tar.gz for application/gzip).
X-Tags no Comma-separated tags. manual and published are recognized — they protect the version from automatic cleanup.
X-Note no An arbitrary note that is saved in the version record.
X-AI-Session-Id no AI session identifier. Groups snapshots in the manifest by session.

Request body

Raw archive bytes, not multipart/form-data. Maximum size — 200 MB.

Response on successful save

HTTP 201:

JSON
{
  "success": true,
  "data": {
    "versionId": "v3",
    "id": "b7c1e2a4-9f5d-4c3a-8e21-0a1b2c3d4e5f",
    "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
  }
}

Response fields

Field Type Description
data.versionId string Version identifier of the form v<N>.
data.id string Internal identifier of the snapshot record (UUID).
data.filename string Name of the archive file in storage.
data.contentType string Archive type: application/gzip, application/zip, application/x-tar, or application/octet-stream.
data.sha256 string SHA-256 of the archive contents. Used for deduplication.
data.size number Archive size in bytes.
data.timestamp string Save time (ISO 8601, UTC).
data.tags string[] Active version tags: manual, published.
data.note string | null Note from the X-Note header or null.
data.deduplicated boolean true if an archive with the same contents was already saved — the existing version is returned.

Idempotency

Re-saving an archive with the same contents yields the same sha256 and does not create a new version. The endpoint returns HTTP 201 with deduplicated: true and the versionId of the already existing version. If new tags or a note are passed on re-save — they are added to the existing version (tags are merged, the note is overwritten).

In both cases the App.lastSourceSavedAt mark is updated — the freshness check before POST /v1/apps/:id/publish accepts a deduplicated save on a par with a real one.

Response when saving is disabled

HTTP 200 — no snapshot created:

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

reason"DISABLED_GLOBALLY" (the platform has not enabled the feature) or "DISABLED_FOR_PORTAL" (the Bitrix24 account owner disabled it for their Bitrix24 account). In either case POST /v1/apps/:id/publish does not check for a snapshot.

Examples

curl — tar.gz

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/apps/<APP_ID>/sources \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Content-Type: application/gzip" \
  -H "X-Note: Added OAuth flow" \
  -H "X-Tags: manual" \
  --data-binary @app-sources.tar.gz

curl — zip

Terminal
curl -X POST https://vibecode.bitrix24.com/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.com/v1/apps/${appId}/sources`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': process.env.VIBE_APP_KEY,
      'Content-Type': 'application/gzip',
      'X-Note': 'Added OAuth flow',
    },
    body: archive,
  },
)
const json = await res.json()
console.log(json.data.versionId, 'deduplicated:', json.data.deduplicated)

Error codes

HTTP Code When returned
400 INVALID_CONTENT_TYPE Content-Type is not in the list of allowed archive formats. multipart/form-data is not accepted — send the raw archive bytes.
400 INVALID_BLOB The request body is not a binary buffer (for example, text was passed).
400 INVALID_FILENAME The X-Filename header contains characters outside the a-zA-Z0-9._- set.
400 INVALID_TAGS A tag from the X-Tags header contains invalid characters (a-zA-Z0-9_- are allowed).
403 NOT_AUTHORIZED Only the application author, the application OAuth key, or a Bitrix24 account administrator can manage snapshots.
404 APP_NOT_FOUND The application does not exist, was deleted, or belongs to another portal.
413 The archive size exceeds the 200 MB limit.
500 SOURCE_STORAGE_ERROR Internal storage error.

Updating version metadata

PATCH /v1/apps/:id/sources/:versionId

Updates the tags and/or note of an existing version without re-uploading the archive. Convenient if you need to add a tag or adjust a note after the fact.

Path parameters

Parameter Type Description
id (path) UUID Application identifier.
versionId (path) string Version identifier of the form v<N>.

Body fields

Field Type Required Description
tags string[] no The new full list of tags. Replaces the existing tags entirely. If the field is absent — the tags are not changed.
note string | null no Note. A string — overwrites the current one. null — clears the note. If the field is absent — the note is not changed.

You can pass only tags, only note, or both fields at once.

Response

HTTP 200:

JSON
{
  "success": true,
  "data": {
    "versionId": "v3",
    "tags": ["manual"],
    "note": "Final version before release"
  }
}

Examples

curl — add the `manual` tag

Terminal
curl -X PATCH https://vibecode.bitrix24.com/v1/apps/<APP_ID>/sources/v3 \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tags": ["manual"] }'

curl — clear the note

Terminal
curl -X PATCH https://vibecode.bitrix24.com/v1/apps/<APP_ID>/sources/v3 \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "note": null }'

JavaScript

javascript
await fetch(
  `https://vibecode.bitrix24.com/v1/apps/${appId}/sources/v3`,
  {
    method: 'PATCH',
    headers: {
      'X-Api-Key': process.env.VIBE_APP_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ tags: ['manual'], note: 'Final version' }),
  },
)

Error codes

HTTP Code When returned
400 INVALID_METADATA The body failed validation (invalid tags or field types).
400 INVALID_VERSION_ID The versionId format does not match v<non-negative integer>.
403 NOT_AUTHORIZED Only the application author, the application OAuth key, or a Bitrix24 account administrator can manage snapshots.
404 APP_NOT_FOUND The application does not exist, was deleted, or belongs to another portal.
404 VERSION_NOT_FOUND A version with this versionId does not exist or was deleted.

List versions

GET /v1/apps/:id/sources

Returns current (not deleted) versions, at most 500 per call, in descending order of save time — the most recent first.

Path parameters

Parameter Type Description
id (path) UUID Application identifier.

Response

HTTP 200:

JSON
{
  "success": true,
  "data": {
    "totalVersions": 3,
    "currentVersionId": "v3",
    "totalSizeBytes": 552960,
    "versions": [
      {
        "versionId": "v3",
        "filename": "2026-05-21T10-15-30-000Z-v3.tar.gz",
        "contentType": "application/gzip",
        "timestamp": "2026-05-21T10:15:30.000Z",
        "size": 184320,
        "sha256": "a3f5d8b2c1e9f4...",
        "tags": [],
        "savedBy": {
          "userId": "8f1a2b3c-...",
          "session": "claude-session-2026-05-21"
        },
        "linkedDeployId": null,
        "deployStatus": null,
        "note": "Added OAuth flow"
      },
      {
        "versionId": "v2",
        "filename": "2026-05-20T18-02-11-000Z-v2-published.tar.gz",
        "contentType": "application/gzip",
        "timestamp": "2026-05-20T18:02:11.000Z",
        "size": 184320,
        "sha256": "9e7c4b2a1f8d3...",
        "tags": ["published"],
        "savedBy": {
          "userId": "8f1a2b3c-...",
          "session": null
        },
        "linkedDeployId": "publish:2026-05-20T18:05:42.000Z",
        "deployStatus": "success",
        "note": null
      },
      {
        "versionId": "v1",
        "filename": "2026-05-20T09-44-50-000Z-v1.tar.gz",
        "contentType": "application/gzip",
        "timestamp": "2026-05-20T09:44:50.000Z",
        "size": 184320,
        "sha256": "5a8d3e2f1c4b9...",
        "tags": [],
        "savedBy": { "userId": "8f1a2b3c-...", "session": null },
        "linkedDeployId": null,
        "deployStatus": null,
        "note": null
      }
    ]
  }
}

Response fields

Field Type Description
data.totalVersions number Total number of current versions. Counted without a cap, so on a long history it exceeds the length of the versions array.
data.currentVersionId string | null Identifier of the most recent version (v<N>). null if there are no versions.
data.totalSizeBytes number Total size of all archives in bytes.
data.versions array The list of versions, newest first. At most 500 entries are returned — on a longer history the array is truncated while totalVersions shows the real number.
data.versions[].versionId string Version identifier of the form v<N>.
data.versions[].filename string Filename in storage. Contains a -published or -manual suffix if the corresponding tag is set.
data.versions[].contentType string | null Archive type (application/gzip, application/zip, etc.).
data.versions[].timestamp string Save time (ISO 8601, UTC).
data.versions[].size number Archive size in bytes.
data.versions[].sha256 string SHA-256 of the archive contents. Used for deduplication.
data.versions[].tags string[] Active tags: manual, published.
data.versions[].savedBy.userId string | null Vibecode user identifier.
data.versions[].savedBy.session string | null AI session identifier (from the X-AI-Session-Id header).
data.versions[].linkedDeployId string | null Publication identifier (filled in after POST /v1/apps/:id/publish).
data.versions[].deployStatus string | null Publication status: success or failed.
data.versions[].note string | null Note from the X-Note field at save time or from PATCH.
data.versions[].serverContext object | null Server-level display context: serverId, serverName, serverDisplayName, linkedApp ({ appId, title } or null). Present on both server-scoped responses and app-scoped version items; linkedApp resolves from the server's current owner OAuth-app key at read time.

Linked server versions (`linkedServerSources`)

The versions array lists only app-scoped versions. Versions stored under a server (via POST /v1/infra/servers/:id/sources or auto-save on deploy under a personal key) are not included there — they are returned in a separate additive field data.linkedServerSources, grouped by server:

JSON
{
  "data": {
    "totalVersions": 0,
    "versions": [],
    "linkedServerSources": [
      {
        "serverContext": {
          "serverId": "8de64f8d-...",
          "serverName": "srv-prod",
          "serverDisplayName": "Prod",
          "linkedApp": null
        },
        "totalVersions": 2,
        "totalSizeBytes": 20,
        "versions": [ /* same shape as versions[] items; v<N> numbering is per server */ ]
      }
    ],
    "linkedServerSourcesTruncated": false,
    "linkedServerHint": {
      "message": "This app has source versions stored under a server (server-keyed storage). List and download them via the server endpoint.",
      "listEndpoint": "GET /v1/infra/servers/:serverId/sources",
      "downloadEndpoint": "GET /v1/infra/servers/:serverId/sources/:versionId/download",
      "docs": "https://vibecode.bitrix24.com/docs-content/source-storage.md"
    }
  }
}
Field Type Description
data.linkedServerSources[] array Groups of server versions (one per server). Empty if there are no linked server versions.
data.linkedServerSources[].serverContext object The server: serverId, serverName, serverDisplayName, linkedApp (null for servers on a personal key).
data.linkedServerSources[].totalVersions number Exact number of versions on the server (not limited by the list size below).
data.linkedServerSources[].totalSizeBytes number Total size of that server's versions.
data.linkedServerSources[].versions[] array Server versions in the same shape as versions[]. The v<N> numbering is per server.
data.linkedServerSourcesTruncated boolean true if the version list was truncated for a very large history (the full list is on the server endpoint).
data.linkedServerHint object | null Present when linkedServerSources is non-empty. Points to the authoritative list and download endpoints for server versions.

The field is populated when the caller is the app author (personal vibe_api_* key) or a portal administrator. When called with an OAuth-app key the section is empty (a machine key does not enumerate the author's personal servers). On a ?sha256= request the section is not computed (the existence probe stays lightweight). Download and the full per-server list are available via GET /v1/infra/servers/:serverId/sources (see serverContext.serverId); this response's versions / totalVersions / currentVersionId stay app-scoped and are unchanged.

Examples

curl

Terminal
curl -H "X-Api-Key: YOUR_APP_KEY" \
  https://vibecode.bitrix24.com/v1/apps/<APP_ID>/sources

JavaScript

javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/apps/${appId}/sources`,
  { headers: { 'X-Api-Key': process.env.VIBE_APP_KEY } },
)
const { data } = await res.json()
console.log(`Versions: ${data.totalVersions}, latest: ${data.currentVersionId}`)

Error codes

HTTP Code When returned
403 NOT_AUTHORIZED Only the application author, the application OAuth key, or a Bitrix24 account administrator can manage snapshots.
404 APP_NOT_FOUND The application does not exist, was deleted, or belongs to another portal.

Downloading the archive

GET /v1/apps/:id/sources/:versionId/download

Returns a signed link to the version's archive. The link is valid for 30 minutes.

Path parameters

Parameter Type Description
id (path) UUID Application identifier.
versionId (path) string Version identifier of the form v<N>.

Response

HTTP 200:

JSON
{
  "success": true,
  "data": {
    "url": "https://<storage-endpoint>/...",
    "expiresAt": "2026-05-21T10:45:30.000Z"
  }
}

Examples

curl

Terminal
# Get the link
curl -H "X-Api-Key: YOUR_APP_KEY" \
  https://vibecode.bitrix24.com/v1/apps/<APP_ID>/sources/v3/download

# Download the archive via the obtained link (no additional headers)
curl -o source-v3.tar.gz "<url from the response above>"

JavaScript

javascript
const { data } = await fetch(
  `https://vibecode.bitrix24.com/v1/apps/${appId}/sources/v3/download`,
  { headers: { 'X-Api-Key': process.env.VIBE_APP_KEY } },
).then((r) => r.json())

const archive = await fetch(data.url)
const buffer = await archive.arrayBuffer()
// Next — unpack the archive (tar.gz: tar library; zip: JSZip or similar)

Error codes

HTTP Code When returned
400 INVALID_VERSION_ID The versionId format does not match v<non-negative integer>.
403 NOT_AUTHORIZED Only the application author, the application OAuth key, or a Bitrix24 account administrator can manage snapshots.
404 APP_NOT_FOUND The application does not exist, was deleted, or belongs to another portal.
404 VERSION_NOT_FOUND A version with this versionId does not exist or was deleted.
502 SOURCE_DOWNLOAD_URL_FAILED Storage is temporarily unavailable — retry the request.

Setting and removing a tag

POST /v1/apps/:id/sources/:versionId/tag

Two tags are recognized, both protect the version from automatic cleanup:

  • manual — the version is pinned manually by an operator.
  • published — the version is fixed as published (this tag is also set automatically after POST /v1/apps/:id/publish).

Path parameters

Parameter Type Description
id (path) UUID Application identifier.
versionId (path) string Version identifier of the form v<N>.

Body fields

Field Type Required Description
tag string yes manual or published.
action string yes add — add the tag, remove — remove it.

Response

HTTP 200:

JSON
{
  "success": true,
  "data": {
    "versionId": "v3",
    "tags": ["manual"]
  }
}

Examples

curl

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/apps/<APP_ID>/sources/v3/tag \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tag": "manual", "action": "add" }'

JavaScript

javascript
await fetch(
  `https://vibecode.bitrix24.com/v1/apps/${appId}/sources/v3/tag`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': process.env.VIBE_APP_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ tag: 'manual', action: 'add' }),
  },
)

Error codes

HTTP Code When returned
400 INVALID_TAG The tag is not in the manual, published set.
400 INVALID_ACTION The action is not equal to add or remove.
400 INVALID_VERSION_ID The versionId format does not match v<non-negative integer>.
403 NOT_AUTHORIZED Only the application author, the application OAuth key, or a Bitrix24 account administrator can manage snapshots.
404 APP_NOT_FOUND The application does not exist, was deleted, or belongs to another portal.
404 VERSION_NOT_FOUND A version with this versionId does not exist or was deleted.

Deleting a version

DELETE /v1/apps/:id/sources/:versionId

Marks the version as deleted. A version cannot be restored via the API. A version with the published or manual tag is protected from deletion — first remove the tag via PATCH /v1/apps/:id/sources/:versionId with the body {"tags": []} (or while keeping other tags). The response includes hint.preservedTags — tags without protective labels that are worth keeping when removing the protection.

Path parameters

Parameter Type Description
id (path) UUID Application identifier.
versionId (path) string Version identifier of the form v<N>.

Response

HTTP 200:

JSON
{
  "success": true,
  "data": { "versionId": "v3" }
}

Examples

curl

Terminal
curl -X DELETE https://vibecode.bitrix24.com/v1/apps/<APP_ID>/sources/v3 \
  -H "X-Api-Key: YOUR_APP_KEY"

JavaScript

javascript
await fetch(
  `https://vibecode.bitrix24.com/v1/apps/${appId}/sources/v3`,
  {
    method: 'DELETE',
    headers: { 'X-Api-Key': process.env.VIBE_APP_KEY },
  },
)

Error codes

HTTP Code When returned
400 INVALID_VERSION_ID The versionId format does not match v<non-negative integer>.
403 NOT_AUTHORIZED Only the application author, the application OAuth key, or a Bitrix24 account administrator can manage snapshots.
404 APP_NOT_FOUND The application does not exist, was deleted, or belongs to another portal.
404 VERSION_NOT_FOUND A version with this versionId does not exist or was already deleted.
409 PROTECTED_BY_TAG The version is marked with the published or manual tag. First remove the tag via PATCH /v1/apps/:id/sources/:versionId.

Bulk cleanup

POST /v1/apps/:id/sources/cleanup

Deletes old versions, keeping the keepLatest most recent ones (5 by default). Versions with the manual and published tags are excluded from cleanup regardless of keepLatest.

Path parameters

Parameter Type Description
id (path) UUID Application identifier.

Body fields

Field Type Required Default Description
keepLatest number no 5 How many of the latest versions to keep. A non-negative integer. 0 will keep only versions with the manual and published tags.

The body can be omitted — the default value applies.

Response

HTTP 200:

JSON
{
  "success": true,
  "data": {
    "deletedVersions": ["v2", "v1"]
  }
}

Examples

curl

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/apps/<APP_ID>/sources/cleanup \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "keepLatest": 3 }'

JavaScript

javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/apps/${appId}/sources/cleanup`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': process.env.VIBE_APP_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ keepLatest: 3 }),
  },
)
const { data } = await res.json()
console.log('Deleted versions:', data.deletedVersions.length)

Error codes

HTTP Code When returned
400 INVALID_KEEP_LATEST The keepLatest value is not a non-negative integer.
403 NOT_AUTHORIZED Only the application author, the application OAuth key, or a Bitrix24 account administrator can manage snapshots.
404 APP_NOT_FOUND The application does not exist, was deleted, or belongs to another portal.

Server-scoped source endpoints

This family mirrors the app-scoped one (/v1/apps/:id/sources…), but it is keyed by a server identifier rather than an application one. The request and response contracts are the same as the app-scoped operations (the same body format, the same headers, the same error codes) — only the path prefix changes.

Authorization: ownership (3-identity) — the server-owner key, the same user's personal key, or a Bitrix24 account administrator (ADMIN); checked via loadAndAuthorizeServer. No explicit scope is required: there is no route-level scope check, and vibe:storage is granted automatically.

Method Path Action
POST /v1/infra/servers/:id/sources Save a raw-bytes snapshot
GET /v1/infra/servers/:id/sources List versions (+ ?sha256= probe)
GET /v1/infra/servers/:id/sources/:versionId Single-version metadata
GET /v1/infra/servers/:id/sources/:versionId/download Signed download link
POST /v1/infra/servers/:id/sources/:versionId/tag Add or remove a tag
PATCH /v1/infra/servers/:id/sources/:versionId Update tags / note
DELETE /v1/infra/servers/:id/sources/:versionId Delete a version (soft-delete)
POST /v1/infra/servers/:id/sources/cleanup Bulk cleanup
  • POST …/sources — the same contract as POST /v1/apps/:id/sources: the raw archive bytes in the body, Content-Type one of application/gzip, application/x-tar, application/zip, application/octet-stream, optional X-Filename / X-Tags / X-Note / X-AI-Session-Id headers, a 200 MB body cap, deduplication by sha256.
  • GET …/sources — the server's version list. The optional ?sha256=<64-hex> parameter is a lightweight probe for the existence of an archive with those contents.
  • GET …/sources/:versionId — single-version metadata (the same shape as a versions[] item).
  • GET …/sources/:versionId/download — a signed link to the archive, valid for 30 minutes.
  • POST …/sources/:versionId/tag — body { "tag": "manual" | "published", "action": "add" | "remove" }.
  • PATCH …/sources/:versionId — updates the tags and/or note (three-state behavior: a string overwrites, null clears, an absent field leaves it unchanged).
  • DELETE …/sources/:versionId — soft-delete. A version with the manual or published tag is protected — it returns 409 PROTECTED_BY_TAG; remove the tag via PATCH first.
  • POST …/sources/cleanup — bulk cleanup: keeps the keepLatest most recent versions plus versions with the manual / published tags.

Every version in this family's responses carries a serverContext field (see "List versions" → data.versions[].serverContext) — the server-level display context.

Deploy from a snapshot

POST /v1/infra/servers/:id/deploy accepts the source.versionId field — an alternative to source.url and source.content. It lets you deploy to the server exactly the version that was saved via save_sources, without a separate file upload.

JSON
{
  "source": {
    "versionId": "v3"
  },
  "start": "node server.js"
}

The server resolves versionId into a signed link to the archive and performs the deploy. After a successful or failed deploy, the snapshot's linkedDeployId field is updated automatically.

Standalone virtual machines only. Deploy by source.versionId works on servers whose kind is STANDALONE. A Galaxy app, whose kind is GALAXY_APP, accepts sources on deploy only as inline source.content — a request with source.versionId or source.url is rejected with 400 GALAXY_DEPLOY_CONTENT_ONLY. See Galaxy apps for details.

Restriction: the server must match an application authorization key (vibe_app_*). Personal and management keys without a linked appId cannot resolve a snapshot — 400 SOURCE_VERSION_REQUIRES_APP is returned.

Behavior before publishing

Described above in the "Snapshot presence guarantee before publishing" section. Reference of POST /v1/apps/:id/publish error codes specific to the snapshot check:

HTTP Code When returned
409 SNAPSHOT_REQUIRED There is no snapshot or it is older than 10 minutes (only when saving is enabled). The response includes a hint field describing the action.

Checking Bitrix24 account state

GET /v1/me returns the data.capabilities.apps.sourceStorage block:

When saving is enabled:

JSON
{
  "capabilities": {
    "apps": {
      "sourceStorage": {
        "enabled": true,
        "requiredBeforeDeploy": false,
        "automaticOnDeploy": true,
        "freshnessWindowMinutes": 10,
        "limits": {
          "maxBlobBytes": 209715200
        }
      }
    }
  }
}

When saving is disabled:

JSON
{
  "capabilities": {
    "apps": {
      "sourceStorage": {
        "enabled": false,
        "requiredBeforeDeploy": false,
        "automaticOnDeploy": false,
        "disabledBy": "platform",
        "readOperations": "available",
        "reactivation": {
          "scope": "platform",
          "permission": "platform admin"
        }
      }
    }
  }
}

The disabledBy field — "platform" (the feature is not enabled at the platform level) or "portal" (the Bitrix24 account owner disabled it). The readOperations: "available" field means that listing versions and downloading remain available even when saving is disabled. The reactivation block indicates at which level (platform or Bitrix24 account) the feature is enabled and which permissions are required for it.

Disabling for a Bitrix24 account

The Bitrix24 account owner can disable source saving via the administration section (PATCH /api/admin/source-storage with the body { "sourceStorageEnabled": false }, requires an administrator session). When disabled:

  • POST /v1/apps/:id/sources returns 200 { skipped: true, reason: "DISABLED_FOR_PORTAL" }.
  • POST /v1/apps/:id/publish does not check for a snapshot.
  • GET /v1/apps/:id/sources continues to return previously saved versions.
  • GET /v1/apps/:id/sources/:versionId/download remains available.

The save history survives re-enabling — previously created versions remain available.

Behavior for AI models

The save_sources MCP tool wraps POST /v1/apps/:id/sources. The load_sources tool downloads and unpacks the latest snapshot into a file tree.

Since 2026-05-23 an explicit save_sources call before deployment is not required — the platform saves the bytes automatically. save_sources remains needed only for the three explicit scenarios (the "Saving sources" section above).

The channels through which the model learns about source saving:

  • The descriptions of the save_sources and load_sources MCP tools — visible on first contact.
  • The hint in the 409 SNAPSHOT_REQUIRED response (deployment with an external URL) — suggests uploading via POST /sources or passing X-Skip-Source-Snapshot.
  • The hint in the 409 SNAPSHOT_REQUIRED response (publishing without a fresh snapshot) — directs into the publish → save_sources → publish cycle (relevant if the source is an external URL and auto-saving was skipped).
  • The capabilities.apps.sourceStorage block in the GET /v1/me response — a programmatic state check.

Source registry

The "App sources" page is available in your account via the left menu (the package icon). It shows a consolidated list of all deposited snapshots for convenient code handoff to a new developer or resuming an AI session.

Who sees what:

  • Bitrix24 account member (MEMBER) — only their own snapshots (the ones they uploaded).
  • Bitrix24 account administrator (ADMIN) — all snapshots of the Bitrix24 account, including snapshots of other members. Can download any snapshot.

How to download:

  1. Open "App sources" in the menu.
  2. Find the row you need (server or application).
  3. Click the "Download latest version" button — the browser will download the archive of the latest version.

For legacy applications (rows with the "Application" type) you can also open "Storage" to view the version history.

Note: the page works independently of the state of the platform deposit toggle — historical snapshots are available even if new saves are temporarily disabled.

Aggregated source registry — `GET /v1/me/sources`

The API twin of the "App sources" cabinet page. Returns a list of source-snapshot owners (servers and legacy apps) across everything the calling key owns. An account administrator (ADMIN) key sees the whole account.

Requires a portal-bound API key. A management key or a key with no portal binding → 403 PORTAL_KEY_REQUIRED.

Query parameters

Parameter Type Default Description
page number 1 Page number.
limit number 25 Page size, maximum 100.
search string Filter by title / server name / owner name.

Response

HTTP 200:

JSON
{
  "success": true,
  "data": [
    {
      "kind": "server",
      "ownerKey": "8de64f8d-...",
      "title": "Prod",
      "server": {
        "id": "8de64f8d-...",
        "name": "srv-prod",
        "displayName": "Prod",
        "mode": "blackhole",
        "status": "sleeping",
        "blackholeStatus": "DISCONNECTED",
        "deleted": false
      },
      "app": null,
      "user": { "id": "8f1a2b3c-...", "name": "Alex" },
      "latestVersionId": "v4",
      "latestSavedAt": "2026-05-21T10:15:30.000Z",
      "versionsCount": 4,
      "totalSizeBytes": 552960,
      "reachableViaApi": true,
      "listEndpoint": "/v1/infra/servers/8de64f8d-.../sources",
      "latestDownloadEndpoint": "/v1/infra/servers/8de64f8d-.../sources/v4/download"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 25
}

Row fields

Field Type Description
kind string server or legacy-app.
ownerKey string Owner id of the row: the server id (kind: server) or the app id (kind: legacy-app).
title string Display title (server or application).
server object | null For kind: server: id, name, displayName, mode, status (lowercase), blackholeStatus, deleted. Otherwise null.
app object | null For kind: legacy-app: id, title. Otherwise null.
user object | null Owner: id, name. May be null.
latestVersionId string Identifier of the most recent version (vN).
latestSavedAt string Save time of the latest version (ISO 8601).
versionsCount number Number of versions.
totalSizeBytes number Total size of the archives in bytes.
reachableViaApi boolean Whether the record is reachable via the V1 endpoints (see below).
listEndpoint string | null Ready-to-use path to list the owner's versions, or null.
latestDownloadEndpoint string | null Ready-to-use path to download the latest version, or null.
  • reachableViaApi. false (with listEndpoint / latestDownloadEndpoint equal to null) for a server whose managing key was deleted (an orphan record) or that was itself soft-deleted, and for a soft-deleted legacy app. The row is still listed — for visibility — but the V1 per-owner drill-in would return 404.
  • Server status. For a Black Hole server, status: "sleeping" is the normal "parked" state (the server wakes on demand), not a failure. Pair it with blackholeStatus to tell "parked, wakes on demand" from a real problem. The value is the fleet-poller-maintained DB status (eventually consistent, ~15 s), not a live provider reconcile.
  • Contrast with GET /v1/infra/servers. That list is scoped strictly to the calling key's servers, so a server bound to another of the owner's keys is absent there but present in this registry.

See also