
## Карточка приложения

`GET /v1/applications/:id`

Возвращает одно приложение портала со сводкой по серверу, сохранённым исходникам и идущей операции. Карточка доступна владельцу и тому, кому приложение открыли — лично или через политику сервера. Администрирование портала здесь роли не играет: администратор, который приложением не владеет и доступа не получал, получает `403 FORBIDDEN` наравне с любым другим зрителем.

## Параметры

| Параметр | Тип | Обяз. | Описание |
|----------|-----|:-----:|---------|
| `id` (path) | string | да | Идентификатор приложения. Список: [`GET /v1/applications`](./list.md) |

## Примеры

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

```bash
curl -H "X-Api-Key: YOUR_API_KEY" \
  https://vibecode.bitrix24.tech/v1/applications/cmsw806qi0000tdskiw2aegii
```

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

```bash
curl -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  https://vibecode.bitrix24.tech/v1/applications/cmsw806qi0000tdskiw2aegii
```

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

```javascript
const id = 'cmsw806qi0000tdskiw2aegii'
const res = await fetch(`https://vibecode.bitrix24.tech/v1/applications/${id}`, {
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})
const { data: application } = await res.json()

console.log(application.name, application.server?.status ?? 'сервера нет')
if (application.sources.hasVersions) {
  console.log('Последняя версия:', application.sources.latestVersionId)
}
```

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

```javascript
const id = 'cmsw806qi0000tdskiw2aegii'
const res = await fetch(`https://vibecode.bitrix24.tech/v1/applications/${id}`, {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})
const { data: application } = await res.json()
```

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

| Поле | Тип | Описание |
|------|-----|---------|
| `success` | boolean | Всегда `true` при успехе |
| `data.id` | string | Идентификатор приложения |
| `data.name` | string | Название приложения |
| `data.description` | string \| null | Описание. `null`, если описание не задавали |
| `data.type` | string | Как приложение заведено: `SHARED` — сразу с сервером и (или) ключом авторизации, `PERSONAL` — без них. Значение проставляется при создании и позже не пересчитывается, поэтому сервер бывает и у `PERSONAL` |
| `data.iconUrl` | string \| null | Относительный путь к иконке без хоста, например `/api/app-icons/7f3…?v=1755500000000`. Раздача анонимная: ни ключа, ни подписи, ни срока жизни — склейте путь с базовым URL API и вставляйте в `<img src>`. Отдаётся PNG 256×256, `Cache-Control: public, max-age=300, must-revalidate` и ETag. `null`, если указателя на иконку у карточки нет |
| `data.createdAt` | string | Момент создания, ISO 8601 |
| `data.updatedAt` | string | Момент последней записи в карточку, ISO 8601. Выкладка карточку не трогает — свежесть кода смотрите в `sources.latestSavedAt` |
| `data.viewerState` | string | Отношение зрителя к приложению: `owner` — владелец, `shared` — доступ открыт лично или через сервер, `usable` — у приложения нет сервера и оно открыто всему порталу, `requestable` — доступа нет. У приложения с сервером доступ определяет политика сервера, и открытое всем такое приложение приходит как `shared` |
| `data.pinned` | boolean | Закрепил ли этот зритель приложение у себя |
| `data.author.name` | string | Отображаемое имя владельца приложения |
| `data.openUrl` | string \| null | Адрес, по которому приложение открывают. Приходит адрес сервера приложения, когда он есть. `null` означает «открывать нечем» и является штатным значением |
| `data.openTarget` | string \| null | Что именно лежит в `openUrl`. Сегодня значение одно — `app`, адрес сервера приложения. `null` приходит вместе с `null` в `openUrl`. Значение, которого ваш клиент не знает, читайте как «открывать нечем», а не как испорченный ответ: набор закрытый, но пополняемый |
| `data.isEmbedded` | boolean | Приложение встроено в интерфейс Битрикс24 — то есть у него есть хотя бы одно привязанное [место встраивания](/docs/apps/placements). Единственное, чем различаются «встроено, открывается внутри Битрикс24» и «ещё не опубликовано»: у обоих `openUrl` и `openTarget` приходят `null`. Признак не зависит от сервера: встроенное приложение без своего сервера — обычное состояние. Приходит всем, кто видит карточку |
| `data.server` | object \| null | Сводка по серверу приложения. `null`, если сервера нет, он удалён или снесён |
| `data.server.id` | string | Идентификатор сервера. Он же в путях раздела [Серверы](/docs/infra/servers) |
| `data.server.status` | string | Состояние сервера: `PROVISIONING`, `RUNNING`, `STOPPED`, `SLEEPING`, `ERROR`. **Заглавными буквами** — [`GET /v1/infra/servers`](/docs/infra/servers/list) для того же сервера отдаёт то же значение строчным, так что перед сравнением строк из двух разделов приводите регистр. Снесённый сервер сюда не попадает вовсе: у него `server` равен `null`. Незнакомое значение читайте как «состояние, которого ваш клиент не знает», а не как испорченный ответ |
| `data.server.url` | string \| null | Адрес приложения. `null`, пока субдомен не выдан — отдельного признака «адрес есть» нет, эта проверка и есть признак. Перенос контейнера между галактиками адрес не меняет: он выводится из субдомена, а перенос субдомен не трогает |
| `data.server.kind` | string | Тип ресурса: `STANDALONE` — отдельная виртуальная машина, `GALAXY_APP` — приложение на общем хосте галактики |
| `data.server.reachable` | boolean | Сервер и работает, и отвечает по сети. Отвечает не на тот вопрос, что `status`: контейнер бывает поднят, а туннель до него не поднят ни разу. Признак уже включает `status === "RUNNING"` — истинным при другом состоянии не приходит, конъюнктить не нужно. ⚠️ У `kind: "GALAXY_APP"` вторая половина признака берётся с ХОСТА галактики, а не с самого контейнера; разбор в «Известных особенностях» |
| `data.server.lastDeployedAt` | string \| null | Момент последней УСПЕШНОЙ выкладки, ISO 8601 — единственный в разделе признак того, что приложением живут, а не только что его сервер поднят. Отдельно от `updatedAt` (та двигается правкой карточки и выкладку не видит) и от `sources.latestSavedAt` («код сохранён», а не «выложен», и приходит только владельцу). ⚠️ `null` не означает «никогда не выкладывали»: у серверов, созданных до 18.08.2026, поле пустое до первой следующей выкладки — историю не восстанавливали намеренно, см. «Известные особенности» |
| `data.sources` | object | Сводка по сохранённым версиям исходников. Наполняется только тому, кто приложением управляет |
| `data.sources.hasVersions` | boolean | Есть ли хотя бы одна сохранённая версия |
| `data.sources.latestVersionId` | string \| null | Последняя версия в форме `v<N>` — ровно в той, которую принимает [`GET /v1/infra/servers/:id/sources/:versionId/download`](/docs/source-storage). Это не первичный ключ записи, за списком версий обращаться не нужно. `null`, если версий нет |
| `data.sources.latestSavedAt` | string \| null | Момент сохранения последней версии, ISO 8601 |
| `data.activeOperation` | object \| null | Идущая операция над сервером. Наполняется только тому, кто приложением управляет. `null` разбирается в «Известных особенностях» |
| `data.activeOperation.kind` | string | Вид операции: `deploy` — выкладка, `repair` — починка, `resize` — изменение тарифа сервера, `migrate` — перенос контейнера между галактиками |
| `data.activeOperation.status` | string | `running` — операция идёт. `unknown` — операция начиналась, исход неизвестен |
| `data.activeOperation.step` | string \| null | Шаг, на котором операция находится. Свободная строка, а не перечисление |
| `data.activeOperation.startedAt` | string | Момент старта операции, ISO 8601 |

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

```json
{
  "success": true,
  "data": {
    "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"
    }
  }
}
```

Приложение без сервера отдаёт `server: null`, пустую сводку `sources` и `activeOperation: null`. Открывать его негде, поэтому `openUrl` и `openTarget` тоже приходят `null` — приложение из примера встроено в Битрикс24, и это ровно тот случай:

```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
  }
}
```

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

404 — приложения с таким идентификатором на портале нет:

```json
{
  "success": false,
  "error": {
    "code": "APPLICATION_NOT_FOUND",
    "message": "Application not found"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 404 | `APPLICATION_NOT_FOUND` | Приложения с таким `id` на портале ключа нет. Тот же ответ приходит на приложение чужого портала и на удалённое |
| 403 | `FORBIDDEN` | Приложение существует, но доступа к нему у зрителя нет |
| 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` | Превышена частота. 120 запросов в минуту — суммарный лимит платформы, доля одной реплики сегодня 40; ориентируйтесь на `X-RateLimit-Limit` из ответа. Счётчик у карточки свой и заметно свободнее, чем у списка. Есть `Retry-After` — пауза в целых секундах |
| 429 | `QUOTA_EXCEEDED` | Исчерпана суточная квота бесплатных вызовов ключа. Тот же статус, другая причина: ожидание не поможет |

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

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

- **Карточка открыта не только владельцу.** Приложение, которым с вами поделились, читается по прямой ссылке — отвечают состояния `owner`, `shared` и `usable`. Зритель без доступа получает `403 FORBIDDEN`, поэтому значение `requestable` в карточке не встречается.
- **У встроенного приложения открывать негде, и оба поля приходят `null`.** Адреса, по которому встроенное приложение открывают внутри портала, платформа пока не знает. Пустое место здесь штатный ответ, а не сбой: выдуманная ссылка увела бы человека не туда, а отличить это от рабочего перехода он бы не смог.
- **Адрес сервера в `openUrl` не подставляется, и подставлять его самостоятельно нельзя.** Поле `server.url` у встроенного приложения приходит как обычно, но местом открытия не является: по нему отдаётся страница входа шлюза с кодом `200`, то есть переход выглядит удачным, не будучи им. Клиент, который «починит» пустой `openUrl` подстановкой `server.url`, получит именно этот тихий отказ.
- **`openUrl` не гасится недоступностью сервера.** Адрес приходит и при `server.reachable: false`. Погашенную кнопку человек диагностировать не может, а показанный отказ — может.
- **Пустую пару открытия различает `isEmbedded`, и только он.** «Встроено, открывается внутри Битрикс24» и «ещё не опубликовано» приходят с одинаковыми `openUrl: null` и `openTarget: null`, а показать человеку нужно разное. Выводить встройку из наличия сервера НЕЛЬЗЯ: встроенное приложение без своего сервера — обычное состояние (встроили раньше, чем выложили код), и наоборот, у встроенного приложения с сервером `server.url` приходит как обычно, но местом открытия не является.
- **У приложения в галактике вторая половина `reachable` — про хост, а не про контейнер.** У `server.kind: "GALAXY_APP"` связность держит общий хост галактики; у самого контейнера своего туннеля нет по устройству. Поэтому `true` здесь означает сразу три вещи: контейнер работает, хост работает, туннель хоста поднят — признак включает `status === "RUNNING"` и без него истинным не бывает. Практическое следствие в другую сторону: только что созданный контейнер, ещё не дошедший до `RUNNING` (а доходит он до него лишь после первой загрузки исходников), приедет с `reachable: false` при совершенно живом хосте. Это «контейнер пока не поднялся», а не «хост недоступен», и по одному признаку два случая не различить — смотрите `status`. У `STANDALONE` такого расщепления нет: там оба факта про одну машину.
- **`lastDeployedAt` заполняется вперёд, а не назад.** Отметку ставит сама выкладка, в тот же момент, когда фиксирует успех, поэтому она не может разойтись с исходом: провалившаяся выкладка её не двигает. Но и истории у поля нет: у серверов, существовавших до 18.08.2026, оно пустое до первой следующей выкладки. Восстановить историю было можно только из журнала операций, а он покрывает лишь один из трёх путей выкладки — тогда дата была бы у приложений на выделенной машине и отсутствовала у приложений в галактике, и эта пустота читалась бы как «галактические приложения мертвы». Ровное «выкладок с тех пор не было» честнее. Поэтому `null` в первые недели — норма, а не признак заброшенного приложения.
- **Снесённый сервер приходит как `server: null`, а не отдельным статусом.** Значений вроде `deleted`, `terminated`, `destroyed` или `archived` в `server.status` вы не увидите — но по разным причинам, и это стоит различать: трёх последних в платформе нет вовсе, а состояние «снесён» существует и просто не публикуется этим разделом. Как только сервер снесён, карточка отдаёт `server: null` целиком, вместе с `openUrl: null`. Если вы нормализуете статусы, пришедшие из [`GET /v1/infra/servers`](/docs/infra/servers/list), учтите, что там набор другой — тот раздел отдаёт статус строчным и по параметру `includeDeleted` показывает удалённые серверы. У витрины такого параметра нет.
- **Приложение чужого портала неотличимо от несуществующего.** На идентификатор из другого портала приходит `404 APPLICATION_NOT_FOUND`, а не `403`: раздел не подтверждает даже факт существования такой записи.
- **Сводка исходников и идущая операция приходят только управляющему.** Зрителю, которому приложение просто открыли, `sources` приходит пустым, а `activeOperation` — `null`, и форма ответа при этом не меняется. Поэтому пустая сводка сама по себе не значит «версий нет»: причин у неё три, и снаружи они выглядят одинаково — у приложения нет сервера, версий действительно нет, или приложение чужое и эти данные вам не отдаются. Клиент, который по пустому `sources` рисует «кода нет», ошибётся на каждом чужом приложении.
- **`activeOperation: null` не означает «с приложением ничего не происходит».** Признак честен только для операций с сохранённой записью — это выкладка, починка, изменение тарифа сервера и перенос контейнера между галактиками. Остальное платформа не журналирует, и такие действия проходят мимо этого поля. Значение `unknown` в `status` означает третье состояние: операция начиналась, а её исход неизвестен — ждать завершения бессмысленно, состояние нужно перечитать по самому серверу.
- **Состояние сервера приходит заглавными буквами.** В карточке `server.status` для работающего сервера равен `RUNNING`, тогда как [`GET /v1/infra/servers`](/docs/infra/servers/list) для того же сервера в тот же момент отдаёт `running`. Клиент, который сверяет строки от обоих разделов, обязан приводить регистр.

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

- [Список приложений](./list.md)
- [Витрина приложений](/docs/applications)
- [Серверы](/docs/infra/servers)
- [Хранилище исходников](/docs/source-storage)
- [Ключи и авторизация](/docs/keys-auth)
- [Ошибки](/docs/errors)
