## Исход операции из ленты сервера

`GET /v1/infra/servers/:id/operations/:operationId`

Возвращает исход выкладки или команды, на которую ссылается запись ленты событий сервера. Операция ищется по паре «сервер, идентификатор».

Нужна, когда исход читает не тот ключ, которым операция запущена: [`GET /v1/infra/operations/:operationId`](./operation-status.md) отвечает только ключу-создателю, а эта ручка — по правилу доступа [ленты событий](/docs/infra/servers/activity). Её читает владелец сервера личным ключом или ключом своего OAuth-приложения из того же портала и участник команды разработки, чья роль включает настройки сервера, — своим личным ключом. Ключ OAuth-приложения читает ленту правами своего владельца: пользователь сессии на доступ не влияет, а токен сессии можно не передавать. Ключи, выданные под узкую задачу со своим перечнем маршрутов, — ключ обслуживания агента, ключ внешнего API приложения, ключ участника команды и ключ внешнего участника — исход по этому пути не читают и получают `403` с кодом вида `…_KEY_OUT_OF_SCOPE`. Ответ содержит только метаданные операции. Приватные поля длительного exec `stdout`, `stderr`, `duration` и `truncated` отсутствуют даже для создателя; читайте их через разрешённую ручку в области операции. Метаданные такие же, как у [`GET /v1/infra/operations/:operationId`](./operation-status.md). Ручка только на чтение, побочных действий у неё нет.

**Длительный exec доступен только отдельной машине.** [`async=true`](./exec.md) принимает таймаут от 1 до 86400 секунд на `STANDALONE`. Другие виды сервера получают `400 DURABLE_EXEC_UNSUPPORTED_SERVER_KIND` до блокировки, создания операции и отправки команды, без перехода к синхронному исполнению. Exec Galaxy остаётся синхронным с пределом 600 секунд. Работающая длительная задача отдельной машины занимает канал после `202`; опрашивайте эту операцию вместо повторного запуска. Неизвестный исход требует проверки и подтверждения остановки. `/unstick` и удаление лока не снимают ограничение приёма длительной задачи.

## Параметры

| Параметр | Тип | Обяз. | Описание |
|----------|-----|:-----:|----------|
| `id` (path) | string | да | ID сервера. Список: [`GET /v1/infra/servers`](/docs/infra/servers/list) |
| `operationId` (path) | string | да | Идентификатор операции — поле `ref.id` записи [ленты событий](/docs/infra/servers/activity), либо идентификатор из ответа [`POST /deploy`](./deploy.md) или [`POST /exec`](./exec.md) |

## Примеры

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

```bash
curl -H "X-Api-Key: YOUR_API_KEY" \
  https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/operations/OPERATION_ID
```

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

```bash
curl -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/operations/OPERATION_ID
```

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

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/operations/${operationId}`,
  { headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
)

if (res.status === 410) {
  console.log('Операция была, подробности больше не хранятся')
} else {
  const { data } = await res.json()
  console.log(`Статус: ${data.status}, шаг: ${data.step}, причина: ${data.error?.code ?? '—'}`)
}
```

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

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/operations/${operationId}`,
  {
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
    },
  }
)
const { data } = await res.json()
```

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

| Поле | Тип | Описание |
|------|-----|----------|
| `success` | boolean | Всегда `true` при успехе |
| `data.operationId` | string | Идентификатор операции |
| `data.kind` | string | Вид операции: `deploy` или `exec` |
| `data.serverId` | string | ID сервера |
| `data.status` | string | `running`, `succeeded`, `failed` или `unknown` |
| `data.step` | string \| null | Шаг, на котором операция находится или остановилась. У `kind: "exec"` всегда `null` |
| `data.exitCode` | number \| null | Только при `kind: "exec"`: код возврата команды, если агент его подтвердил, иначе `null`. У выкладки поля нет |
| `data.startedAt` | string | Время старта, ISO 8601 |
| `data.finishedAt` | string \| null | Время завершения, ISO 8601. `null`, пока операция идёт |
| `data.error` | object \| null | `{ code, message }` при `status: "failed"`, иначе `null`. `code` может быть `null`, если платформа не назвала код отказа |

Смысл статусов и пары `exitCode` + `status` — на странице [Исход операции](./operation-status.md).

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

```json
{
  "success": true,
  "data": {
    "operationId": "cmop1abcdefghijklmnopqrs",
    "kind": "deploy",
    "serverId": "8f14e45f-ceea-467a-9c8d-2f1c5a6b7e30",
    "status": "failed",
    "step": "download",
    "startedAt": "2026-10-06T12:13:17.762Z",
    "finishedAt": "2026-10-06T12:13:28.135Z",
    "error": {
      "code": "DOWNLOAD_HTTP_STATUS",
      "message": "The source returned HTTP 404."
    }
  }
}
```

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

404 — сервера нет, он недоступен этому ключу или лента для владельца сервера ещё не включена:

```json
{
  "success": false,
  "error": {
    "code": "SERVER_NOT_FOUND",
    "message": "Server not found"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `INVALID_OPERATION_ID` | Идентификатор не той формы, которую выдаёт платформа |
| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |
| 401 | `INVALID_API_KEY` | Ключ не опознан |
| 401 | `INVALID_SESSION` | Ключ OAuth-приложения передан с токеном сессии, который не опознан или истёк |
| 403 | `SESSION_APP_MISMATCH` | Токен сессии выдан другому приложению или для другого портала, чем ключ OAuth-приложения в `X-Api-Key` |
| 403 | `AGENT_MAINTENANCE_KEY_OUT_OF_SCOPE` | Запрос сделан ключом обслуживания агента: у него свой перечень маршрутов |
| 403 | `APP_API_KEY_OUT_OF_SCOPE` | Запрос сделан ключом внешнего API приложения: у него свой перечень маршрутов |
| 403 | `EXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE` | Запрос сделан ключом внешнего участника: у него свой перечень маршрутов |
| 403 | `PORTAL_COLLABORATOR_KEY_OUT_OF_SCOPE` | Запрос сделан ключом участника команды. Исход участник читает своим личным ключом |
| 403 | `SERVER_ROLE_FORBIDDEN` | Вы в команде разработки сервера, но роль не включает настройки сервера |
| 404 | `SERVER_NOT_FOUND` | Сервера нет, он недоступен этому ключу или лента для владельца сервера ещё не включена |
| 404 | `OPERATION_NOT_FOUND` | Сервер доступен, но такой операции у него нет: идентификатор неизвестен, принадлежит другому серверу или запись уже удалена |
| 410 | `OPERATION_OUTCOME_EXPIRED` | Исход больше не хранится — прошло больше 7 суток после завершения. Тело ошибки несёт ещё `kind`, `serverId` и `startedAt` операции |
| 429 | `RATE_LIMITED` | Превышен лимит 60 запросов в минуту на ключ. Точное значение — в заголовке `x-ratelimit-limit`, потолок делится на реплики |

Полный список общих ошибок API — [Ошибки](/docs/errors).

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

- **Чужой сервер и выключенная лента дают один и тот же `404 SERVER_NOT_FOUND`.** Отдельный ответ подтверждал бы, что такой сервер существует.
- **Исход читается и у операции, которой нет в ленте.** Ручка ищет операцию по серверу, а не по записи ленты, поэтому отвечает и на выкладку, запущенную до включения ленты для владельца сервера, — пока исход хранится.
- **`OPERATION_NOT_FOUND` отвечает только тому, кто сервер видит.** Операция другого сервера по этому пути не читается, даже если оба сервера ваши. У записи ленты своего `serverId` нет: берите `ref.id` из ленты того сервера, чей `id` стоит в пути запроса.

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

- [Лента событий сервера](/docs/infra/servers/activity)
- [Исход операции](./operation-status.md)
- [Последние операции сервера](./operations.md)
- [Логи сервиса](./logs.md)
