## Лента событий сервера

`GET /v1/infra/servers/:id/activity`

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

Звать первой, когда приложение перестало работать, бот молчит, событие портала не пришло или выкладка упала: запись называет причину машинным кодом, а запись о выкладке или команде ведёт на её исход. Лента не записывает ошибки вызовов API, бота, который сам забирает события запросами, и успешные доставки, поэтому пустая лента не доказывает, что события дошли.

Ленту читает владелец сервера личным ключом или ключом своего OAuth-приложения из того же портала и участник команды разработки, чья роль включает настройки сервера, — своим личным ключом. Ключ OAuth-приложения читает ленту правами своего владельца: пользователь сессии на доступ не влияет, а токен сессии можно не передавать. Ключи, выданные под узкую задачу со своим перечнем маршрутов, — ключ обслуживания агента, ключ внешнего API приложения, ключ участника команды и ключ внешнего участника — ленту не читают и получают `403` с кодом вида `…_KEY_OUT_OF_SCOPE`. Остальным, включая администратора портала без роли в команде, сервер отвечает так же, как несуществующий.

## Параметры

| Параметр | Тип | Обяз. | По умолч. | Описание |
|----------|-----|:-----:|-----------|----------|
| `id` (path) | string | да | — | ID сервера. Список: [`GET /v1/infra/servers`](/docs/infra/servers/list) |
| `cursor` (query) | string | нет | — | Значение `nextCursor` из прошлой страницы, без изменений. Для первой страницы не передаётся |
| `limit` (query) | integer | нет | `50` | Записей на странице, от 1 до 100. Значение больше 100 ограничивается до 100. Ноль, отрицательное, нечисловое и пустое значение дают `400 INVALID_LIMIT` |
| `severity` (query) | string | нет | все | Важности через запятую: `error`, `warning`, `info` |
| `kind` (query) | string | нет | все | Виды записей через запятую, например `deploy.failed,wake.failed`. Список видов — в таблице ниже |
| `since` (query) | string | нет | — | Дата и время RFC 3339 с явным смещением от UTC, `Z` или `+03:00`, например `2026-10-06T12:00:00Z`. Возвращаются только записи, последнее повторение которых не раньше этого момента |

Пустое значение параметра считается переданным: `?severity=` даёт `400 INVALID_SEVERITY`, а не все важности.

**Страницы.** Записи идут от новых к старым по времени первого появления. Пока в ответе приходит непустой `nextCursor`, передавайте его в `cursor` следующего запроса без изменений. `null` означает последнюю страницу.

## Примеры

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

```bash
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/activity?limit=20"
```

### 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/activity?limit=20"
```

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

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/activity?limit=20`,
  { headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
)
const { data } = await res.json()

for (const e of data.events) {
  console.log(e.firstAt, e.severity, e.kind, e.code ?? '', e.count > 1 ? `×${e.count}` : '')
  if (e.ref?.type === 'deploy_operation') {
    console.log(`  подробности: /v1/infra/servers/${serverId}/operations/${e.ref.id}`)
  }
}
```

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

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

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

| Поле | Тип | Описание |
|------|-----|----------|
| `success` | boolean | Всегда `true` при успехе |
| `data.events` | array | Записи ленты, от новых к старым |
| `data.events[].id` | string | Идентификатор записи |
| `data.events[].kind` | string | Вид записи — таблица ниже |
| `data.events[].severity` | string | Важность: `error`, `warning` или `info` |
| `data.events[].code` | string \| null | Причина из закрытого списка вида. Значение, которого нет в списке, приходит как `other` |
| `data.events[].meta` | object \| null | Подробности вида — ключи из таблицы ниже, без свободного текста |
| `data.events[].ref` | object \| null | `{ type: "deploy_operation", id }` у записи о выкладке или команде, у которой есть операция: её исход читается через [`GET /v1/infra/servers/:id/operations/:operationId`](/docs/infra/deploy/server-operation). У остальных записей `null` |
| `data.events[].firstAt` | string | Первое появление, ISO 8601 |
| `data.events[].lastAt` | string | Последнее повторение, ISO 8601 |
| `data.events[].count` | number | Сколько раз событие произошло в пятиминутном окне записи |
| `data.nextCursor` | string \| null | Курсор следующей страницы. `null` на последней |

Виды записей:

| `kind` | Что произошло | `severity` | `code` | `meta` |
|--------|---------------|------------|--------|--------|
| `portal_event.delivery_paused` | Доставка событий портала приложению приостановлена | `warning` | `not_connected`, `http`, `http_hard`, `error` | `retryAt` |
| `portal_event.delivery_failed` | Событие портала не доставлено | `error` | `http_NNN` — код ответа приложения, `not_connected`, `no_app_url`, `park_expired`, `policy_denied` | `eventName`, `attempts` |
| `portal_event.subscription_degraded` | Подписка на события портала переведена в ухудшенный режим | `error` | `breaker_trips`, `max_attempts` | — |
| `portal_event.dropped` | Событие портала пришло к неактивной подписке и отброшено | `warning` | `subscription_inactive` | `eventName`, `subscriptionStatus` |
| `bot.delivery_deferred` | Доставка события боту отложена | `warning` | `NO_TUNNEL`, `AGENT_NOT_CONNECTED`, `TUNNEL_SEND_FAILED`, `GATEWAY_ERROR`, `BACKLOG` | `botId`, `eventType` |
| `bot.event_dropped` | Недоставленное событие бота удалено через 72 часа | `error` | `outbox_ttl_expired` | `botId` |
| `bot.disabled` | Бот отключён | `error` | `AUTH_FAILURES`, `B24_UNREGISTERED`, `PORTAL_DELETED`, `USER_BLOCKED` | `botId` |
| `bot.event_mode_changed` | Бот переключён между доставкой на адрес и забором событий запросами | `warning` | `webhook_to_fetch`, `fetch_to_webhook` | `botId`, `reason` |
| `deploy.succeeded` | Выкладка завершилась успешно | `info` | — | `durationMs` |
| `deploy.failed` | Выкладка провалилась | `error` | тот же код, что `error.code` исхода операции, либо `null` — разбор в «Известных особенностях» | `step` |
| `deploy.unknown` | Исход выкладки неизвестен | `warning` | — | `step` |
| `exec.failed` | Команда не запускалась | `warning` | код отказа — тот же, что `error.code` исхода операции, либо `null` | — |
| `wake.succeeded` | Сервер проснулся по блокирующему пробуждению | `info` | — | `trigger` |
| `wake.failed` | Сервер не проснулся | `error` | код отказа пробуждения | `trigger` |
| `server.status_changed` | Статус сервера сменился | `error` при переходе в `error`, `warning` при заморозке по балансу, в остальных случаях `info` | — | `from`, `to`, `reason` |

`meta.trigger` — кто будил сервер: `manual`, `api`, `deploy`, `gateway`, `auto-wake-on-preempt`, `auto-wake-on-unfreeze`, `scheduled`. `meta.from` и `meta.to` — статусы сервера строчными, как в [`GET /v1/infra/servers/:id`](/docs/infra/servers/get). `meta.reason` у смены статуса равен `billing_freeze`, когда работающий сервер усыплён заморозкой по балансу, иначе поля нет.

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

```json
{
  "success": true,
  "data": {
    "events": [
      {
        "id": "0b6f5b0e-3c4d-4a51-9a53-3f1d2c7e8a10",
        "kind": "deploy.failed",
        "severity": "error",
        "code": "DOWNLOAD_HTTP_STATUS",
        "meta": {
          "step": "download"
        },
        "ref": {
          "type": "deploy_operation",
          "id": "cmop1abcdefghijklmnopqrs"
        },
        "firstAt": "2026-10-06T12:13:32.251Z",
        "lastAt": "2026-10-06T12:13:32.251Z",
        "count": 1
      },
      {
        "id": "5e2a7c41-9d8b-4f06-8e1a-6b0c4d9f2e33",
        "kind": "server.status_changed",
        "severity": "info",
        "code": null,
        "meta": {
          "to": "provisioning",
          "from": "sleeping"
        },
        "ref": null,
        "firstAt": "2026-10-06T12:13:28.740Z",
        "lastAt": "2026-10-06T12:13:28.740Z",
        "count": 1
      },
      {
        "id": "a3d9e2f7-6b1c-4e8a-b5d0-7c2f9e4a1b68",
        "kind": "server.status_changed",
        "severity": "info",
        "code": null,
        "meta": {
          "to": "sleeping",
          "from": "running"
        },
        "ref": null,
        "firstAt": "2026-10-06T12:13:28.367Z",
        "lastAt": "2026-10-06T12:13:28.367Z",
        "count": 1
      },
      {
        "id": "c71f04b2-8e5a-4d93-9f6e-2b8a0d5c3e17",
        "kind": "server.status_changed",
        "severity": "info",
        "code": null,
        "meta": {
          "to": "running",
          "from": "provisioning"
        },
        "ref": null,
        "firstAt": "2026-10-06T12:13:08.096Z",
        "lastAt": "2026-10-06T12:13:29.035Z",
        "count": 2
      }
    ],
    "nextCursor": null
  }
}
```

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

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

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

## Ошибки

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `INVALID_LIMIT` | `limit` равен нулю, отрицательный, нечисловой или пустой |
| 400 | `INVALID_CURSOR` | `cursor` не получен из `nextCursor` или изменён |
| 400 | `INVALID_SEVERITY` | В `severity` есть значение кроме `error`, `warning`, `info`, либо параметр пустой |
| 400 | `INVALID_KIND` | В `kind` есть вид, которого нет в таблице видов, либо параметр пустой |
| 400 | `INVALID_SINCE` | `since` — не дата и время RFC 3339 или без смещения от UTC |
| 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` | Сервера нет, он удалён, недоступен этому ключу или лента для владельца сервера ещё не включена. Случаи намеренно неразличимы |
| 429 | `RATE_LIMITED` | Превышен лимит 60 запросов в минуту на ключ. Точное значение — в заголовке `x-ratelimit-limit`, потолок делится на реплики |

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

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

- **Повтор не создаёт новую запись.** Одинаковые события сервера в одном пятиминутном окне склеиваются в одну запись: растёт `count`, сдвигается `lastAt`. Окна выровнены по пяти минутам UTC: 12:10–12:15, 12:15–12:20 и так далее. Запись при этом не переезжает в начало ленты, порядок держится по `firstAt`, поэтому обход по `nextCursor` идёт без пропусков и повторов, даже если сервер продолжает сбоить во время чтения. Исключение — смена статуса внутри долгой операции, см. ниже.
- **`firstAt` — момент записи в ленту, а не самого события.** Запись появляется через несколько секунд после события: выкладка из примера ответа завершилась в 12:13:28, а её запись датирована 12:13:32.
- **Пробуждение видно сменами статуса.** Спящая отдельная виртуальная машина просыпается цепочкой `sleeping` → `provisioning` → `running`, Galaxy-приложение — сразу `sleeping` → `running`. Запись `wake.succeeded` появляется только после блокирующего пробуждения, которое дождалось подъёма сервера. Неблокирующее пробуждение оставляет в ленте только смены статуса.
- **Код в записи о выкладке — не верхний код ответа выкладки.** Ответ [`POST /deploy`](/docs/infra/deploy/deploy) при провале шага несёт `error.code: "DEPLOY_FAILED"`, а причину шага — в `error.causeCode`. Запись `deploy.failed` несёт тот же код, что `error.code` исхода операции: причину шага, если она известна, например `DOWNLOAD_HTTP_STATUS` при провале загрузки, `DEPLOY_STEP_FAILED`, если шаг провалился без названной причины, и `DEPLOY_FAILED` при сбое самой платформы.
- **Текст ошибки выкладки в ленте не хранится** — только код. Сообщение и шаг читаются по `ref.id` через [`GET /v1/infra/servers/:id/operations/:operationId`](/docs/infra/deploy/server-operation), пока платформа хранит исход, — 7 суток.
- **Глубина истории.** Смены статуса записываются для каждого сервера с момента выпуска записи смен статуса, остальные записи — с момента, когда лента включена для владельца сервера. Записи хранятся 30 дней. В первые 30 дней после включения история короче.
- **Смена статуса может появиться ниже уже прочитанной страницы**, если статус менялся внутри долгой операции платформы: у записи время начала этой операции. Следующее чтение ленты с первой страницы её покажет.
- **Лента принадлежит серверу.** После передачи сервера другому владельцу новый владелец видит записи, сделанные до передачи, а прежний получает `404`.

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

- [Исход операции из ленты сервера](/docs/infra/deploy/server-operation)
- [Получить сервер](/docs/infra/servers/get)
- [Последние операции сервера](/docs/infra/deploy/operations)
- [Логи сервиса](/docs/infra/deploy/logs)
- [Подписки на события портала](/docs/infra/event-subscriptions)
- [MCP](/docs/mcp)
