
# Infrastructure

Create and manage virtual servers for deploying Bitrix24 applications. Each server is invisible from the internet by default (Black Hole mode) — the application is reachable only through the HTTPS subdomain `app-{id}.vibecode.bitrix24.com`. Server management and deployment go through the REST API without SSH.

**Scope:** `vibe:infra` (added automatically to every API key) · **Base URL:** `https://vibecode.bitrix24.com/v1` · **Authorization:** `X-Api-Key` header

## Documentation sections

- [Providers and catalogs](/docs/infra/providers) — list of providers, plans, regions, and OS images (4 endpoints).
- [Servers](/docs/infra/servers) — create, list, details, update name and description, delete (5 endpoints).
- [Lifecycle](/docs/infra/lifecycle) — start, stop, sleep, wake, tunnel repair, provisioning status (9 endpoints).
- [Scheduled wake](/docs/infra/wake-schedules) — recurring wake windows for a sleeping server on a cron schedule (4 endpoints).
- [Access and modes](/docs/infra/access) — access policy, user/department list, SSH credentials, BLACKHOLE↔OPEN mode (7 endpoints).
- [Deploy API](/docs/infra/deploy) — run commands, upload files, deploy the application, logs, port, metrics, lock, runtimes (8 endpoints).
- [Access tokens](/docs/infra/access-tokens) — short-lived tokens for e2e checks and shareable links (3 endpoints).
- [What arrives at the application](/docs/infra/app-runtime) — Gateway injection of `X-Vibe-Authorization: Bearer`, reading identity via `/v1/me`, handler skeletons in Node/Python/Go.
- [Portal event subscriptions](/docs/infra/event-subscriptions) — delivery of Bitrix24 events (`ONTASKADD` and similar) to the app through the tunnel, without polling (3 endpoints).
- [Galaxy app](/docs/infra/galaxy) — the "container in a shared galaxy" placement model: how to tell it from a regular server (`kind`), the build-on-upload lifecycle, per-galaxy pricing.
- [Server access recovery](/docs/infra/server-access-recovery) — the server is running but a new key cannot see it: an empty list, `404 NOT_FOUND`, changing the managing key.

## What to know up front

1. **The application port is always 3000.** The Black Hole tunnel proxies exactly this port; you don't need to change it. The server is an isolated environment: `:3000` inside the virtual machine has no relation to the ports on your local machine.
2. **Deploy API is BLACKHOLE-only.** All of `/deploy`, `/exec`, `/upload`, `/logs` require servers in `BLACKHOLE` mode with the tunnel agent in status `CONNECTED`. For OPEN servers they return an error.
3. **`/deploy` and `/exec` return JSON by default.** This is safe for AI agents and MCP clients — no extra query parameters are needed. If you genuinely need a streaming response (live deploy-step output in the UI), pass `?stream=true` — then you get SSE (Server-Sent Events). The documentation used to claim the opposite (SSE by default, `?stream=false` for JSON) — that is outdated and no longer matches the API behavior.
4. **`accessPolicy` is security.** Changing the policy from `OWNER_ONLY` to `PORTAL`/`AUTHENTICATED`/`PUBLIC` opens the application to other users. **Never change `accessPolicy` without explicit user confirmation.**
5. **The server is a clean Ubuntu 24.04 with root access enabled.** The virtual machine is created from a stock Ubuntu image with no preinstalled software (except the tunnel agent). The agent runs as the `root` user — no `sudo` is needed in `preStart`, `install`, or `/exec` commands. Outbound internet is unrestricted: `apt-get`, `curl`, `wget`, `pip` work directly. Inbound traffic is blocked except the tunnel connection. **The app itself, however, does not run as `root` — it runs under a dedicated unprivileged account.** This applies to the app process only. Deploy commands and `/exec` still run with administrator privileges. Details and how to opt out — [Deploying an app](/docs/infra/deploy/deploy).
6. **The Bitrix24 plan plays a dual role.** First, the Bitrix24 REST API itself is available only on commercial Bitrix24 account plans — without one, neither applications, nor the `/v1/deals` proxy, nor bots, nor any other call proxied into Bitrix24 work. Second, on top of that — creating servers, deploying, and waking require a qualifying Bitrix24 plan: a commercial plan grants full access, a demo plan grants a limited trial, and a free plan is declined with `INT_TARIFF_REQUIRED`. This is an AI Beta; terms may change at any time. AI Router works independently of the Bitrix24 plan — it does not proxy into REST and is available even on free plans (BYOK free; platform models are billed against the Vibecode balance). Details are in the "Plan and access" section below.
7. **User authorization inside the application.** On every request the Gateway injects six headers prefixed with `X-Vibe-`: `Request-Id` always, plus `User-Id`, `User-Name`, `User-Role`, `Portal-Id`, `Authorization` (`Bearer vibe_session_<…>`) for an authenticated request. The browser never sees or stores the `Authorization` token — it lives only between the Gateway and the app server. For a quick user identifier the `X-Vibe-User-Id` header is enough; the full context (scopes, `capabilities`, plan, application info) comes from a single `GET /v1/me` call with server-side caching. The user ID in the `/v1/me` response is `data.currentUser.bitrixUserId`; the Bitrix24 account domain is `data.portal`. The full header table, the BFF pattern, and handler skeletons in Node/Python/Go — [What arrives at the application](/docs/infra/app-runtime).
8. **Creating servers requires a user session for `vibe_app_` keys.** `POST /v1/infra/servers` is gated by a plan check that needs to know exactly who is creating the server. For `vibe_api_` the user context already lives in the key itself; for `vibe_app_` an `Authorization: Bearer <session>` is required — without it the response is `401 UNAUTHENTICATED` with an `error.hint` pointing at the OAuth flow. Reads and the Deploy API on already-existing servers don't require a session — the "Endpoint authorization" table below collects all the rules in one place.

## Quick start

Three calls — create a server and run the application.

### curl — personal key

```bash
export VIBE_KEY="YOUR_API_KEY"

# 1. Create a server (automatically in Black Hole mode)
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: $VIBE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "bitrix-cloud",
    "name": "my-app",
    "plan": "bc-small",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts"
  }'

# 2. Wait until ready: status=running AND blackholeStatus=CONNECTED
curl -H "X-Api-Key: $VIBE_KEY" \
  https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID

# 3. Deploy the application (?stream=false — JSON instead of SSE).
#    X-Skip-Source-Snapshot: deploy from an external URL while source
#    storage is enabled, otherwise 409 SNAPSHOT_REQUIRED (see below).
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/deploy?stream=false" \
  -H "X-Api-Key: $VIBE_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Skip-Source-Snapshot: deploy from external URL" \
  -d '{
    "source": { "url": "https://github.com/user/app/archive/main.tar.gz" },
    "runtime": "node20",
    "install": "cd /opt/app && npm install --production",
    "start": "cd /opt/app && node server.js",
    "port": 3000
  }'
```

The application is reachable at `https://app-{id}.vibecode.bitrix24.com` — the `appUrl` field in the `/deploy` response.

**Deploying from an external URL and source storage.** When [source storage](/docs/source-storage) is enabled for your Bitrix24 account, a deploy from an external address — not from Vibecode storage — returns `409 SNAPSHOT_REQUIRED` so that the application's version history is not lost. The `X-Skip-Source-Snapshot: <reason>` header continues the deploy without saving a snapshot. To keep a snapshot, first upload the archive via `POST /v1/apps/:id/sources`, then deploy it via `{ "source": { "versionId": "vN" } }`. More details — [Source storage](/docs/source-storage).

### curl — OAuth application

```bash
# Same thing, just add the Authorization: Bearer header with the session token
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "bitrix-cloud", "name": "my-app", "plan": "bc-small", "region": "bc-eu-central", "image": "ubuntu-2404-lts" }'
```

## Full example

A realistic JavaScript scenario — create a server, wait until ready, deploy, get the application URL.

```javascript
const VIBE_KEY = process.env.VIBE_KEY
const BASE = 'https://vibecode.bitrix24.com/v1'

async function api(method, path, body = null, extraHeaders = {}) {
  const opts = { method, headers: { 'X-Api-Key': VIBE_KEY, ...extraHeaders } }
  if (body) {
    opts.headers['Content-Type'] = 'application/json'
    opts.body = JSON.stringify(body)
  }
  const res = await fetch(`${BASE}${path}`, opts)
  if (!res.ok) throw new Error(`${method} ${path} → ${res.status}`)
  return res.json()
}

// 1. Pick provider, plan, region, image
const { data: plans } = await api('GET', '/infra/providers/bitrix-cloud/plans')
const { data: regions } = await api('GET', '/infra/providers/bitrix-cloud/regions')
const { data: images } = await api('GET', '/infra/providers/bitrix-cloud/images')

const plan = plans.find(p => p.id === 'bc-small')
const region = regions.find(r => r.id === 'bc-eu-central')
const image = images[0]

// 2. Create a server (always in Black Hole)
const { data: server } = await api('POST', '/infra/servers', {
  provider: 'bitrix-cloud',
  name: 'my-crm-bot',
  plan: plan.id,
  region: region.id,
  image: image.id,
})
console.log(`Server created: ${server.id}, subdomain: ${server.subdomain}`)

// 3. Wait until status becomes running AND blackholeStatus becomes CONNECTED
let info = server
while (info.status !== 'running' || info.blackholeStatus !== 'CONNECTED') {
  await new Promise(r => setTimeout(r, 10000)) // 10 seconds between polls
  const res = await api('GET', `/infra/servers/${server.id}`)
  info = res.data
  console.log(`status=${info.status}, blackhole=${info.blackholeStatus}`)
}

// 4. Deploy the application (?stream=false is required for a JSON response).
//    The X-Skip-Source-Snapshot header is needed when deploying from an
//    external URL while source storage is enabled — otherwise 409 SNAPSHOT_REQUIRED.
const deploy = await api('POST', `/infra/servers/${server.id}/deploy?stream=false`, {
  source: { url: 'https://github.com/user/app/archive/main.tar.gz' },
  runtime: 'node20',
  install: 'cd /opt/app && npm install --production',
  preStart: 'cd /opt/app && npx prisma migrate deploy',
  start: 'cd /opt/app && node server.js',
  port: 3000,
  env: { NODE_ENV: 'production' },
}, { 'X-Skip-Source-Snapshot': 'deploy from external URL' })

console.log(`Application is live: ${deploy.data.appUrl}`)

// 5. (Optional) Configure auto-sleep after 60 minutes of inactivity
await api('PATCH', `/infra/servers/${server.id}/sleep`, { sleepAfterMinutes: 60 })
```

> **Galaxy app — a different contract.** If your Bitrix24 account places apps in galaxies, the same `POST /v1/infra/servers` creates a Galaxy app — a container on a shared host. Upload the code **right after creation**, without waiting for `blackholeStatus: "CONNECTED"`. The full model, lifecycle, cost, and deploy differences — [Galaxy app](./infra/galaxy).

## Endpoint reference

Endpoint reference for the section. Links lead to pages with parameters, examples, and error codes.

**Providers and catalogs:**

| Method | Path | Description |
|-------|------|----------|
| GET | [`/v1/infra/providers`](/docs/infra/providers/list) | List of cloud providers |
| GET | [`/v1/infra/providers/:providerId/plans`](/docs/infra/providers/plans) | Provider plans |
| GET | [`/v1/infra/providers/:providerId/regions`](/docs/infra/providers/regions) | Provider regions |
| GET | [`/v1/infra/providers/:providerId/images`](/docs/infra/providers/images) | OS images |

**Servers:**

| Method | Path | Description |
|-------|------|----------|
| POST | [`/v1/infra/servers`](/docs/infra/servers/create) | Create a server (always Black Hole) |
| GET | [`/v1/infra/servers`](/docs/infra/servers/list) | List of your servers |
| GET | [`/v1/infra/servers/:id`](/docs/infra/servers/get) | Server details |
| PATCH | [`/v1/infra/servers/:id`](/docs/infra/servers/update) | Update the name and description |
| DELETE | [`/v1/infra/servers/:id`](/docs/infra/servers/delete) | Delete a server |

**Lifecycle:**

| Method | Path | Description |
|-------|------|----------|
| POST | [`/v1/infra/servers/:id/start`](/docs/infra/lifecycle/start) | Start a stopped/sleeping server |
| POST | [`/v1/infra/servers/:id/stop`](/docs/infra/lifecycle/stop) | Stop a running server |
| POST | [`/v1/infra/servers/:id/reboot`](/docs/infra/lifecycle/reboot) | Reboot the server |
| POST | [`/v1/infra/servers/:id/wake`](/docs/infra/lifecycle/wake) | Wake a sleeping server (async or blocking) |
| POST | [`/v1/infra/servers/:id/sleep-now`](/docs/infra/lifecycle/sleep-now) | Immediately put a BLACKHOLE server to sleep |
| PATCH | [`/v1/infra/servers/:id/sleep`](/docs/infra/lifecycle/sleep) | Configure auto-sleep |
| POST | [`/v1/infra/servers/:id/refresh`](/docs/infra/lifecycle/refresh) | Request status and IP from the provider |
| POST | [`/v1/infra/servers/:id/repair`](/docs/infra/lifecycle/repair) | Restore the tunnel via serial console |
| GET | [`/v1/infra/servers/:id/repair-status`](/docs/infra/lifecycle/repair-status) | Tunnel restore progress |

**Scheduled wake:**

| Method | Path | Description |
|-------|------|----------|
| GET | [`/v1/infra/servers/:id/wake-schedules`](/docs/infra/wake-schedules/list) | List wake windows and their firing history |
| POST | [`/v1/infra/servers/:id/wake-schedules`](/docs/infra/wake-schedules/create) | Create a wake window |
| PATCH | [`/v1/infra/servers/:id/wake-schedules/:scheduleId`](/docs/infra/wake-schedules/update) | Update a wake window |
| DELETE | [`/v1/infra/servers/:id/wake-schedules/:scheduleId`](/docs/infra/wake-schedules/delete) | Delete a wake window |

**Access and modes:**

| Method | Path | Description |
|-------|------|----------|
| GET | [`/v1/infra/servers/:id/ssh`](/docs/infra/access/ssh) | SSH credentials (OPEN only) |
| PATCH | [`/v1/infra/servers/:id/mode`](/docs/infra/access/mode) | Switch BLACKHOLE↔OPEN |
| PATCH | [`/v1/infra/servers/:id/access-policy`](/docs/infra/access/access-policy) | Application access policy |
| GET | [`/v1/infra/servers/:id/access`](/docs/infra/access/access-list) | List of users and departments with access |
| POST | [`/v1/infra/servers/:id/access`](/docs/infra/access/access-add) | Add a user or department |
| DELETE | [`/v1/infra/servers/:id/access/:accessId`](/docs/infra/access/access-delete) | Remove an access entry |
| GET | [`/v1/infra/servers/:id/b24-users`](/docs/infra/access/b24-users) | Search Bitrix24 account users |

**Deploy API:**

| Method | Path | Description |
|-------|------|----------|
| POST | [`/v1/infra/servers/:id/exec`](/docs/infra/deploy/exec) | Run a command (SSE or JSON) |
| POST | [`/v1/infra/servers/:id/upload`](/docs/infra/deploy/upload) | Upload a file (base64 or by URL) |
| GET | [`/v1/infra/servers/:id/logs`](/docs/infra/deploy/logs) | Service logs (`journalctl` utility) |
| POST | [`/v1/infra/servers/:id/deploy`](/docs/infra/deploy/deploy) | Full application deploy |
| PATCH | [`/v1/infra/servers/:id/port`](/docs/infra/deploy/port) | Set the application port |
| GET | [`/v1/infra/servers/:id/metrics`](/docs/infra/deploy/metrics) | Tunnel activity metrics |
| DELETE | [`/v1/infra/servers/:id/lock`](/docs/infra/deploy/lock) | Release a stuck operation lock |
| GET | [`/v1/infra/runtimes`](/docs/infra/deploy/runtimes) | List of available runtimes |

**Access tokens:**

| Method | Path | Description |
|-------|------|----------|
| POST | [`/v1/infra/servers/:id/access-tokens`](/docs/infra/access-tokens/create) | Issue an access token (`api-bearer` or `share-url`) |
| GET | [`/v1/infra/servers/:id/access-tokens`](/docs/infra/access-tokens/list) | List of server tokens |
| DELETE | [`/v1/infra/servers/:id/access-tokens/:tokenId`](/docs/infra/access-tokens/delete) | Revoke a token |

**Portal event subscriptions:**

| Method | Path | Description |
|-------|------|----------|
| POST | [`/v1/infra/servers/:id/event-subscriptions`](/docs/infra/event-subscriptions) | Subscribe the server to a portal event (`event.bind` under the OAuth app) |
| GET | [`/v1/infra/servers/:id/event-subscriptions`](/docs/infra/event-subscriptions) | List subscriptions + recent deliveries |
| DELETE | [`/v1/infra/servers/:id/event-subscriptions/:subId`](/docs/infra/event-subscriptions) | Remove a subscription |

## Endpoint authorization

All infra endpoints require the `X-Api-Key` header. For `vibe_app_` keys (bound to an OAuth application) some POST operations additionally require `Authorization: Bearer <session>` — without it the response is `401 UNAUTHENTICATED` with an `error.hint`. For `vibe_api_` keys the user context already lives in the key itself, so a separate session is not needed.

| Endpoint | `X-Api-Key` | `Authorization: Bearer` for `vibe_app_` | When Bearer is required |
|----------|:-----------:|:--------------------------------------:|----------------------|
| `GET /v1/infra/providers/*` | yes | no | — |
| `GET /v1/infra/servers`, `GET /v1/infra/servers/:id` | yes | no | — |
| `GET /v1/infra/servers/:id/logs`, `/metrics`, `/access`, `/access-tokens`, `/b24-users`, `/ssh` | yes | no | — |
| `GET /v1/infra/runtimes` | yes | no | — |
| `POST /v1/infra/servers` (create a server) | yes | **yes** | Plan gate: the platform needs to know exactly who is creating the server. |
| `POST /v1/infra/servers/:id/deploy`, `/exec`, `/upload` | yes | no | The `vibe:infra` scope on the key is enough. |
| `POST /v1/infra/servers/:id/start`, `/stop`, `/reboot`, `/wake`, `/sleep-now`, `/refresh`, `/repair`, `PATCH /sleep` | yes | no | — |
| `GET /v1/infra/servers/:id/wake-schedules` | yes | no | — |
| `POST /v1/infra/servers/:id/wake-schedules`, `PATCH/DELETE .../wake-schedules/:scheduleId` | yes | no | Requires scheduled wake to be enabled for the Bitrix24 account; otherwise `403 WAKE_SCHEDULE_DISABLED`. |
| `PATCH /v1/infra/servers/:id/mode`, `/access-policy`, `/port` | yes | no | — |
| `POST /v1/infra/servers/:id/access`, `DELETE /v1/infra/servers/:id/access/:accessId` | yes | no | — |
| `POST /v1/infra/servers/:id/access-tokens`, `DELETE .../:tokenId` | yes | no | The platform flag `accessTokensEnabled` must be enabled; otherwise `503 FEATURE_DISABLED`. |
| `POST/GET/DELETE /v1/infra/servers/:id/event-subscriptions` | yes | no | The server must be backed by an OAuth app with an `application_token`; otherwise `400 NOT_OAUTH_APP`. |
| `PATCH /v1/infra/servers/:id` (name and description) | yes | no | — |
| `DELETE /v1/infra/servers/:id` | yes | no | — |
| `DELETE /v1/infra/servers/:id/lock` | yes | no | — |

Quick check before calling: `GET /v1/me` → `data.capabilities.servers.create.available`. For `vibe_app_` without a session it returns `false` with `reason: "SESSION_REQUIRED"` and a hint in `userMessage` — the model immediately sees that it needs to go through the OAuth flow rather than hitting a `401` on the `POST /v1/infra/servers` itself.

## Limits

| Limit | Value |
|-------|----------|
| Servers per API key | Set by the platform |
| Deploy API operations per minute per server | 10 |
| Concurrent `exec`/`deploy` per server | 1 |
| `exec` timeout | 1–600 seconds (default 300) |
| base64 body size (`upload` inline, `source.content`) | 500 MB |
| File size via `source.url` / `upload url` | 500 MB |
| multipart archive size in `deploy` | 500 MB |
| `/ssh` request rate | up to 10 per minute |

The platform rate limit is shared across all V1 endpoints, [see the "Limits and optimization" section](/docs/optimization).

## Server statuses

| Status | Description |
|--------|----------|
| `provisioning` | The virtual machine is being created at the provider (1–3 minutes) |
| `running` | The virtual machine is running, IP assigned. For the tunnel you also need `blackholeStatus: CONNECTED` |
| `sleeping` | Stopped by the sleep timer or manually. Wakes automatically on a request to the HTTPS subdomain or a `/deploy`/`/start`/`/wake` call |
| `error` | The server is not in a working state: the virtual machine was deleted at the provider, the agent hasn't connected for a long time, the `externalId` is missing |
| `deleted` | The server is deleted (soft-delete). Cannot be restored |

The `blackholeStatus` field describes the agent tunnel state independently of `status`:

| Value | Description |
|----------|----------|
| `NONE` | Right after server creation, before the agent's first connection attempt |
| `WAITING` | The agent is preparing to connect |
| `CONNECTED` | The tunnel is active, the Deploy API is available |
| `DISCONNECTED` | The agent was connected, but the connection is now lost — try [`/repair`](/docs/infra/lifecycle/repair) |

The `kind` field distinguishes the placement model: `STANDALONE` — a separate virtual machine, `GALAXY_APP` — a Galaxy app (a container in a galaxy), `GALAXY` — the galaxy itself (the host carrier; created by the platform, not the user). A Galaxy app's `blackholeStatus` stays `NONE` until the code is uploaded — it does not connect on its own. More details — [Galaxy app](/docs/infra/galaxy).

The `runtimeStatus` field is deprecated, kept for compatibility. For servers created after 2026-04-25 (when the `runtime` parameter was removed from [`POST /v1/infra/servers`](/docs/infra/servers/create)) it always returns `null`. The runtime is now installed at the [`POST /:id/deploy`](/docs/infra/deploy/deploy) stage, and the readiness signal is the success of the `runtime` step itself in the deploy response.

## Plan and access

Vibecode infrastructure has two levels of access conditions.

**Level 1 — Bitrix24 REST API.** Bitrix24 itself opens the REST API only on commercial Bitrix24 account plans. This is not about Vibecode: on free Bitrix24 plans it simply does not return REST responses. So without a commercial Bitrix24 plan the following do not work:

- Creating and publishing applications (`POST /api/apps`) — registered in the Bitrix24 account via REST.
- REST proxy: `/v1/deals`, `/v1/contacts`, `/v1/batch`, `/v1/bots`, `/v1/tasks` and all other entities.
- Bots, chats, tasks — everything proxied into Bitrix24.

**Level 2 — Vibecode infrastructure.** On top of the first condition, creating servers, deploying, and waking require a qualifying Bitrix24 plan — a commercial plan grants full access, a demo plan a limited trial, and a free plan is declined with `INT_TARIFF_REQUIRED` (this is an AI Beta; terms may change at any time):

- `POST /v1/infra/servers` — creating a server.
- `POST /v1/infra/servers/:id/deploy` — deploying the application.
- `POST /v1/infra/servers/:id/wake` and automatic waking when `preventWake=true`.
- Creating agents and managed bots (they provision servers under the hood).

**What works on any Bitrix24 plan, including free:**

- AI Router — `POST /v1/chat/completions`, `POST /v1/audio/transcriptions`, `GET /v1/models`. Does not proxy into Bitrix24, goes directly to LLM providers. With BYOK keys — free; with platform models — billed against the Vibecode balance.
- Basic platform endpoints: `GET /v1/me`, `GET /v1/feedback`, `GET /v1/guide` — for AI-agent self-orientation.

**Check before calling:** `GET /v1/me` → the `capabilities.servers.create.available` field. If `false` — the `capabilities.servers.create.userMessage` field contains a translated explanation for the user.

**Force refresh after a plan upgrade:** `GET /v1/me?refresh=tariff` — skips the cache (1 hour by default) and makes a fresh `app.info` request.

**Access to servers:** the single signal is `capabilities.servers.create` in the `GET /v1/me` response (see above). If access is closed, `POST /v1/infra/servers` returns `402` (or `403` for `REGION_NOT_SUPPORTED`) with the access-gate code (see "Error codes" below). Access is governed by your Bitrix24 plan: a commercial plan grants full access, a demo plan a limited trial, and a free plan is declined with `INT_TARIFF_REQUIRED`.

**Response headers** of the infra endpoints:

| Header | Value |
|-----------|----------|
| `X-Tariff-Checked-At` | ISO timestamp of the last reconciliation via `app.info` (max 1 hour of cache) |
| `X-Tariff-Is-Commercial` | `"true"` or `"false"` |

The access-gate error codes are listed in the "Error codes" section below.

## Error codes

### Infrastructure errors

| Code | HTTP | Description |
|-----|------|----------|
| `NOT_FOUND` | 404 | Server not found or belongs to a different API key |
| `INVALID_REQUEST` | 400 | Validation error (invalid name, plan, region, image) |
| `INFRA_NOT_PERMITTED` | 403 | Infrastructure disabled on the platform or on the portal |
| `SERVER_CREATION_DISABLED` | 403 | Server creation forbidden by portal policy |
| `MAX_SERVERS_REACHED` | 403 | The per-API-key server limit is exceeded |
| `NO_CREDENTIALS` | 404 | The provider is not configured on the platform |
| `SERVER_NOT_READY` | 409 | The server is still being created, the operation is not available yet |
| `CONFLICT` | 409 | The server is in a status from which the action cannot be performed (e.g. `start` on a running one) |
| `PROVIDER_ERROR` | 502 | The cloud provider returned an error |
| `VM_MISSING` | 422 | The record has no `externalId` — the virtual machine was not created at the provider or was deleted externally. Delete the server via `DELETE` and create a new one |
| `PORT_RESTRICTED` | 400 | Port 1–1023 (system ports forbidden). Allowed: `0` (auto-detect) and `1024–65535` |
| `BLACKHOLE_ONLY` | 400 | The endpoint works only for BLACKHOLE servers (applies to `/sleep-now`, `/sleep`, `/metrics`) |
| `OPEN_MODE_NOT_ALLOWED` | 403 | Switching to OPEN is forbidden by the portal policy `allowOpenMode` |
| `SAME_MODE` | 400 | The server is already in the requested mode |
| `NOT_IMPLEMENTED` | 501 | The action is not supported by the provider (e.g. `/reboot` on some plugins) |
| `REPAIR_BLOCKED` | 409 | Repair is blocked (`preventWake=true` or the server is deleted) |

### Deploy API errors

| Code | HTTP | Description |
|-----|------|----------|
| `SERVER_NOT_READY` | 409 | The server is not ready for the operation: it is not running, the tunnel is not connected, or the server is listed as connected but the Gateway has no live tunnel. The response carries a `hint` field with the reason and the next step. The platform attempts to restore the tunnel itself. If that does not succeed — wake the server or call [`/repair`](/docs/infra/lifecycle/repair) and repeat the request |
| `EXEC_BUSY` | 409 | Another operation is already running on the server. Use [`/lock`](/docs/infra/deploy/lock) to release a stuck lock |
| `COMMAND_TOO_LONG` | 400 | The `/exec` command is longer than 10,000 characters. Ship large payloads and scripts via [`/upload`](/docs/infra/deploy/upload) |
| `EXEC_TIMEOUT` | 200 | Execution timeout exceeded. The failure arrives in the response body |
| `EXEC_FAILED` | 200 | Command execution error on the agent. The failure arrives in the response body |
| `UPLOAD_PATH_DENIED` | 403 | Forbidden upload path |
| `DEPLOY_FAILED` | 200 | One of the deploy steps failed — `error.step` names which one. The failure arrives in the response body |
| `DEPLOY_TIMEOUT` | 200 | A deploy step did not finish within its time limit |
| `DEPLOY_CONNECTION_TERMINATED` | 200 | The connection to the server dropped mid-deploy. The deploy may be partially applied — check the failing step and retry |
| `DEPLOY_TUNNEL_STALE` | 200 | The Gateway has no live tunnel for the server. Call [`/repair`](/docs/infra/lifecycle/repair) and retry the deploy |
| `VALIDATION_ERROR` | 400 | Malformed Deploy API request body |

For [`/exec`](/docs/infra/deploy/exec) and [`/deploy`](/docs/infra/deploy/deploy) on a separate virtual machine (`kind: "STANDALONE"`), the connection is held open for the whole run, so the `200` status is sent before the work starts. A failure during execution arrives in the response body — the marker is `success: false`, not the HTTP status. Check `success`, otherwise a failed command will be mistaken for a successful one. For a Galaxy app (`kind: "GALAXY_APP"`) the same error arrives with the status `502` — the `200` HTTP statuses in the table above apply to a separate virtual machine.

In streaming mode (`?stream=true`) a failure arrives as an SSE `error` event with `code` and `message` — this is how `/exec` and a deploy report a failure on an exception or a transport drop. For a deploy, a failure of an individual **step** arrives differently — as a `step` event with `status: "error"` and the step name. There is no `success` field in the stream.

### Access-gate and billing errors

| Code | HTTP | Description |
|-----|------|----------|
| `INT_TARIFF_REQUIRED` | 402 | The portal is on a free Bitrix24 plan — creating servers, deploying, and waking require a commercial plan (a demo plan grants a limited trial) |
| `REGION_NOT_SUPPORTED` | 403 | Infrastructure access is not yet available in the portal's region |
| `COMMERCIAL_PLAN_REQUIRED` | 402 | The Bitrix24 plan is free and does not grant access — a commercial plan is required |
| `TRIAL_PORTAL_LIMIT` | 402 | The per-portal server limit for the trial period is exceeded (1 server per portal) |
| `PLAN_NOT_ALLOWED_ON_TRIAL` | 402 | The requested plan is not available during the trial period. `error.details.allowedPlans` lists the plans the trial gate is configured for; `error.details.requestedPlan`, when present, echoes the rejected plan |
| `ACCOUNT_FROZEN` | 402 | The Vibecode balance is frozen. A top-up is required |
| `BILLING_EXHAUSTED` | 402 | The Vibecode balance is exhausted. Waking and deployment are blocked |
| `SERVER_WAKE_BLOCKED` | 403 | Waking is blocked (not due to billing: administrative block, security) |

### System errors

| Code | HTTP | Description |
|-----|------|----------|
| `MISSING_API_KEY` | 401 | The `X-Api-Key` header was not provided |
| `INVALID_API_KEY` | 401 | Invalid or expired API key |
| `SCOPE_DENIED` | 403 | The key does not have the `vibe:infra` scope |
| `RATE_LIMITED` | 429 | The request rate limit was exceeded. The response carries the `Retry-After` header |
| `INTERNAL_ERROR` | 500 | Internal server error |

The full reference of common errors — [Errors](/docs/errors).

## App icon

The app icon (SVG) appears in the Bitrix24 catalog and as the browser-tab favicon. The format, requirements and order (favicon `<link>` before deploy, `POST /v1/infra/servers/:id/icon` upload after) live on a dedicated page — [App icon](/docs/infra/app-icon).

## Recipes

- [Load a database dump onto a server](/docs/recipes/db-dump-restore) — upload a dump and restore the database in the background via `exec`.
- [A fast release cycle](/docs/infra/deploy/fast-cycle) — shorten the edit cycle without running every step.

## See also

- [API overview](/docs/entity-api) — overall platform architecture.
- [Limits and optimization](/docs/optimization) — rate limits, auto-pagination, batch requests.
- [Keys and authorization (`/v1/me`)](/docs/keys-auth) — plan and capability check.
- [What arrives at the application](/docs/infra/app-runtime) — Gateway injection of `X-Vibe-Authorization`, BFF pattern, handler skeletons.
