# Version list and download

Reading the snapshot history: the list of an application's versions, the metadata of a single version, and a signed link to the archive. All three operations stay available even when source saving is disabled.

Storage overview and the endpoint reference — [Source code storage](/docs/source-storage).

## List versions

`GET /v1/apps/:id/sources`

Returns non-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. Get it via [`GET /v1/apps`](/docs/apps/list). |

### Query parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|----------|
| `sha256` | string | no | A probe for whether an archive with the given contents already exists: exactly 64 hexadecimal characters, otherwise `400 INVALID_SHA256`. Responds with the same shape as a list call without this parameter, but the `linkedServerSources` section is not computed — the probe stays lightweight. |

### 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,
        "serverContext": null
      }
    ],
    "linkedServerSources": [],
    "linkedServerSourcesTruncated": false
  }
}
```

The `linkedServerSources` and `linkedServerSourcesTruncated` fields are always present — when there are no linked server versions they are an empty array and `false`.

### Response fields

| Field | Type | Description |
|------|-----|----------|
| `data.totalVersions` | number | Total number of non-deleted versions. Counted without a cap, so with 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 — with 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 within one owner. |
| `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 | How the DEPLOY of this version ended: `success` or `failed`. Empty for versions saved by hand — they were never deployed. Publishing does not change this field: it is not a deploy and knows nothing about how one ended. |
| `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` is resolved at read time from the current OAuth key of the app that owns the server. |

### 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 */ ]
      }
    ],
    "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-en/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[]`. Version numbers come from a single sequence shared by the server and its linked application, so an individual server's sequence may not start at `v1` and may contain gaps. |
| `data.linkedServerSourcesTruncated` | boolean | `true` if the version list was truncated because the history is very large (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 Bitrix24 account 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

```bash
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 |
|------|-----|---------------------|
| 400 | `INVALID_SHA256` | The `sha256` parameter is not exactly 64 hexadecimal characters. |
| 403 | `SOURCE_APP_ID_MISMATCH` | The call was made with an authorization key `vibe_app_…` issued for a different application. Such a key can access only its own application's snapshots, even when both applications were created by the same author. |
| 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. |

## Metadata for a single version

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

Returns one version in the same shape as a `data.versions[]` element of the list. Useful when the version identifier is already known — for example, it arrived in `savedVersionId` of the deploy response — and there is no reason to fetch the whole list.

### 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": "v1",
    "filename": "2026-08-17T11-30-16-449Z-v1-published.tar.gz",
    "contentType": "application/gzip",
    "timestamp": "2026-08-17T11:30:16.449Z",
    "size": 451,
    "sha256": "3e9568ee635bb9faacb3eab4be8e59c747792e0b2bfcdc71ea42dd3dabb12d2c",
    "tags": ["published"],
    "savedBy": {
      "userId": "8f1a2b3c-...",
      "session": null
    },
    "linkedDeployId": "publish:2026-08-17T11:30:27.898Z",
    "deployStatus": "success",
    "note": "Repro snapshot",
    "serverContext": null
  }
}
```

### Response fields

The field set matches a `data.versions[]` element — see [the list response fields](#response-fields). This response carries no `data.id`: the internal record identifier is returned only on save.

### Error response example

`404` — the application has no version with that number:

```json
{
  "success": false,
  "error": {
    "code": "VERSION_NOT_FOUND",
    "message": "Version v99 not found"
  }
}
```

### Examples

#### curl

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

#### JavaScript

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

### Error codes

| HTTP | Code | When returned |
|------|-----|---------------------|
| 400 | `INVALID_VERSION_ID` | The `versionId` format does not match `v<non-negative integer>`. |
| 403 | `SOURCE_APP_ID_MISMATCH` | The call was made with an authorization key `vibe_app_…` issued for a different application. Such a key can access only its own application's snapshots, even when both applications were created by the same author. |
| 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. |

## Downloading the archive

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

Returns a signed link to the version's archive. The requested link lifetime is 30 minutes; the actual signature expiry is returned in `expiresAt` and may be shorter, so download as early as you can and check that field.

### 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

```bash
# 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 using that 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 | `SOURCE_APP_ID_MISMATCH` | The call was made with an authorization key `vibe_app_…` issued for a different application. Such a key can access only its own application's snapshots, even when both applications were created by the same author. |
| 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. |
| 410 | `SOURCE_VERSION_BYTES_PURGED` | The version record still exists, but its bytes have already been purged from storage. The only recovery is to save the archive again. |
| 502 | `SOURCE_DOWNLOAD_URL_FAILED` | Storage is temporarily unavailable — retry the request. |

The full code reference — [Error codes](/docs/errors).

## See also

- [Source code storage](/docs/source-storage)
- [Save a snapshot](/docs/source-storage/save)
- [Version tags and notes](/docs/source-storage/metadata)
- [Server-keyed source endpoints](/docs/source-storage/servers)
- [Source registry and Bitrix24 account state](/docs/source-storage/registry)
