
## Список приложений

`GET /v1/applications`

Возвращает приложения портала, которые видит владелец ключа, — свои, открытые коллегами или всё вместе. Выборка постраничная, набор полей строки совпадает с карточкой приложения.

## Параметры

| Параметр | Тип | Обяз. | По умолч. | Описание |
|----------|-----|:-----:|-----------|---------|
| `scope` (query) | string | нет | `feed` | Какие приложения вернуть: `mine` — свои, `shared` — доступные вам чужие, `feed` — общий список из первых двух. Значения регистрозависимы, а пустое `scope=` отвечает `400 INVALID_SCOPE` наравне с неизвестным: параметр либо опускают, либо задают одним из трёх значений |
| `page` (query) | number | нет | `1` | Номер страницы, считая с единицы. Значение меньше единицы и нечисловое читаются как `1` |
| `limit` (query) | number | нет | `50` | Сколько приложений на странице, максимум `100`. Большее значение приводится к `100`, `0` и нечисловое — к `50`, отрицательное — к `1` |

Постраничный обход строится по `total`, `page` и `limit` из ответа: страницы запрашиваются подряд, пока сумма полученных строк не догонит `total`. Страница за пределами выдачи отвечает `200` с пустым массивом `data`, а не ошибкой.

Считать число страниц как `total / limit` можно только при `truncated: false`. При `true` `total` перестаёт быть полным числом, страниц за ним нет, и за остатком идут в `mine` и `shared` — у них предела нет.

## Примеры

### curl — личный ключ

```bash
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20"
```

### curl — OAuth-приложение

```bash
curl -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  "https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20"
```

### JavaScript — личный ключ

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20', {
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})
const { data: applications, total } = await res.json()

applications.forEach(app => {
  const state = app.activeOperation
    ? `${app.activeOperation.kind} — ${app.activeOperation.status}`
    : app.server?.status ?? 'сервера нет'
  console.log(`${app.name}: ${state}`)
})
console.log(`Показано ${applications.length} из ${total}`)
```

### JavaScript — OAuth-приложение

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20', {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})
const { data: applications, total } = await res.json()
```

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

| Поле | Тип | Описание |
|------|-----|---------|
| `success` | boolean | Всегда `true` при успехе |
| `data` | array | Массив приложений. Элемент массива и `data` одной карточки — одна и та же форма, её поля разобраны в [Карточке приложения](./get.md) |
| `total` | number | Сколько приложений попало под запрошенный `scope`. У `feed` это размер выборки, а не число приложений портала — прежде чем считать по нему страницы, читайте `truncated` |
| `page` | number | Номер выданной страницы, уже приведённый к допустимому значению |
| `limit` | number | Размер страницы, уже приведённый к допустимому значению |
| `truncated` | boolean | Урезана ли выдача. Приходит всегда, в любом `scope`. `false` — `total` точное число. `true` — `total` это размер выборки, а не «сколько есть», и страниц за ним нет |

## Пример ответа

```json
{
  "success": true,
  "data": [
    {
      "id": "cmsw806qp0001tdskzf6mitka",
      "name": "Черновик без сервера",
      "description": null,
      "type": "PERSONAL",
      "iconUrl": null,
      "createdAt": "2026-08-16T19:51:18.817Z",
      "updatedAt": "2026-08-16T19:51:18.817Z",
      "viewerState": "owner",
      "pinned": false,
      "author": { "name": "Автор приложений" },
      "isEmbedded": true,
      "openUrl": null,
      "openTarget": null,
      "server": null,
      "sources": { "hasVersions": false, "latestVersionId": null, "latestSavedAt": null },
      "activeOperation": null
    },
    {
      "id": "cmsw806qi0000tdskiw2aegii",
      "name": "Отчёт по сделкам",
      "description": "Сводка по воронке за период",
      "type": "PERSONAL",
      "iconUrl": null,
      "createdAt": "2026-08-16T19:51:18.810Z",
      "updatedAt": "2026-08-16T19:51:18.810Z",
      "viewerState": "owner",
      "pinned": false,
      "author": { "name": "Автор приложений" },
      "isEmbedded": false,
      "openUrl": "https://app-05b67cf7.vibecode.bitrix24.tech",
      "openTarget": "app",
      "server": {
        "id": "5cbb50f9-f95f-4ddf-ba3b-b771209cb6fb",
        "status": "RUNNING",
        "url": "https://app-05b67cf7.vibecode.bitrix24.tech",
        "kind": "STANDALONE",
        "reachable": true,
        "lastDeployedAt": "2026-08-16T19:50:41.302Z"
      },
      "sources": {
        "hasVersions": true,
        "latestVersionId": "v2",
        "latestSavedAt": "2026-08-16T19:41:18.818Z"
      },
      "activeOperation": {
        "kind": "deploy",
        "status": "running",
        "step": "build",
        "startedAt": "2026-08-16T19:50:33.822Z"
      }
    }
  ],
  "total": 2,
  "page": 1,
  "limit": 50,
  "truncated": false
}
```

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

400 — значение `scope` вне допустимого набора:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_SCOPE",
    "message": "scope must be one of: mine, shared, feed"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 400 | `INVALID_SCOPE` | Значение `scope` вне набора `mine`, `shared`, `feed`. Сюда же попадают пустое значение и другой регистр |
| 401 | `NO_PORTAL` | Ключ не привязан к порталу — фактический ответ на этот случай |
| 403 | `MANAGEMENT_KEY_NO_ENTITY_ACCESS` | Управляющий ключ: нужен API-ключ или ключ авторизации |
| 403 | `PORTAL_KEY_REQUIRED` | Страховка раздела: повреждённая запись ключа без владельца. Обычный «ключ без портала» — это `401 NO_PORTAL` выше |
| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |
| 401 | `INVALID_API_KEY` | Неверный или просроченный API-ключ |
| 429 | `RATE_LIMITED` | Превышена частота. 60 запросов в минуту — суммарный лимит платформы, доля одной реплики сегодня 20; ориентируйтесь на `X-RateLimit-Limit` из ответа. Счётчик на владельца ключа. Лимит строже, чем у карточки: список рассчитан на обновление экрана, а не на опрос в цикле. Есть `Retry-After` — пауза в целых секундах |
| 429 | `QUOTA_EXCEEDED` | Исчерпана суточная квота бесплатных вызовов ключа. Тот же статус, другая причина: ожидание не поможет |

Текст `error.message` всегда английский и предназначен для журналов: тексты для интерфейса стройте по `error.code`. Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

- **Выборка идёт по владельцу ключа, а не по самому ключу.** В список попадают и приложения, заведённые в личном кабинете: их серверы привязаны к другим ключам того же человека, поэтому в [`GET /v1/infra/servers`](/docs/infra/servers/list) они не видны, а здесь видны. Обратное тоже верно: сменив ключ на новый, вы увидите тот же список приложений.
- **Порядок строк зависит от `scope`, и он полный.** У `feed` сверху идут закреплённые приложения, за ними свои, затем открытые вам, и внутри каждой группы новее по `updatedAt`. У `mine` и `shared` порядок один — по `createdAt`, новые сверху. В обоих случаях при равных метках времени порядок доопределяется по `id`, и это важно для обхода: у приложений, созданных пакетно, метки совпадают, а без вторичного ключа две страницы одного обхода могли бы пересечься или пропустить приложение.
- **У `feed` выборка ограничена сверху, и об этом говорит `truncated`.** Порядок для этого списка считается на стороне сервиса, а не выборкой из базы, поэтому страница нарезается уже после сортировки, а `total` показывает размер выборки, а не всего портала. Признак читайте осторожно: он поднимается, когда выборка упёрлась в предел, а отличить «упёрлись» от «ровно столько и было» одним запросом нельзя, поэтому платформа ошибается в сторону признания неполноты. Ложное `true` вы диагностируете сами — сходите за следующей страницей и получите пустую. Ложное `false` не диагностируется никак, поэтому его и не допускают. У `mine` и `shared` предела нет, `total` — полное число строк под фильтр, `truncated` всегда `false`.
- **`updatedAt` — не свежесть кода.** Поле двигает запись в саму карточку: переименование, смена описания, отвязка удалённого сервера. Выкладка его не двигает, поэтому приложение, куда сегодня выкладывались десять раз, по `updatedAt` уедет в хвост `feed`. Свежесть кода — `sources.latestSavedAt`.
- **В `shared` и `feed` сводка исходников почти всегда пустая.** Оба блока обогащения наполняются только тому, кто приложением управляет, поэтому у чужих строк `sources` приходит пустым, а `activeOperation` — `null`. Форма строки при этом не меняется, и отличить «данные не отдаются» от «версий нет» по самой строке нельзя. Экран, который рисует статус кода для всей выдачи, обязан это учитывать.
- **Пустой `shared` — рабочее состояние, а не отказ.** Ответ `200` с пустым `data` и `total: 0` означает, что доступных вам чужих приложений нет. Ошибку в этом случае искать негде.
- **`requestable` в списке не возвращается — ни в одном `scope`.** В выдачу попадают только приложения, которые ваши, доступны вам через политику сервера, открыты вам лично грантом или не имеют сервера и открыты всему порталу. То есть кнопка «запросить доступ» разделу не нужна — и вести её было бы некуда: операций записи здесь нет. Значение `requestable` существует для полноты набора и приходит только там, где карточка отказывает: `403 FORBIDDEN` на прямой запрос чужого закрытого приложения.
- **У `scope` и `viewerState` разные границы.** В `shared` попадают не только приложения, доступ к которым открыли лично вам, но и приложения без сервера, открытые всему порталу, — а такая строка приходит с `viewerState: "usable"`, не `"shared"`. Клиент, который отбирает строки по `viewerState === "shared"`, часть выдачи потеряет.

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

- [Карточка приложения](./get.md)
- [Витрина приложений](/docs/applications)
- [Серверы](/docs/infra/servers)
- [Ключи и авторизация](/docs/keys-auth)
- [Лимиты и оптимизация](/docs/optimization)
- [Ошибки](/docs/errors)
