Para agentes de IA: markdown desta página — /docs-content-en/infra.md índice da documentação — /llms.txt

Os artigos da documentação estão disponíveis atualmente em inglês.

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 · Base URL: https://vibecode.bitrix24.com/v1 · Authorization: X-Api-Key header

Documentation sections

  • Providers and catalogs — list of providers, plans, regions, and OS images (4 endpoints).
  • Servers — create, list, get details, update name and description, delete (5 endpoints).
  • Lifecycle — start, stop, sleep, wake, tunnel repair, provisioning status (9 endpoints).
  • Scheduled wake — recurring wake windows for a sleeping server on a cron schedule (4 endpoints).
  • Access and modes — access policy, user/department list, SSH credentials, BLACKHOLE↔OPEN mode (7 endpoints).
  • Deploy API — run commands, upload files, logs, deploy the application, outcome and operation list, port, metrics, lock, runtimes (10 endpoints).
  • Access tokens — short-lived tokens for e2e checks and shareable links (4 endpoints; the section is enabled on the platform side).
  • What the app receives — Gateway injection of X-Vibe-Authorization: Bearer, reading identity via /v1/me, handler skeletons in Node/Python/Go.
  • Portal event subscriptions — delivery of Bitrix24 events (ONTASKADD and similar) to the app through the tunnel, without polling (3 endpoints).
  • Activity and automation rule callback delivery — a business process activity or automation rule handler on a Black Hole subdomain: queued calls, sleeping server wake-up, registration with a single request.
  • Galaxy app — 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 — 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 the CONNECTED status. 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 a security setting. 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.
  6. The Bitrix24 plan plays a dual role. First, the Bitrix24 REST API itself is available only on commercial Bitrix24 account plans — without one, none of the following work: applications, the /v1/deals proxy, bots, or any other call proxied into Bitrix24. Second, on top of that, creating servers, deploying, and waking require a qualifying Bitrix24 plan: a commercial plan grants full access, a trial plan grants limited access, and on a free plan the request is declined with INT_TARIFF_REQUIRED. On .com full platform access is sold as a plan of the Vibe+ line, and the refusal text names it — read details.requiredTariffs for the plans that clear the refusal today. 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 the app receives.
  8. A new machine falls asleep after 60 minutes of inactivity. A separate virtual machine is created with a 60-minute idle timeout. Idleness is measured by inbound requests to the application — requests to its HTTPS subdomain. Requests the application sends outbound do not reset the timer, so an application built around continuously polling an external API stops together with its machine after an hour. The allowed timeout values, and how to disable auto-sleep with nullConfigure auto-sleep.
  9. 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

Terminal
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. Separate virtual machine — wait until ready:
#    status=running AND blackholeStatus=CONNECTED.
#    A Galaxy app (kind=GALAXY_APP in the step 1 response) never reaches
#    that state — go straight to step 3, see the note below.
curl -H "X-Api-Key: $VIBE_KEY" \
  https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID

# 3. Deploy the application (JSON is the default).
#    The example below is for a separate virtual machine: the code comes
#    from an external URL. A Galaxy app accepts an inline archive only —
#    "source": { "content": "<base64>" }, see the note above.
#    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" \
  -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 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.

curl — OAuth application

Terminal
# 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" }'

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. In the create response its kind is GALAXY_APP and createdVia is galaxy. Such an app never reaches blackholeStatus: "CONNECTED": its container is built by uploading the code, so step 2 does not apply to it and the code is uploaded right after creation. The code source there is an inline base64 archive — the source.content field; source.url is available where the platform has enabled link deploys for you (otherwise 400 GALAXY_DEPLOY_CONTENT_ONLY) and points only at the platform source storage (otherwise 400 GALAXY_SOURCE_URL_NOT_ALLOWED), while source.versionId is not accepted at create — deploy a saved version as a second step, via POST /v1/infra/servers/:id/deploy. The runtime and start fields are required. If you need a separate virtual machine, pass the placement field set to dedicated in the create body. The full model, lifecycle, cost, and deploy differences — Galaxy app.

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. Separate virtual machine: wait for running and CONNECTED.
//    A Galaxy app (kind === 'GALAXY_APP') never reaches that state —
//    the step is skipped for it, the code is uploaded right away.
let info = server
if (server.kind === 'STANDALONE') {
  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 (JSON is the default).
//    The code source below is an external URL — that is the variant for a
//    separate virtual machine. For a Galaxy app (kind === 'GALAXY_APP') the
//    only source is inline: source: { content: '<base64>' }.
//    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`, {
  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. Change the idle timeout if the default 60 minutes does not fit.
//    Allowed values are 15, 30, 60, 240 and null, which disables
//    auto-sleep. An application that polls continuously needs null:
//    outbound requests do not reset the idle timer.
await api('PATCH', `/infra/servers/${server.id}/sleep`, { sleepAfterMinutes: 240 })

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 List of cloud providers
GET /v1/infra/providers/:providerId/plans Provider plans
GET /v1/infra/providers/:providerId/regions Provider regions
GET /v1/infra/providers/:providerId/images OS images

Servers:

Method Path Description
POST /v1/infra/servers Create a server (always Black Hole)
GET /v1/infra/servers List of your servers
GET /v1/infra/servers/:id Server details
PATCH /v1/infra/servers/:id Update the name and description
DELETE /v1/infra/servers/:id Delete a server

Lifecycle:

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

Scheduled wake:

Method Path Description
GET /v1/infra/servers/:id/wake-schedules List wake windows and their firing history
POST /v1/infra/servers/:id/wake-schedules Create a wake window
PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId Update a wake window
DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId Delete a wake window

Access and modes:

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

Deploy API:

Method Path Description
POST /v1/infra/servers/:id/exec Run a command (SSE or JSON)
POST /v1/infra/servers/:id/upload Upload a file (base64 or by URL)
GET /v1/infra/servers/:id/logs Service logs (journalctl utility)
POST /v1/infra/servers/:id/deploy Full application deploy
GET /v1/infra/operations/:operationId Deploy outcome by operation ID
GET /v1/infra/servers/:id/operations Recent deploy operations started by the current key
PATCH /v1/infra/servers/:id/port Set the application port
GET /v1/infra/servers/:id/metrics Tunnel activity metrics
DELETE /v1/infra/servers/:id/lock Release a stuck operation lock
GET /v1/infra/runtimes List of available runtimes

Access tokens:

Method Path Description
POST /v1/infra/servers/:id/access-tokens Issue an access token (api-bearer or share-url)
POST /v1/infra/servers/:id/access-tokens/:tokenId/refresh Issue a fresh JWT for the same api-bearer token
GET /v1/infra/servers/:id/access-tokens List of server tokens
DELETE /v1/infra/servers/:id/access-tokens/:tokenId Revoke a token

Portal event subscriptions:

Method Path Description
POST /v1/infra/servers/:id/event-subscriptions Subscribe the server to a portal event (event.bind under the OAuth app)
GET /v1/infra/servers/:id/event-subscriptions List subscriptions + recent deliveries
DELETE /v1/infra/servers/:id/event-subscriptions/:subId 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, /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 .../wake-schedules/:scheduleId yes no Requires scheduled wake to be enabled for the Bitrix24 account; otherwise 403 WAKE_SCHEDULE_DISABLED. If the feature is enabled for the account and the server is a Galaxy app, the response is 403 WAKE_SCHEDULE_GALAXY_DISABLED: for those apps the feature is enabled separately from standalone servers.
DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId yes no Deleting a window is not restricted by those conditions — a window can be removed even after scheduled wake has been disabled for the Bitrix24 account or for Galaxy apps.
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/GET /v1/infra/servers/:id/access-tokens, DELETE .../:tokenId, POST .../:tokenId/refresh yes no The access-tokens section must be enabled by the platform; otherwise all four endpoints return 503 FEATURE_DISABLED. The pre-call check is data.capabilities.servers.preview in GET /v1/me.
POST/GET/DELETE /v1/infra/servers/:id/event-subscriptions yes no The server must be bound to 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/medata.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 100. Only separate virtual machines created with this key are counted — Galaxy apps and the galaxy host machines themselves are outside the limit. To see your current limit and how much of it is used, call GET /v1/me: data.infra.limits.max and data.infra.limits.used
Deploy API operations per minute per server 10
Concurrent exec/deploy per server 1
exec timeout 1–600 seconds (default 300)
Body size with inline base64 (upload content, deploy source.content, source on server creation) 96 MB of body, about 72 MB of archive. Over the cap — 413 INLINE_SOURCE_TOO_LARGE
File size via source.url / upload url 500 MB
Archive size of a saved version (source.versionId) 500 MB
Multipart archive size in deploy 500 MB as long as the archive streams into storage. Otherwise about 72 MB — conditions
/ssh request rate up to 10 per minute

The platform rate limit is shared across all V1 endpoints — see the "Limits and optimization" section.

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 on a /deploy//start//wake call, and a request to the HTTPS subdomain wakes it under the automatic wake conditions
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

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 machine that carries them, created by the platform rather than by 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.

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) it always returns null. The runtime is now installed at the POST /:id/deploy stage, and the readiness signal is the success of the runtime step itself in the deploy response.

Plan and access

Access to Vibecode infrastructure is gated at two levels.

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

  • Creating and publishing applications (POST /api/apps) — the app is 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 trial plan grants limited access, and on a free plan the request is declined with INT_TARIFF_REQUIRED (this is an AI Beta; terms may change at any time). On .com that refusal names a plan of the Vibe+ line — full platform access is sold as a Vibe+ plan. For accounts where access is narrowed to the Vibe+ plan line, infrastructure and key issuance require a Vibe+ plan, and an ordinary commercial plan is declined with INT_VIBE_PLUS_REQUIRED. The operations gated at this level:

  • 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. BYOK keys are free; platform models are billed against the Vibecode balance.
  • Basic platform endpoints: GET /v1/me, GET /v1/feedback, GET /v1/guide — so an AI agent can orient itself.

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

Force refresh after a plan upgrade: GET /v1/me?refresh=tariff — skips the cache (one hour by default) and re-requests the plan from Bitrix24.

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

Response headers of the infra endpoints:

Header Value
X-Tariff-Checked-At ISO timestamp of the last SUCCESSFUL plan reconciliation with Bitrix24, cached for up to 1 hour. A failed attempt sends no header: its absence means "no reliable reconciliation"
X-Tariff-Is-Commercial "true" or "false"

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

Windows / PowerShell and UTF-8

Non-ASCII characters (accented letters such as ö, ü, é, or characters from any non-Latin script) in the server displayName and description may turn into question marks (?) if the request is sent from Windows PowerShell without explicit UTF-8 serialization. This is not a display problem on the platform side — the bytes are lost on the client side, before the HTTP request is even sent.

Cause. By default, PowerShell re-encodes the string from the -Body parameter of Invoke-WebRequest and Invoke-RestMethod into the system's legacy (non-UTF-8) code page — for example windows-1252 on Western installs — and any character outside that page is lost before the request is assembled. The Content-Type: charset=utf-8 header does not help here — by the time it applies, the original bytes are already lost.

Solution. Pass the request body as a UTF-8 byte array.

powershell
# 1. Console output encoding — does not affect how the body is encoded
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# 2. Build the JSON and convert it to a UTF-8 byte array
$body = @{
  displayName = 'Kundenübersicht'
  description = 'The bot sends notifications about deals'
} | ConvertTo-Json -Compress

$bytes = [System.Text.Encoding]::UTF8.GetBytes($body)

# 3. Pass a byte array to -Body, not a string, and set the encoding in Content-Type
Invoke-WebRequest `
  -Uri 'https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID' `
  -Method PATCH `
  -Headers @{
    'X-Api-Key'    = 'YOUR_API_KEY'
    'Content-Type' = 'application/json; charset=utf-8'
  } `
  -Body $bytes

Common mistakes:

  • Saving the .ps1 with a UTF-8 BOM — older PowerShell versions may fail to parse the script itself.
  • Passing the string $body to -Body instead of the byte array $bytes — the string is re-encoded through the system code page.
  • Relying only on Content-Type: application/json; charset=utf-8 without UTF8.GetBytes — this header does not restore the lost bytes; it only declares the body encoding to the server.

The same serialization is needed everywhere you pass a display name and a description: creating a server, updating the name and description and deploying an application — from there these values go into the application card in the Bitrix24 catalog.

Node.js (fetch) and Python (requests) encode the body in UTF-8 themselves; no extra steps are required. The problem is specific to PowerShell.

Error codes

Infrastructure errors

Code HTTP Description
NOT_FOUND 404 Server not found, or it belongs to another API key while you are not on its development team
SERVER_ROLE_FORBIDDEN 403 You are on this server's development team, but the operation is wider than your role. error.hint carries yourRole, requiredRole, the denied action and the list of calls open to you in allowedHere. Role breakdown — List servers
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 Tunnel 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 and repeat the request
EXEC_BUSY 409 Another operation is already running on the server. Use /lock to release a stuck lock. In a galaxy this releases the platform lock only: if the server stays busy, the host's shared exec channel is occupied — retry at the Retry-After interval, and contact support if the refusals keep coming
COMMAND_TOO_LONG 400 The /exec command is longer than 10,000 characters. Send large payloads and scripts via /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
EXEC_NO_EXIT 200 The /exec stream ended without sending an exit status: the command's outcome on the server is unknown. The failure arrives in the response body, the output collected so far in data. Standalone virtual machines (kind: "STANDALONE") only
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 The Gateway stopped waiting for a deploy step, so the operation outcome is unknown. Read the operation list and perform the read-only checks from error.hint first; do not repeat the deploy blindly
DEPLOY_CONNECTION_TERMINATED 200 The connection to the server dropped mid-deploy, so the operation outcome is unknown. Read the operation list and perform the read-only checks from error.hint first; do not repeat the deploy blindly
DEPLOY_TUNNEL_STALE 200 The Gateway lost its live tunnel during the deploy, so the operation outcome is unknown. Read the operation list and perform the read-only checks from error.hint first; do not call /repair or repeat the deploy blindly
VALIDATION_ERROR 400 Malformed Deploy API request body

For /exec and /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. The exception is EXEC_BUSY: a busy shared host exec channel arrives as 409 with a Retry-After header, because it means "busy, retry" rather than a failure.

In streaming mode (?stream=true) a failure arrives as an SSE error event with code and message — this is how both /exec and /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
INFRA_SCOPE_REQUIRED 403 The key does not have the vibe:infra scope. Returned when creating a server and on wake schedules
INFRA_FORBIDDEN_FOR_COWORK_KEY 403 The call was made with a Cowork/Code key — it is data-plane only and does not reach the control plane. It arrives on EVERY method of this section except reads (GET). error.details.requiredAction carries the exact fix, and error.details.deployableKeys lists the owner's ordinary keys that can deploy, up to the five most recent. The special-purpose project key never appears there, so an empty list does not mean no suitable key can be issued. To issue a suitable one, see Project key for deploy
WRITE_BLOCKED_READONLY_KEY 403 The key is in read-only mode. This gate is on server CREATION ONLY: the same key still deploys, runs commands, and drives lifecycle operations on a server it already owns. The mode is switched on the API Keys page
INT_TARIFF_REQUIRED 402 The portal is on a free Bitrix24 plan — creating servers, deploying, and waking require a commercial plan (a trial plan grants limited access)
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
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.

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.

Recipes

See also