For AI agents: markdown of this page — /docs-content-en/infra/servers.md documentation index — /llms.txt

Servers

CRUD operations for servers: create a new server, list your servers, fetch details of a single server by ID, update its name and description, and delete a server. A created server is always in Black Hole mode — invisible from the internet, with access to the application only via an HTTPS subdomain.

Scope: vibe:infra

Create a server

POST /v1/infra/servers

Creates an application with a cloud provider. Always in Black Hole mode — iptables blocks all inbound ports, the application is invisible from the internet.

Your Bitrix24 account has two placement models, and what to expect after creation depends on the model:

  • A dedicated virtual machine (the default model). The response is returned immediately with the provisioning status. Provisioning takes 1–3 minutes, after which you need to poll GET /v1/infra/servers/:id until status: "running" and blackholeStatus: "CONNECTED". The response contains the SSH data ssh.password and ssh.privateKey only once — they will be needed when switching to OPEN mode, so save them right away. This page describes that model in the request fields, response fields, and error table below.
  • A Galaxy application — a container on a shared host. If your Bitrix24 account places apps in galaxies, the same POST /v1/infra/servers creates a Galaxy application instead of a virtual machine. In the response, this model is marked by createdVia: "galaxy". The "Galaxy application" section below describes the two startup scenarios, and the full model is on the Galaxy app page.

Galaxy application

If your Bitrix24 account places apps in galaxies, POST /v1/infra/servers creates a Galaxy application — a container on a shared host rather than a dedicated virtual machine. The request and the call order are the same, but the lifecycle differs. In the response, this model is marked by createdVia: "galaxy". The full model, cost, and deploy differences are on the Galaxy app page.

Do not wait for CONNECTED before uploading the code. A Galaxy application never reaches blackholeStatus: "CONNECTED" on its own — its container is built on code upload. If you just poll the status, the app stays in provisioning, and after about 20 minutes the platform marks it as error and records in the provisionError field that the code was never uploaded. So upload the code right away — using one of the two scenarios below.

A single request. Pass the application code in the source field of POST /v1/infra/servers together with runtime and start (and, if needed, install, env, port). The build runs in the background. After the response, poll GET /v1/infra/servers/:id until status: "running". A separate code-upload call is not needed.

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-crm-app",
    "displayName": "My CRM bot",
    "source": { "content": "<base64-archive>" },
    "runtime": "node20",
    "start": "node index.js",
    "install": "npm ci",
    "port": 3000
  }'

The source, runtime, start, install, env, port fields have the same meaning as in the POST /:id/deploy body:

Field Type Required Description
source object yes (for this scenario) The code source. source.content — the application archive in base64, up to 500 MB per request body
runtime string yes (if source is passed) Runtime ID: node20, python311, php83, static, and others. The list — GET /v1/infra/runtimes
start string yes (if source is passed) The application start command, on a single line. A line break will return 400
install string no The dependency-install command, on a single line
env object no Environment variables { "KEY": "value" }. Injected into the container at start, see below
port number no The port the application listens on

Without the runtime and start fields, the source field returns 400 — both fields are required. On a Bitrix24 account without Galaxy mode, source is forbidden and returns 400 SOURCE_AT_CREATE_GALAXY_ONLY.

Scenario 2 — two-step

Create the application without source — the response comes with the provisioning status and the hint next: "deploy". Immediately call POST /v1/infra/servers/:id/deploy with the code in source, the runtime, and the start command. Upload the code right away, without waiting for CONNECTED.

Terminal
# Step 1 — create the application without code
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "bitrix-cloud", "name": "my-crm-app", "plan": "bc-medium", "region": "bc-eu-central" }'

# The response contains data.id, data.next = "deploy", and data.hint.
# Step 2 — upload the code right away, without waiting for CONNECTED
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/deploy \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": { "content": "<base64-archive>" },
    "runtime": "node20",
    "start": "node index.js",
    "port": 3000
  }'

provider, plan and region are required by the schema for a create without source; for a galaxy app the values are informational — the app inherits its host's plan and region.

Diagnostics and environment variables

  • The provisionError field. If the build or the startup failed, GET /v1/infra/servers/:id returns the failure reason in data.provisionError, and on an image-build failure — also the tail of the Docker build log. Read this field to find the cause.
  • env variables are injected at start. Values from env are passed into the container at start time via docker run --env, not baked into the image. So API keys and secrets from env do not end up in the image layers.

Request fields (body)

Field Type Required Description
provider string yes Provider ID from GET /v1/infra/providers, for example bitrix-cloud. 1–50 characters
name string yes System name of the server — the technical identifier. 2–63 characters, only lowercase Latin letters, digits, and -, with a letter as the first character. Pattern: ^[a-z][a-z0-9-]*$. Used in the subdomain URL (app-<hex> is generated separately), logs, the audit log. Immutable after creation. For a human-readable name, use displayName
displayName string no Human-readable name in any language (Russian, Chinese, emoji — anything). 2–100 characters, no control bytes. Displayed in the UI, server-down notifications, security alerts, billing lines, the Bitrix24 catalog. If not passed — name is substituted. The name itself remains the technical identifier, immutable, and is used in the subdomain URL and logs
description string no Application description shown on the Bitrix24 catalog card, up to 500 characters. Line breaks and tabs are allowed, other control bytes are rejected. The value is trimmed at both ends. If not passed — the description stays empty, and it can be set later via PATCH /v1/infra/servers/:id
plan string yes Plan ID from GET /v1/infra/providers/:providerId/plans, for example bc-small
region string yes Region ID from GET /v1/infra/providers/:providerId/regions, for example bc-eu-central
image string no OS image ID from GET /v1/infra/providers/:providerId/images. If omitted — the platform resolves the provider's current image itself. If you pass it, take the current one from the response: the ID changes when the build is updated
sshPublicKey string no Your SSH public key (ssh-rsa …, ssh-ed25519 …, ecdsa-sha2-nistp256/384/521 …, security keys). Up to 8192 characters. If not passed — the platform will generate a private key and return it once in ssh.privateKey
placement string no Placement model: auto (default) or dedicated. On a Bitrix24 account in Galaxy First mode (galaxies-only), the value dedicated creates a standalone virtual machine instead of a Galaxy application — "graduating" the application onto its own server. It passes the same checks as a normal server create: the serverCreation policy and the per-user server quota. With auto the behavior is unchanged (in Galaxy First mode a Galaxy application is created)
graduateFrom string no The identifier of your Galaxy application (kind=GALAXY_APP) to delete after the dedicated server is created (graduation cleanup — so the old application does not linger in the galaxy). Owner-scoped: the same key, the same Bitrix24 account, kind=GALAXY_APP. A foreign or non-Galaxy identifier returns 404 and deletes nothing. Meaningful only together with placement: dedicated. For a dedicated server, the Idempotency-Key header is not compatible with this parameter — see Idempotency

Note: for the dedicated-virtual-machine model the runtime parameter is not accepted in POST /v1/infra/servers — passing it will return 400 RUNTIME_PARAM_REMOVED. The runtime is installed at the deploy stage. See POST /:id/deploy. Exception — Galaxy mode: when source is passed, runtime is required (see the "Galaxy application" section), and 400 RUNTIME_PARAM_REMOVED is not returned in that case.

Examples

curl — personal key

Terminal
# name — lowercase Latin letters, digits and hyphens only, first character a letter (^[a-z][a-z0-9-]*$)
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "bitrix-cloud",
    "name": "my-crm-app",
    "displayName": "My CRM bot",
    "plan": "bc-small",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts"
  }'

curl — OAuth application

Terminal
# name — lowercase Latin letters, digits and hyphens only, first character a letter (^[a-z][a-z0-9-]*$)
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-crm-app",
    "displayName": "My CRM bot",
    "plan": "bc-small",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts"
  }'

JavaScript — personal key

javascript
// name — lowercase Latin letters, digits and hyphens only, first character a letter (^[a-z][a-z0-9-]*$)
const res = await fetch('https://vibecode.bitrix24.com/v1/infra/servers', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    provider: 'bitrix-cloud',
    name: 'my-crm-app',
    displayName: 'My CRM bot',
    plan: 'bc-small',
    region: 'bc-eu-central',
    image: 'ubuntu-2404-lts',
  }),
})
const { data } = await res.json()
console.log('Server ID:', data.id, 'Subdomain:', data.subdomain)

// Save the SSH credentials just in case — they are returned only once
if (data.ssh.privateKey) {
  await saveLocally(`${data.id}.key`, data.ssh.privateKey)
}

JavaScript — OAuth application

javascript
// name — lowercase Latin letters, digits and hyphens only, first character a letter (^[a-z][a-z0-9-]*$)
const res = await fetch('https://vibecode.bitrix24.com/v1/infra/servers', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    provider: 'bitrix-cloud',
    name: 'my-crm-app',
    displayName: 'My CRM bot',
    plan: 'bc-small',
    region: 'bc-eu-central',
    image: 'ubuntu-2404-lts',
  }),
})

Response fields

Field Type Description
success boolean Always true on success
data.id string (UUID) Unique server identifier
data.status string Always "provisioning" right after creation. Wait for "running" in 1–3 minutes
data.provider string Echo of the passed provider
data.name string Echo of the passed name — the system name of the server
data.kind string Server type, always: STANDALONE or GALAXY_APP
data.galaxyId string Only for the galaxy branch (the galaxy host id); absent from a standalone response
data.displayName string | null Human-readable name. If displayName was not passed at creation — it will equal name
data.description string | null Echo of the passed description. The key is present only in the response for a dedicated virtual machine. For a Galaxy application, where createdVia equals galaxy, the key is absent from the response even though the description is stored — read it via GET /v1/infra/servers/:id
data.ip string | null Public IP. null right after creation, filled in when the virtual machine starts
data.ssh.user string User for SSH: root for new servers
data.ssh.port number SSH port: 22
data.ssh.password string | null One-time! The root password. Save it right away — it will not be returned later. Needed when switching to OPEN mode
data.ssh.privateKey string | null One-time! Private key in OpenSSH format (if sshPublicKey was not passed at creation). Save it right away
data.plan string Echo of the passed plan. Exception — a Galaxy application (createdVia: "galaxy"): the host's plan is returned, not the one requested
data.region string The region the server actually landed in. May differ from the requested one when a zone fallback triggers (see "Known specifics"). For a Galaxy application — the host's region
data.image string Echo of the passed image
data.mode string Always "BLACKHOLE" right after creation
data.createdVia string "api" for calls via the v1 API, "ui" for calls from the Vibecode dashboard
data.subdomain string Subdomain for the application, for example app-92fb1c34. Used in appUrl
data.blackholeStatus string State of the agent tunnel. Right after creation — "NONE", then it passes through WAITING and ends at CONNECTED
data.accessPolicy string Access policy for the application. By default "OWNER_ONLY" — only the key owner
data.runtimeId string | null Always null at creation. Filled in on deploy with runtime
data.runtimeStatus string | null Always null at creation. Updated during deploy
data.appUrl string | null HTTPS address of the application: https://{subdomain}.vibecode.bitrix24.com
data.createdAt string (ISO 8601) Creation timestamp

Response example

A dedicated virtual machine:

JSON
{
  "success": true,
  "data": {
    "id": "db008c84-91a5-4e15-b9d5-6c6aa2838448",
    "status": "provisioning",
    "provider": "bitrix-cloud",
    "name": "docs-test-temp",
    "kind": "STANDALONE",
    "ip": null,
    "ssh": {
      "user": "root",
      "port": 22,
      "password": "0FPIIR9EfO2OAeSSMK6bKA",
      "privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmU…\n-----END OPENSSH PRIVATE KEY-----\n"
    },
    "plan": "bc-small",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts",
    "mode": "BLACKHOLE",
    "createdVia": "api",
    "subdomain": "app-92fb1c34",
    "blackholeStatus": "NONE",
    "accessPolicy": "OWNER_ONLY",
    "runtimeId": null,
    "runtimeStatus": null,
    "appUrl": "https://app-92fb1c34.vibecode.bitrix24.com",
    "createdAt": "2026-04-22T10:50:11.477Z"
  }
}

A Galaxy application via scenario 1 — source was passed, the build runs in the background. There are no ssh or next fields:

JSON
{
  "success": true,
  "data": {
    "id": "7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33",
    "status": "provisioning",
    "provider": "bitrix-cloud",
    "name": "my-crm-app",
    "kind": "GALAXY_APP",
    "galaxyId": "<galaxy-host-id>",
    "displayName": "My CRM bot",
    "ip": null,
    "ssh": null,
    "plan": "bc-medium",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts",
    "mode": "BLACKHOLE",
    "createdVia": "galaxy",
    "subdomain": "app-7c2b1f08",
    "blackholeStatus": "NONE",
    "accessPolicy": "OWNER_ONLY",
    "runtimeId": null,
    "runtimeStatus": null,
    "appUrl": "https://app-7c2b1f08.vibecode.bitrix24.com",
    "createdAt": "2026-04-22T10:50:11.477Z"
  }
}

A Galaxy application via scenario 2 — source was not passed, next and hint appear:

JSON
{
  "success": true,
  "data": {
    "id": "7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33",
    "status": "provisioning",
    "provider": "bitrix-cloud",
    "name": "my-crm-app",
    "kind": "GALAXY_APP",
    "galaxyId": "<galaxy-host-id>",
    "displayName": "My CRM bot",
    "ip": null,
    "ssh": null,
    "plan": "bc-medium",
    "region": "bc-eu-central",
    "image": "ubuntu-2404-lts",
    "mode": "BLACKHOLE",
    "createdVia": "galaxy",
    "subdomain": "app-7c2b1f08",
    "blackholeStatus": "NONE",
    "accessPolicy": "OWNER_ONLY",
    "runtimeId": null,
    "runtimeStatus": null,
    "appUrl": "https://app-7c2b1f08.vibecode.bitrix24.com",
    "createdAt": "2026-04-22T10:50:11.477Z",
    "next": "deploy",
    "hint": "This is a galaxy app — POST /v1/infra/servers/:id/deploy with source.content now; it will not reach \"running\" on its own."
  }
}

If a Galaxy application's build failed, GET /v1/infra/servers/:id returns the reason in data.provisionError:

JSON
{
  "success": true,
  "data": {
    "id": "7c2b1f08-3a4d-4e91-9b6c-2f5e8a1d0c33",
    "status": "error",
    "createdVia": "galaxy",
    "provisionError": "Docker build failed: npm ci exited with code 1"
  }
}

Error response example

400 — validation failed (the name starts with an uppercase letter):

JSON
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "name: Name must start with a letter and contain only lowercase letters, digits, and hyphens"
  }
}

Errors

HTTP Code Description
400 INVALID_REQUEST Field validation failed (invalid name format, a required field is missing, an invalid SSH key, etc.). The message field contains the specific reason. On a portal that places applications in a galaxy, when the body carries neither source nor the full provider + plan + region tuple, an error.hint object is added (see below)
400 SOURCE_AT_CREATE_GALAXY_ONLY The source field was passed on a portal without Galaxy mode. Upload the code via POST /:id/deploy after creation
400 RUNTIME_PARAM_REMOVED The runtime parameter was passed without source. Specify the runtime in POST /:id/deploy, or pass runtime, start and source together in this same request. On a portal that places applications in a galaxy, an error.hint object is added (see below)
400 UNKNOWN_PARAM The request body has an unknown field (e.g. deployMode instead of placement). details.unknownFields lists the extra fields, details.suggestions proposes the correct name, and details.validParams is the full list of accepted fields
401 MISSING_API_KEY The X-Api-Key header was not passed
401 INVALID_API_KEY Invalid or expired API key
402 COMMERCIAL_PLAN_REQUIRED The Bitrix24 plan is free and the trial period is unavailable (already used). See the Plan and access section
402 TRIAL_EXPIRED The trial was used and has ended
402 TRIAL_PORTAL_LIMIT The portal's server quota on trial was exceeded
402 TRIAL_USER_LIMIT The per-user server quota on trial was exceeded
402 PLAN_NOT_ALLOWED_ON_TRIAL 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
402 ACCOUNT_FROZEN The Vibecode balance is frozen, a top-up is required
403 INFRA_NOT_PERMITTED Infrastructure is disabled on the platform or on the portal
403 SERVER_CREATION_DISABLED Server creation is forbidden by portal policy
403 MAX_SERVERS_REACHED The per-API-key server limit was exceeded. Delete unneeded ones via DELETE or create a new API key
403 WRITE_BLOCKED_READONLY_KEY The key is in read-only mode, but creating a server is a write operation. Switch the key to read and write — see Access mode
403 INFRA_SCOPE_REQUIRED The key lacks the vibe:infra scope — infrastructure management is unavailable. Add the scope or use a key with infrastructure permissions
403 INFRA_FORBIDDEN_FOR_COWORK_KEY The call was made with a Co-work key. Such a key works with data only and does not manage servers. error.details.requiredAction carries the steps for obtaining a key with deploy rights
404 NO_CREDENTIALS The provider is not configured on the platform
502 PROVIDER_ERROR The cloud provider returned an error while creating the virtual machine. The server record is marked as deleted, the quota is not consumed — you can retry immediately. The message field contains the text from the provider
429 RATE_LIMITED The platform's overall request limit was exceeded
503 POOL_EXHAUSTED The service is temporarily overloaded — the database connection pool is exhausted. The response carries retryAfter and a Retry-After header, retry after a few seconds

The full list of common API errors — Errors.

The `error.hint` object

On a Bitrix24 account that places applications in a galaxy, the INVALID_REQUEST and RUNTIME_PARAM_REMOVED errors are extended with an error.hint object. It names the reason for the refusal and gives a ready-made request body. The condition differs between the two codes. For INVALID_REQUEST the hint arrives when the body carries neither source nor the full provider + plan + region tuple. For RUNTIME_PARAM_REMOVED it arrives when runtime is passed without source, even if the tuple is filled in completely. On a Bitrix24 account with dedicated virtual machines there is no hint in either case.

Do not confuse it with data.hint from a successful response: that one is a string about the next step after creating an application via scenario 2, this one is an object inside error.

Field Type Description
error.hint.reason string Why the request was refused
error.hint.recovery string What to change in the request so it succeeds
error.hint.example object A ready-made request body to start from
JSON
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "provider: Required; plan: Required; region: Required",
    "hint": {
      "reason": "This portal places new apps on shared galaxy hosts, and the request lacked `source` and the full provider/plan/region tuple.",
      "recovery": "RECOMMENDED: create-and-deploy in ONE call — POST /v1/infra/servers { name, source: { content }, runtime, start }; OMIT provider/plan/region. Two-step also works: pass provider/plan/region (informational for a galaxy — values from GET /v1/infra/providers catalogs) to create an empty slot, then POST /v1/infra/servers/:id/deploy with the source. See GET /v1/me -> deployment.galaxyApp.checklist. For a deliberate dedicated standalone VM pass placement: \"dedicated\" together with provider/plan/region.",
      "example": {
        "name": "<slug>",
        "source": { "content": "<base64 gzip-tar of the app>" },
        "runtime": "node20",
        "start": "node server.js",
        "port": 3000
      }
    }
  }
}

If the body carries placement: "dedicated", the hint is different: it suggests adding provider, plan and region while keeping the dedicated server, instead of moving the application into a galaxy container.

Idempotency

Pass the optional Idempotency-Key header to safely retry creating a standalone server. If the response to the first request is lost (a network drop, a load-balancer timeout), a retry with the same key does not create a second server — it returns the same server the first request created, with status 201 and an Idempotent-Replayed: true response header.

  • The key is a 1–255 character string from [A-Za-z0-9_.:-]. It is scoped to your API key.
  • On a replay the one-time SSH credentials are not re-exposed. In the response body ssh.privateKey and ssh.password are null, and a note field explains this. Keep the credentials from the first create response.
  • The header applies to standalone servers only. On galaxy-placement Bitrix24 accounts a well-formed key is ignored without an error, and the retry protection does not extend to that path.
  • The header is not supported together with graduateFrom for a dedicated server — it returns 400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION.
Status code When
400 INVALID_IDEMPOTENCY_KEY The key fails validation (length or disallowed characters)
400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION The key together with graduateFrom for a dedicated server
409 IDEMPOTENCY_KEY_ALREADY_USED The key was already used for a server that has since been deleted — pick a new key
409 IDEMPOTENCY_CONCURRENT_RETRY A concurrent request with the same key is still in progress — retry shortly

Known specifics

  • Examples of valid and invalid names by the pattern ^[a-z][a-z0-9-]*$:
    • Valid: my-app, bot-1, crm-dashboard.
    • Invalid: My-App (uppercase), bot_1 (underscore), my.app (dot), café (non-ASCII letters).
  • sshPublicKey vs auto-generation. If you pass your own public key — the platform does not generate a private one, and ssh.privateKey in the response will be null. The password in ssh.password is still returned.
  • When region in the response differs from the requested one. The requested zone may have run out of IP addresses (Address space exhausted) — then the platform automatically tries the next zones in the order from GET /v1/infra/providers/:providerId/regions, and records this in the audit log with the SERVER_ZONE_FALLBACK event.
  • Provisioning timeout is 15 minutes. If status stays in provisioning longer, the platform moves the server to error and fills in provisionError and provisionErrorCode. After that, all that remains is to call DELETE and create a new one.
  • The runtime is installed at the deploy stage. The runtime parameter has been removed from POST /v1/infra/servers (it will return 400 RUNTIME_PARAM_REMOVED). Specify runtime in the body of POST /:id/deploy — it will be installed between archive extraction and application start.

See also