## Server activity feed

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

Returns a timeline of failures and key events for a server: undelivered Bitrix24 events, bot delivery problems, deployments, wake-ups and status changes.

Call this first when an application has stopped working, a bot is silent, a Bitrix24 event did not arrive or a deployment failed: an entry names the cause as a machine-readable code, and an entry about a deployment or a command leads to its outcome. The feed does not record API call errors, a bot that polls for events itself, or successful deliveries, so an empty feed does not prove that events were delivered.

The feed is read by the server owner with a personal key or with the key of their own OAuth application from the same Bitrix24 account, and by a development team member whose role includes server settings, with their own personal key. An OAuth application key reads the feed with the rights of its owner: the session user does not affect access, and the session token can be omitted. Keys issued for a narrow task with their own list of routes (an agent maintenance key, an application external API key, a team member key and an external collaborator key) do not read the feed and get `403` with a code of the form `…_KEY_OUT_OF_SCOPE`. For everyone else, including a Bitrix24 account administrator without a role in the team, the server responds as if it did not exist.

## Parameters

| Parameter | Type | Required | Default | Description |
|----------|-----|:-----:|-----------|----------|
| `id` (path) | string | yes | — | Server ID. List: [`GET /v1/infra/servers`](/docs/infra/servers/list) |
| `cursor` (query) | string | no | — | The `nextCursor` value from the previous page, unchanged. Not passed for the first page |
| `limit` (query) | integer | no | `50` | Entries per page, from 1 to 100. A value above 100 is capped at 100. Zero, negative, non-numeric and empty values give `400 INVALID_LIMIT` |
| `severity` (query) | string | no | all | Severities, comma-separated: `error`, `warning`, `info` |
| `kind` (query) | string | no | all | Entry kinds, comma-separated, for example `deploy.failed,wake.failed`. The list of kinds is in the table below |
| `since` (query) | string | no | — | An RFC 3339 date and time with an explicit UTC offset, `Z` or `+03:00`, for example `2026-10-06T12:00:00Z`. Only entries whose last repetition is not earlier than this moment are returned |

An empty parameter value counts as passed: `?severity=` gives `400 INVALID_SEVERITY`, not all severities.

**Pages.** Entries go from newest to oldest by time of first appearance. While the response carries a non-empty `nextCursor`, pass it in `cursor` of the next request unchanged. `null` means the last page.

## Examples

### curl — personal key

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

### curl — OAuth application

```bash
curl -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/activity?limit=20"
```

### JavaScript — personal key

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/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(`  details: /v1/infra/servers/${serverId}/operations/${e.ref.id}`)
  }
}
```

### JavaScript — OAuth application

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

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` on success |
| `data.events` | array | Feed entries, from newest to oldest |
| `data.events[].id` | string | Entry identifier |
| `data.events[].kind` | string | Entry kind — see the table below |
| `data.events[].severity` | string | Severity: `error`, `warning` or `info` |
| `data.events[].code` | string \| null | Cause from the closed list for the kind. A value that is not in the list arrives as `other` |
| `data.events[].meta` | object \| null | Kind details — keys from the table below, no free text |
| `data.events[].ref` | object \| null | `{ type: "deploy_operation", id }` on an entry about a deployment or a command that has an operation: its outcome is read through [`GET /v1/infra/servers/:id/operations/:operationId`](/docs/infra/deploy/server-operation). On all other entries it is `null` |
| `data.events[].firstAt` | string | First appearance, ISO 8601 |
| `data.events[].lastAt` | string | Last repetition, ISO 8601 |
| `data.events[].count` | number | How many times the event occurred in the entry's five-minute window |
| `data.nextCursor` | string \| null | Cursor of the next page. `null` on the last one |

Entry kinds:

| `kind` | What happened | `severity` | `code` | `meta` |
|--------|---------------|------------|--------|--------|
| `portal_event.delivery_paused` | Delivery of Bitrix24 events to the application is paused | `warning` | `not_connected`, `http`, `http_hard`, `error` | `retryAt` |
| `portal_event.delivery_failed` | A Bitrix24 event was not delivered | `error` | `http_NNN` — the application's response code, `not_connected`, `no_app_url`, `park_expired`, `policy_denied` | `eventName`, `attempts` |
| `portal_event.subscription_degraded` | The Bitrix24 event subscription was moved to degraded mode | `error` | `breaker_trips`, `max_attempts` | — |
| `portal_event.dropped` | A Bitrix24 event arrived at an inactive subscription and was dropped | `warning` | `subscription_inactive` | `eventName`, `subscriptionStatus` |
| `bot.delivery_deferred` | Delivery of an event to the bot was deferred | `warning` | `NO_TUNNEL`, `AGENT_NOT_CONNECTED`, `TUNNEL_SEND_FAILED`, `GATEWAY_ERROR`, `BACKLOG` | `botId`, `eventType` |
| `bot.event_dropped` | An undelivered bot event was deleted after 72 hours | `error` | `outbox_ttl_expired` | `botId` |
| `bot.disabled` | The bot was disabled | `error` | `AUTH_FAILURES`, `B24_UNREGISTERED`, `PORTAL_DELETED`, `USER_BLOCKED` | `botId` |
| `bot.event_mode_changed` | The bot was switched between delivery to an address and polling for events | `warning` | `webhook_to_fetch`, `fetch_to_webhook` | `botId`, `reason` |
| `deploy.succeeded` | The deployment completed successfully | `info` | — | `durationMs` |
| `deploy.failed` | The deployment failed | `error` | the same code as `error.code` of the operation outcome, or `null` — explained in "Known specifics" | `step` |
| `deploy.unknown` | The deployment outcome is unknown | `warning` | — | `step` |
| `exec.failed` | The command did not run | `warning` | failure code — the same as `error.code` of the operation outcome, or `null` | — |
| `wake.succeeded` | The server woke up through a blocking wake-up | `info` | — | `trigger` |
| `wake.failed` | The server did not wake up | `error` | wake-up failure code | `trigger` |
| `server.status_changed` | The server status changed | `error` on transition to `error`, `warning` on a balance freeze, `info` otherwise | — | `from`, `to`, `reason` |

`meta.trigger` — who woke the server: `manual`, `api`, `deploy`, `gateway`, `auto-wake-on-preempt`, `auto-wake-on-unfreeze`, `scheduled`. `meta.from` and `meta.to` are server statuses in lowercase, as in [`GET /v1/infra/servers/:id`](/docs/infra/servers/get). `meta.reason` of a status change equals `billing_freeze` when a running server was put to sleep by a balance freeze; otherwise the field is absent.

## Response example

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

## Error response example

404 — the server does not exist, is unavailable to this key, or the feed is not yet enabled for the server owner:

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

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_LIMIT` | `limit` is zero, negative, non-numeric or empty |
| 400 | `INVALID_CURSOR` | `cursor` was not obtained from `nextCursor` or was altered |
| 400 | `INVALID_SEVERITY` | `severity` contains a value other than `error`, `warning`, `info`, or the parameter is empty |
| 400 | `INVALID_KIND` | `kind` contains a kind that is not in the kinds table, or the parameter is empty |
| 400 | `INVALID_SINCE` | `since` is not an RFC 3339 date and time or has no UTC offset |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |
| 401 | `INVALID_API_KEY` | The key is not recognized |
| 401 | `INVALID_SESSION` | An OAuth application key was passed with a session token that is not recognized or has expired |
| 403 | `SESSION_APP_MISMATCH` | The session token was issued to a different application or for a different portal than the OAuth application key in `X-Api-Key` |
| 403 | `AGENT_MAINTENANCE_KEY_OUT_OF_SCOPE` | The request was made with an agent maintenance key: it has its own list of routes |
| 403 | `APP_API_KEY_OUT_OF_SCOPE` | The request was made with an application external API key: it has its own list of routes |
| 403 | `EXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE` | The request was made with an external collaborator key: it has its own list of routes |
| 403 | `PORTAL_COLLABORATOR_KEY_OUT_OF_SCOPE` | The request was made with a team member key. A team member reads the feed with their personal key |
| 403 | `SERVER_ROLE_FORBIDDEN` | You are on the server's development team, but the role does not include server settings |
| 404 | `SERVER_NOT_FOUND` | The server does not exist, was deleted, is unavailable to this key, or the feed is not yet enabled for the server owner. The cases are intentionally indistinguishable |
| 429 | `RATE_LIMITED` | The limit of 60 requests per minute per key was exceeded. The exact value is in the `x-ratelimit-limit` header; the ceiling is divided among replicas |

The full list of common API errors is in [Errors](/docs/errors).

## Known specifics

- **A repeat does not create a new entry.** Identical server events within one five-minute window are merged into one entry: `count` grows and `lastAt` moves. Windows are aligned to five-minute UTC boundaries: 12:10–12:15, 12:15–12:20 and so on. The entry does not move to the top of the feed; the order is kept by `firstAt`, so paging through `nextCursor` has no gaps or repeats, even if the server keeps failing during reading. The exception is a status change inside a long operation, see below.
- **`firstAt` is the moment of writing to the feed, not of the event itself.** An entry appears a few seconds after the event: the deployment in the response example finished at 12:13:28, and its entry is dated 12:13:32.
- **A wake-up is visible through status changes.** A sleeping standalone virtual machine wakes through the chain `sleeping` → `provisioning` → `running`, a Galaxy application goes straight `sleeping` → `running`. A `wake.succeeded` entry appears only after a blocking wake-up that waited for the server to come up. A non-blocking wake-up leaves only status changes in the feed.
- **The code in a deployment entry is not the top-level code of the deployment response.** The response of [`POST /deploy`](/docs/infra/deploy/deploy) carries `error.code: "DEPLOY_FAILED"` when a step fails, and the step cause in `error.causeCode`. A `deploy.failed` entry carries the same code as `error.code` of the operation outcome: the step cause if it is known, for example `DOWNLOAD_HTTP_STATUS` when a download fails, `DEPLOY_STEP_FAILED` if the step failed without a named cause, and `DEPLOY_FAILED` on a failure of the platform itself.
- **The deployment error text is not stored in the feed**, only the code. The message and the step are read by `ref.id` through [`GET /v1/infra/servers/:id/operations/:operationId`](/docs/infra/deploy/server-operation), while the platform retains the outcome (7 days).
- **History depth.** Status changes are recorded for every server since the status change recording was released; other entries, since the moment the feed was enabled for the server owner. Entries are kept for 30 days. In the first 30 days after enabling, the history is shorter.
- **A status change can appear below an already read page** if the status changed inside a long platform operation: the entry carries the start time of that operation. The next read of the feed from the first page will show it.
- **The feed belongs to the server.** After a server is transferred to another owner, the new owner sees the entries made before the transfer, and the previous one gets `404`.

## See also

- [Operation outcome from the server feed](/docs/infra/deploy/server-operation)
- [Get a server](/docs/infra/servers/get)
- [Recent server operations](/docs/infra/deploy/operations)
- [Service logs](/docs/infra/deploy/logs)
- [Bitrix24 event subscriptions](/docs/infra/event-subscriptions)
- [MCP](/docs/mcp)
