# Список версий и скачивание

Чтение истории снапшотов: список версий приложения, метаданные одной версии и подписанная ссылка на архив. Все три операции доступны и при отключённом сохранении исходников.

Обзор хранилища и справочник эндпоинтов — [Хранилище исходного кода](/docs/source-storage).

## Список версий

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

Возвращает актуальные (не удалённые) версии в порядке убывания времени сохранения, не более 500 за вызов — самая свежая первой.

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

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

### Параметры запроса

| Параметр | Тип | Обяз. | Описание |
|----------|-----|:-----:|----------|
| `sha256` | string | нет | Проба на существование архива с таким содержимым: ровно 64 шестнадцатеричных символа, иначе `400 INVALID_SHA256`. Отвечает той же формой, что и список без параметра, но секция `linkedServerSources` не вычисляется — проба остаётся лёгкой. |

### Ответ

`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": "Добавил OAuth-флоу"
      },
      {
        "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
  }
}
```

Поля `linkedServerSources` и `linkedServerSourcesTruncated` приходят всегда — при отсутствии связанных серверных версий это пустой массив и `false`.

### Поля ответа

| Поле | Тип | Описание |
|------|-----|----------|
| `data.totalVersions` | number | Общее количество актуальных версий. Считается без ограничения, поэтому при длинной истории превышает длину массива `versions`. |
| `data.currentVersionId` | string \| null | Идентификатор самой свежей версии (`v<N>`). `null`, если версий нет. |
| `data.totalSizeBytes` | number | Суммарный размер всех архивов в байтах. |
| `data.versions` | array | Список версий от самой свежей к старым. Отдаётся **не более 500 записей** — при более длинной истории массив обрезается, а `totalVersions` показывает реальное число. |
| `data.versions[].versionId` | string | Идентификатор версии вида `v<N>`. |
| `data.versions[].filename` | string | Имя файла в хранилище. Содержит суффикс `-published` или `-manual`, если есть соответствующий тег. |
| `data.versions[].contentType` | string \| null | Тип архива (`application/gzip`, `application/zip` и т.д.). |
| `data.versions[].timestamp` | string | Время сохранения (ISO 8601, UTC). |
| `data.versions[].size` | number | Размер архива в байтах. |
| `data.versions[].sha256` | string | SHA-256 содержимого архива. Используется для дедупликации в пределах владельца. |
| `data.versions[].tags` | string[] | Активные теги: `manual`, `published`. |
| `data.versions[].savedBy.userId` | string \| null | Идентификатор пользователя Вайбкод. |
| `data.versions[].savedBy.session` | string \| null | Идентификатор AI-сессии (из заголовка `X-AI-Session-Id`). |
| `data.versions[].linkedDeployId` | string \| null | Идентификатор публикации (заполняется после `POST /v1/apps/:id/publish`). |
| `data.versions[].deployStatus` | string \| null | Чем закончился ДЕПЛОЙ этой версии: `success` или `failed`. Пусто у версий, которые сохранили вручную, — деплоя у них не было. Публикация это поле не меняет: она не деплой и об его исходе не знает. |
| `data.versions[].note` | string \| null | Комментарий из поля `X-Note` при сохранении или из PATCH. |
| `data.versions[].serverContext` | object \| null | Контекст сервера для отображения: `serverId`, `serverName`, `serverDisplayName`, `linkedApp` (`{ appId, title }` или `null`). Присутствует и в серверных ответах, и в приложенческих элементах версий. Значение `linkedApp` заполняется из текущего OAuth-ключа приложения-владельца сервера на момент чтения. |

### Связанные серверные версии (`linkedServerSources`)

Массив `versions` перечисляет только версии, привязанные к приложению. Версии, сохранённые под сервером (через `POST /v1/infra/servers/:id/sources` или авто-сохранение при деплое под личным ключом), в него не входят — они возвращаются отдельным **аддитивным** полем `data.linkedServerSources`, сгруппированным по серверу:

```json
{
  "data": {
    "totalVersions": 0,
    "versions": [],
    "linkedServerSources": [
      {
        "serverContext": {
          "serverId": "8de64f8d-...",
          "serverName": "srv-prod",
          "serverDisplayName": "Prod",
          "linkedApp": null
        },
        "totalVersions": 2,
        "totalSizeBytes": 20,
        "versions": [ /* та же форма, что и элементы versions[] */ ]
      }
    ],
    "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.tech/docs-content/source-storage.md"
    }
  }
}
```

| Поле | Тип | Описание |
|------|-----|----------|
| `data.linkedServerSources[]` | array | Группы серверных версий (по одной на сервер). Пусто, если связанных серверных версий нет. |
| `data.linkedServerSources[].serverContext` | object | Сервер: `serverId`, `serverName`, `serverDisplayName`, `linkedApp` (`null` для серверов на личном ключе). |
| `data.linkedServerSources[].totalVersions` | number | Точное число версий на сервере (не ограничено размером списка ниже). |
| `data.linkedServerSources[].totalSizeBytes` | number | Суммарный размер версий этого сервера. |
| `data.linkedServerSources[].versions[]` | array | Версии сервера в том же формате, что `versions[]`. Номера версий сквозные для сервера и связанного с ним приложения, поэтому последовательность отдельного сервера может начинаться не с `v1` и содержать пропуски. |
| `data.linkedServerSourcesTruncated` | boolean | `true`, если при очень большой истории список версий был усечён (полный список — на серверном эндпоинте). |
| `data.linkedServerHint` | object \| null | Присутствует, когда `linkedServerSources` не пуст. Указывает на авторитетные эндпоинты списка и скачивания серверных версий. |

Поле заполняется, когда вызывающий — автор приложения (личный `vibe_api_*` ключ) или администратор портала. При вызове **ключом OAuth-приложения** секция пуста (машинный ключ не перечисляет личные серверы автора). При запросе с `?sha256=` секция не вычисляется (проба на существование остаётся лёгкой). Скачивание и полный список по серверу — через `GET /v1/infra/servers/:serverId/sources` (см. `serverContext.serverId`). Поля `versions` / `totalVersions` / `currentVersionId` этого ответа остаются привязанными к приложению и не меняются.

### Примеры

#### curl

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

#### JavaScript

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/apps/${appId}/sources`,
  { headers: { 'X-Api-Key': process.env.VIBE_APP_KEY } },
)
const { data } = await res.json()
console.log(`Версий: ${data.totalVersions}, последняя: ${data.currentVersionId}`)
```

### Коды ошибок

| HTTP | Код | Когда возвращается |
|------|-----|---------------------|
| 400 | `INVALID_SHA256` | Параметр `sha256` не равен 64 шестнадцатеричным символам. |
| 403 | `SOURCE_APP_ID_MISMATCH` | Вызов сделан ключом авторизации `vibe_app_…`, выписанным на другое приложение. Такой ключ обращается только к снапшотам своего приложения, даже если оба приложения создал один автор. |
| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |
| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |

## Метаданные одной версии

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

Возвращает одну версию в той же форме, что элемент `data.versions[]` в списке. Нужен, когда идентификатор версии уже известен — например, пришёл в `savedVersionId` ответа деплоя — и полный список забирать незачем.

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

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` (path) | UUID | Идентификатор приложения. |
| `versionId` (path) | string | Идентификатор версии вида `v<N>`. |

### Ответ

`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": "Снапшот для воспроизведения",
    "serverContext": null
  }
}
```

### Поля ответа

Набор полей совпадает с элементом `data.versions[]` — описание см. в [полях списка](#поля-ответа). Поля `data.id` в этом ответе нет: внутренний идентификатор записи приходит только при сохранении.

### Пример ответа при ошибке

`404` — версии с таким номером у приложения нет:

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

### Примеры

#### curl

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

#### JavaScript

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/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)
```

### Коды ошибок

| HTTP | Код | Когда возвращается |
|------|-----|---------------------|
| 400 | `INVALID_VERSION_ID` | Формат `versionId` не соответствует `v<целое неотрицательное число>`. |
| 403 | `SOURCE_APP_ID_MISMATCH` | Вызов сделан ключом авторизации `vibe_app_…`, выписанным на другое приложение. Такой ключ обращается только к снапшотам своего приложения, даже если оба приложения создал один автор. |
| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |
| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |
| 404 | `VERSION_NOT_FOUND` | Версия с таким `versionId` не существует или была удалена. |

## Скачивание архива

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

Возвращает подписанную ссылку на архив версии. Ссылка действует 30 минут.

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

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` (path) | UUID | Идентификатор приложения. |
| `versionId` (path) | string | Идентификатор версии вида `v<N>`. |

### Ответ

`HTTP 200`:

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

### Примеры

#### curl

```bash
# Получить ссылку
curl -H "X-Api-Key: YOUR_APP_KEY" \
  https://vibecode.bitrix24.tech/v1/apps/<APP_ID>/sources/v3/download

# Скачать архив по полученной ссылке (без дополнительных заголовков)
curl -o source-v3.tar.gz "<url из ответа выше>"
```

#### JavaScript

```javascript
const { data } = await fetch(
  `https://vibecode.bitrix24.tech/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()
// Дальше — распаковка архива (tar.gz: tar library; zip: JSZip или аналог)
```

### Коды ошибок

| HTTP | Код | Когда возвращается |
|------|-----|---------------------|
| 400 | `INVALID_VERSION_ID` | Формат `versionId` не соответствует `v<целое неотрицательное число>`. |
| 403 | `SOURCE_APP_ID_MISMATCH` | Вызов сделан ключом авторизации `vibe_app_…`, выписанным на другое приложение. Такой ключ обращается только к снапшотам своего приложения, даже если оба приложения создал один автор. |
| 403 | `NOT_AUTHORIZED` | Только автор приложения, OAuth-ключ приложения или администратор портала могут управлять снапшотами. |
| 404 | `APP_NOT_FOUND` | Приложение не существует, удалено или принадлежит другому порталу. |
| 404 | `VERSION_NOT_FOUND` | Версия с таким `versionId` не существует или была удалена. |
| 410 | `SOURCE_VERSION_BYTES_PURGED` | Запись о версии жива, но её байты уже вычищены из хранилища. Восстановление одно — сохранить архив заново. |
| 502 | `SOURCE_DOWNLOAD_URL_FAILED` | Хранилище временно недоступно — повторите запрос. |

Полный справочник кодов — [Коды ошибок](/docs/errors).

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

- [Хранилище исходного кода](/docs/source-storage)
- [Сохранить снапшот](/docs/source-storage/save)
- [Теги и комментарии версии](/docs/source-storage/metadata)
- [Серверные эндпоинты исходников](/docs/source-storage/servers)
- [Реестр исходников и состояние портала](/docs/source-storage/registry)
