Para agentes de IA: markdown de esta página — /docs-content-en/infra/servers.md índice de la documentación — /llms.txt
Los artículos de la documentación están disponibles actualmente en inglés.
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 at a cloud provider, always in Black Hole mode — iptables blocks all inbound ports, so the application is invisible from the internet.
There are two placement models, and what to expect after creation depends on the one your Bitrix24 account uses:
- A dedicated virtual machine (the default model). The response is returned immediately with the
provisioningstatus. Provisioning takes 1–3 minutes, after which you need to pollGET /v1/infra/servers/:iduntilstatus: "running"andblackholeStatus: "CONNECTED". The response contains the SSH credentialsssh.passwordandssh.privateKeyonly 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/serverscreates a Galaxy application instead of a virtual machine. In the response, this model is marked bycreatedVia: "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 when you upload the code. 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.
Scenario 1 — transparent, recommended
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.
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, healthPath 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 96 MB per request body, which is about 72 MB of the archive itself: base64 is roughly a third larger than the raw bytes. A body over the cap is refused with 413 INLINE_SOURCE_TOO_LARGE. Deploy a larger archive in two steps — scenario 2 below |
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 is rejected with 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 |
healthPath |
string | no | The path the platform uses to check the application's readiness inside its container. Up to 500 characters, must start with /. Defaults to /. Ignored when creating a dedicated virtual machine |
The one-shot create accepts no other deploy fields: preStart, systemd, serviceName, cleanDeploy, extractTo, hardening, preserveEnv, dataDirs, dataDirsRecursive return 400 UNKNOWN_PARAM, and the full list of accepted fields arrives in details.validParams. These fields belong to a dedicated virtual machine deploy and do not apply to a Galaxy application — put the commands that must run before the application starts into install.
Passing source without runtime and start returns 400 — both fields are required. On a Bitrix24 account without Galaxy mode, a fresh request carrying source is forbidden and returns 400 SOURCE_AT_CREATE_GALAXY_ONLY. A retry that safely converges on an in-flight keyless GALAXY_APP does so before that fresh-only refusal: it returns the existing id with sourceIgnored: true and never starts a second build.
During the trial period, first read the deployment and capabilities.servers.create blocks from GET /v1/me. When galaxyApp is present, a one-shot create is worth trying, but the block reflects stored state and does not guarantee success: only the POST checks live capacity. When Galaxy mode is disabled, placementNote is absent and a request carrying source returns 400 SOURCE_AT_CREATE_GALAXY_ONLY. With no host or in dedicated-machine mode, the two-step path is available only while capabilities.servers.create.available is true; select an allowed plan ID from capabilities.servers.create.limits.allowedPlans. When an existing host is unsuitable, wait for it to recover, ask an administrator to repair it, or upgrade the plan before retrying one-shot creation. While available is false, do not switch to a dedicated machine: with trial enforcement active, the occupied slot returns 402 TRIAL_PORTAL_LIMIT. After removing the host, re-read GET /v1/me and use the two-step path advertised there.
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.
# 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 and plan are required by the schema for a create without source. region is optional: omit it and the platform resolves the chosen provider's default region. For a Galaxy application the values are informational — the app inherits its host's provider, plan and region.
When a value you sent differs from the one the app actually got, the response carries an entry in warnings[] next to data: it names the diverging fields, shows both the value you sent and the one in force, and warns that re-sending will change nothing. Fields that matched are not mentioned, so a correct call stays free of warnings. If you need a machine whose provider, plan and region you choose, create it with placement set to dedicated.
A dedicated virtual machine instead of a Galaxy app
If your Bitrix24 account places apps in galaxies but this particular app needs its own server, pass the placement field set to dedicated. The provider + plan pair is required in that case; region stays optional. A source field in such a request returns 400 SOURCE_AT_CREATE_GALAXY_ONLY: a dedicated virtual machine is deployed in two steps, and the code is uploaded via POST /:id/deploy.
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-small",
"region": "bc-eu-central",
"placement": "dedicated"
}'
The response carries kind set to STANDALONE. From there the dedicated virtual machine flow applies: wait for status: "running" and blackholeStatus: "CONNECTED", then deploy.
Diagnostics and environment variables
- The
provisionErrorfield. If the build or startup failed,GET /v1/infra/servers/:idreturns the failure reason indata.provisionError— plus the tail of the Docker build log when the image build failed. Read this field to find the cause. envvariables are injected at start. Values fromenvare passed into the container at start time viadocker run --env, not baked into the image. The platform passesPORTalongside them (equal to theportfield,3000by default): thePORTkey is reserved, your own value is overridden, and the response carries awarnings[]entry — see POST /v1/infra/servers/:id/deploy. So API keys and secrets fromenvdo 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, and the audit log. Immutable after creation. For a human-readable name, use displayName |
displayName |
string | no | Human-readable name in any language or script (emoji included). 2–100 characters, no control bytes. Displayed in the UI, server-down notifications, security alerts, billing line items, and the Bitrix24 catalog. If not passed, name is used instead. 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; you can set it 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 | no | Region ID from GET /v1/infra/providers/:providerId/regions, for example bc-eu-central. Omit it and the platform resolves the provider's default region |
image |
string | no | Public OS image identifier from GET /v1/infra/providers/:providerId/images. Stable family identifiers such as ubuntu-2404-lts are accepted; the platform resolves them to the provider's current concrete image. If omitted, the provider's default image is resolved |
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 generates a private key and returns 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 dedicated 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) that the platform deletes as soon as it has created the dedicated server for it — so the old application does not linger in the galaxy. Owner-scoped: the same key, the same Bitrix24 account, kind=GALAXY_APP. An identifier that is not yours, or that does not belong to a Galaxy application, 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 |
runMode |
object | no | The run mode the server is created in. Same shape as PATCH /:id/run-mode: {"mode": "ALWAYS"}, {"mode": "IDLE", "idleMinutes": 15|30|60|240} or {"mode": "SCHEDULE", "scheduleId": "…"}. The block is discriminated on mode, so a field that does not belong to the chosen mode is rejected rather than silently dropped. The refusal comes BEFORE the machine is created, so a failed request leaves no server behind. Without the block the server gets the schedule that an administrator of the Bitrix24 account has marked for new machines — Set the default schedule. Without such a mark the default auto-sleep timeout from "Known specifics" applies |
Note: for the dedicated-virtual-machine model the
runtimeparameter is not accepted inPOST /v1/infra/servers— passing it will return400 RUNTIME_PARAM_REMOVED. The runtime is installed at the deploy stage. See POST /:id/deploy. Exception — Galaxy mode: whensourceis passed,runtimeis required (see the "Galaxy application" section), and400 RUNTIME_PARAM_REMOVEDis not returned in that case.
Examples
curl — personal key
# 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
# 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
// 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
// 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 | "provisioning" right after a new server is created — wait for "running", which takes 1–3 minutes. On a reuse response (reused: true) this is the existing server's status and can be anything, including "running" immediately — in which case waiting for "running" verifies nothing |
data.provider |
string | Echo of the passed provider. Exception — a Galaxy application (createdVia: "galaxy"): the host's provider is returned, not the one requested |
data.name |
string | The server's system name. Echo of the passed name when a new server is created. On a reuse response it is the existing server's name, and the one you asked for is in data.requestedName |
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 equals 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 | SSH user: 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 | Resolved public image identifier. It may be the provider default when image was omitted, or a stable family identifier normalized from the provider's concrete 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. On a reuse response it is the existing server's value, so a v1 create response may carry "ui" |
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.sleepAfterMinutes |
number | null | Saved auto-sleep timeout in minutes. The field is present in every successful 201 response — for a new dedicated machine and Galaxy application, and on reuse and idempotency replay. null is the saved "Never" choice and disables idle auto-sleep. Setting it is rejected while wake windows are enabled, and storing it blocks schedule creation and update even for a disabled window, except for an app in a galaxy and a server in the SCHEDULE run mode; for an older conflicting record with an effective schedule, the platform's post-window timeout applies |
data.runtimeId |
string | null | Always null at creation. Filled in on deploy with runtime |
data.runtimeStatus |
string | null | A deprecated field, kept for compatibility. For servers created after 2026-04-25 it always returns null. Runtime readiness after a deploy is determined by the success of the runtime step in the POST /:id/deploy response, not this field |
data.appUrl |
string | null | HTTPS address of the application: https://{subdomain}.vibecode.bitrix24.com |
data.createdAt |
string (ISO 8601) | Creation timestamp |
warnings |
array<string> | Next to data, not inside it. Returned only when there is something to report: for example, displayName or description lost their non-ASCII characters on the way in (Windows PowerShell without explicit UTF-8 serialization is the usual case). The server is still created, and both values reach the Bitrix24 catalog card as is |
data.sleepAfterMinutes is a response field, not a create parameter. To change the saved policy after creating a dedicated machine, call PATCH /v1/infra/servers/:id/sleep. Do not substitute the expected default for this field: on reuse or idempotency replay the response carries the existing record's value, including null or a historical numeric value. It is not a computed effective timeout: enabled wake-schedule windows must be deleted or disabled before null can be set, and once null is stored, every schedule create/update is rejected, even for a disabled window, except for an app in a galaxy and a server in the SCHEDULE run mode. If a reuse or replay exposes an older conflicting record with an effective schedule, the platform's post-window fallback applies — 15 minutes by default.
Response example
A dedicated virtual machine:
{
"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",
"sleepAfterMinutes": 60,
"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. ssh is null. There is no next field:
{
"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",
"sleepAfterMinutes": 60,
"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:
{
"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",
"sleepAfterMinutes": 60,
"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:
{
"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):
{
"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 (an invalid name format, a missing required field, 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 required provider + plan pair, an error.hint object is added (see below) |
| 400 | SOURCE_AT_CREATE_GALAXY_ONLY |
The request carrying source could not be placed in a galaxy. This happens when Galaxy mode is disabled, when the request also carries placement: "dedicated", or when the actual check finds no suitable host. Follow error.hint and capabilities.servers.create: with no galaxy host the two-step path is available only when available is true; with an existing host and available: false, the hint rules that path out because with trial enforcement active a dedicated-machine create returns 402 TRIAL_PORTAL_LIMIT |
| 400 | GALAXY_SOURCE_URL_NOT_ALLOWED |
The link in source.url did not pass the check that runs before it is sent to the galaxy host. Reasons: the address does not point at the platform source storage, the value is not an absolute URL, the scheme is neither http nor https, or the address does not point at the public network — a local, internal or service host. The response text names the rule that fired, and for an outside address it names the replacement — save the archive as a version and deploy it as a second step through code deploy. The same check runs on that deploy too |
| 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 |
| 400 | WORK_SCHEDULE_NOT_FOUND |
The runMode block named a schedule ID that does not exist in your account. No machine is created |
| 400 | WORK_SCHEDULE_EMPTY |
The named schedule has no windows — a server on it would never run. No machine is created |
| 400 | RUN_MODE_UNAVAILABLE |
Run modes are not enabled for your account yet. No machine is created |
| 400 | INVALID_PLAN |
The plan value is not in the provider catalog. List — GET /v1/infra/providers/:providerId/plans |
| 400 | INVALID_REGION |
The region you passed is not in the provider catalog. List — GET /v1/infra/providers/:providerId/regions. Second case: region was omitted and the provider exposes no regions at all — message then names the provider, and the platform has no default to substitute |
| 401 | MISSING_API_KEY |
The X-Api-Key header was not passed |
| 401 | INVALID_API_KEY |
The key is not recognized — no such key exists on the platform |
| 401 | UNAUTHENTICATED |
An authorization key (vibe_app_) was sent without the Authorization: Bearer header — server creation runs a plan check that has to know who is creating the server. The error.hint field names the next step: complete the OAuth flow, or use a personal API key (vibe_api_), which needs no session. See Keys and authorization |
| 402 | INT_TARIFF_REQUIRED |
The portal is on a free Bitrix24 plan — creating servers requires a commercial plan (a trial plan grants limited access). For accounts where access is narrowed to the Vibe+ plan line, infrastructure requires a Vibe+ plan, and an ordinary commercial plan is declined with INT_VIBE_PLUS_REQUIRED |
| 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 for the trial period was exceeded (error.retryable: false); do not repeat creation with different names, keys, providers or plans. Check deployment.standalone.reuseTarget in GET /v1/me; otherwise follow userMessage and alternatives. |
| 409 | APPLICATION_REUSE_CONFLICT |
The application binding changed concurrently during server reuse. error.retryable: true; retry the request |
| 402 | TRIAL_USER_LIMIT |
The per-user server quota for the trial period was exceeded |
| 402 | PLAN_NOT_ALLOWED_ON_TRIAL |
The requested plan is not available during the trial period. Read the allowed plans from error.details.allowedPlans, which lists the plans the trial gate is configured for — the set varies, so do not hardcode it. 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 Cowork/Code key — such a key works with data only and cannot perform write operations. To issue a key that can, see Project key for deploy |
| 404 | NO_CREDENTIALS |
The provider is not configured on the platform |
| 409 | GALAXY_FULL |
The portal hosts applications in galaxies and there is no room for another one. Before refusing, the platform tries to place the application on another galaxy, so this response means there really is no room left. Delete an unneeded Galaxy application, or ask for a dedicated machine — the placement field with the value dedicated |
| 409 | REISSUE_IN_PROGRESS |
The application card owner is re-issuing its authorization key right now, so no server was created: it would have landed on a key that is being revoked at the same moment. The refusal is transient — repeat the request once the re-issue finishes |
| 409 | APPLICATION_KEY_RECOVERY_REQUIRED |
The replacement of the API key that made the request is unfinished: its rotation ended with a 500 response or has not finished yet. No server was created: it would have landed on a key with an unfinished replacement. The steps are in Management keys, in the key rotation section |
| 413 | INLINE_SOURCE_TOO_LARGE |
The body carrying inline source.content is over 96 MB, which is about 72 MB of the archive itself. The decision is based on the Content-Length header before the body is read, so the refusal is deterministic — re-sending the same body fails identically. It saves no traffic: the platform accepts the whole body first, and the refusal arrives once the upload has finished. error.hint carries four strings — reason, recovery, recoveryAction and note: the recipe here is to create the server without source, save the archive as a version on the identifier that comes back, and deploy it with {"source": {"versionId": "vN"}}, because every URL on the versioned path is built from the server identifier, which does not exist before creation |
| 429 | RATE_LIMITED |
The platform's overall request limit was exceeded |
| 429 | DEPLOY_BACKEND_BUSY |
The request carried an archive in source.content while the backend was already running the maximum number of such heavy requests (one shared counter with POST /:id/deploy and POST /:id/upload). The response carries a Retry-After: 30 header — retry in half a minute. To avoid the queue entirely, create the server without source and upload the code in a separate request with a link, {source: {url: ...}} — that path has no concurrency cap |
| 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 carries a localized description of the failure plus an incident code — the provider's raw response never reaches the body; it stays in the platform logs. Quote the incident code when contacting support |
| 503 | POOL_EXHAUSTED |
The create outcome is unknown: the database failure may have occurred after the operation started. Wait out Retry-After, check the server list, and retry creation only once you have confirmed that no server appeared |
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 required provider + plan pair. The region field is optional and defaults to the chosen provider's region. For RUNTIME_PARAM_REMOVED the hint arrives when runtime is passed without source, even if provider and plan are filled in. On a Bitrix24 account with dedicated virtual machines there is no hint in either case.
During trial access, these early hints use the same advisory assessment as GET /v1/me. A host that is suitable according to stored state produces a recommendation to try the one-shot request. With no host, the two-step path is available only while capabilities.servers.create.available is true. When an existing host is unsuitable, wait for recovery, repair the host, or upgrade the plan before retrying one-shot creation; after removing the host, re-read GET /v1/me and use the two-step path advertised there. The hint is advisory: the POST makes the final decision.
When a request carrying source has already reached the actual placement check and returns SOURCE_AT_CREATE_GALAXY_ONLY, its error.hint reflects that result. With an existing host during trial access, the hint rules out the two-step path while capabilities.servers.create.available is false: with trial enforcement active, that path returns 402 TRIAL_PORTAL_LIMIT. With no host, it permits creating a dedicated machine without source and deploying afterwards only after that capability check succeeds.
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 |
{
"success": false,
"error": {
"code": "INVALID_REQUEST",
"message": "provider: Required; plan: Required",
"hint": {
"reason": "This portal places new apps on shared galaxy hosts, and the request lacked `source` and the required provider/plan pair. `region` is optional and defaults to the provider's region.",
"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 and plan (region is optional and defaults to the provider's region) 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\" with provider and plan; region is optional.",
"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: instead of moving the application into a galaxy container, it suggests keeping the dedicated server and adding the required provider and plan, with region omitted to get the provider's default region.
Reusing a server
POST /v1/infra/servers does not create a second server and returns an existing one in two cases. First, the calling key belongs to a card in Applications that already has a live server bound to it. Second, the same API key is already creating a GALAXY_APP in provisioning, that server has a live keyless card, Applications are visible to the portal, and the parent Galaxy host is still live: it has not been deleted, is still a GALAXY, and is not in error or deleted. The second case applies to every placement except placement: "dedicated", even if placement settings switched to standalone machines after the first request, and it is checked before the fresh-only source/placement refusal. Once that application leaves provisioning, the next create may create a new server again.
The name you sent plays no part in that decision: reuse is determined from the calling key and server state, not the name.
On a reuse response (reused: true) every data field describes the existing server, not your request — status, provider, plan, region, image, createdVia, createdAt all belong to it. The status can be anything, so waiting for "running" is not a check that the server is yours. The check is reused plus the name ⊕ requestedName pair.
Such a response always carries data.reused: true and, next to data, a warnings array with at least one entry. Compare data.name with data.requestedName — they differ whenever the application's server was created earlier under a different name.
| Field | Type | When |
|---|---|---|
data.reused |
boolean | Always on such a response. No new server was created |
data.reusedReason |
string | APPLICATION_ALREADY_HAS_SERVER |
data.requestedName |
string | Echo of your name — always, even when it equals the existing name |
data.sourceIgnored |
boolean | The source you sent was not deployed by this request |
data.deploying |
boolean | The source you sent is being built onto the application's container right now |
data.metaIgnored |
boolean | The displayName/description you sent were not applied |
warnings |
array | Next to data, not inside it. At least one entry |
What to do before deploying. Call GET /v1/infra/servers/:id and inspect what is already deployed there. A deploy replaces the code currently running — it is not additive. If this is not the server you intended, stop and ask the user which application this key should be working on.
About the source you sent. Ordinary reuse builds source onto a Galaxy application only when it has no container yet or its previous build failed — the response then carries deploying: true, and you poll GET /v1/infra/servers/:id until running. When the request converges on a keyless application in provisioning, it never starts a second build and returns sourceIgnored: true. If the earlier build is already active, data.next is absent: do not call deploy in parallel. Wait until the server leaves provisioning, then deploy the ignored source through POST /v1/infra/servers/:id/deploy only if replacement is still needed. A parked application with no active build keeps next: "deploy".
About the displayName and description you sent. A reuse never renames the server it hands back: it keeps its own name and description, and the values you sent are discarded — the response shows this as data.metaIgnored: true plus a dedicated warning. Rename it deliberately with PATCH /v1/infra/servers/:id once you have confirmed the server is the right one.
About data.next. On a reuse response the field arrives only for a parked Galaxy application with no container, no active build, and a live galaxy host. During an already active build, next is deliberately absent: poll the server and do not start a competing deploy. It is also absent when the application is detached from its host or the server can already run code. Do not read the missing field as an error or permission to overwrite code.
How to get a different server for the application. There is no separate call that changes the binding. When you need a dedicated virtual machine and want the binding left alone, create it with placement: "dedicated" — reuse does not fire, but the new server is not bound to the application either. When you need to change the bound server itself, delete the current one via DELETE /v1/infra/servers/:id: the next POST /v1/infra/servers with the same key then clears the stale binding and binds the newly created server. Deletion is irreversible and destroys everything deployed on the server — confirm via GET /v1/infra/servers/:id that it is not someone else's work first.
A create with placement: "dedicated" is not affected by reuse.
Idempotency
Pass the optional Idempotency-Key header to safely retry a registered standalone-server or Galaxy-application create. 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 resource — it returns the first request's result with status 201 and an Idempotent-Replayed: true response header.
The retry works for 15 minutes from key reservation (from server creation for earlier standalone records). Past that window the same key no longer returns the server: the request runs as a new create with every policy check — and gets either an ordinary policy refusal (402 / 403) or 409 IDEMPOTENCY_KEY_ALREADY_USED. Unless you deleted it, the server is still there — the response text says so.
Important: retry with THE SAME key, not a fresh one. For a standalone create the same Idempotency-Key cannot produce a second machine: it returns either the server itself or a 409 that tells you its fate. A fresh Idempotency-Key is not itself a replay of the earlier request, but ordinary reuse rules still apply. In particular, a request with the same API key returns a live keyless GALAXY_APP in provisioning with reused: true for every non-dedicated placement. A fresh key therefore does not mean that a new server will necessarily be created: inspect data.id, data.reused, and GET /v1/infra/servers.
- The key is a 1–255 character string from
[A-Za-z0-9_.:-]. It is scoped to your API key. - On a standalone replay the one-time SSH credentials are not re-exposed. In the response body
ssh.privateKeyandssh.passwordarenull, and anotefield explains this. Keep the credentials from the first create response. AGALAXY_APPhas no SSH credentials of its own:sshis null and no note is required. - Galaxy create and reuse are protected once the new protection is enabled. The key is reserved independently of the resource row: different keys can return one reused slot. New fresh, revived, and reused results record the exact
applicationId; if the fresh/revived server's background attachment finds the rightful card already created for this key, the record atomically switches from its preallocated id to that card's id and is fixed thereafter. A retry within 15 minutes accepts only that exact card, not any later same-owner card attached to the server. The platform also rechecks both rows' owner and portal and the live Galaxy host. If the recorded card was deleted, detached, or replaced, ownership changed, or the host was deleted or enterederror/deleted, the key remains spent but the response is409 IDEMPOTENCY_KEY_ALREADY_USEDwithoutdataor the previous id; the request never falls through to a fresh create. UUID-only records from before activation remain legacy. Past the window ordinary checks and the spent-key refusal apply. Until activation the first galaxy create still does not reserve a key; recorded keys are read even after protection is switched off. Earlier galaxy creates without a recorded key cannot be recovered. - The header is not supported together with
graduateFromfor a dedicated server — it returns400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION. - A retry of a recorded key does not restart the build. After a build failure, run an explicit deploy. A fresh/revived Galaxy result replays through the ordinary create response; semantic reuse again carries
reused,reusedReason,requestedName,sourceIgnored/metaIgnored,warnings,hint, and an applicablenext. These fields use the replay's current validated body, but a new reuse record separately remembers whether the original request accepted itssource: while the build it started remains active, the replay returnsdeploying: truewithoutsourceIgnoredornext; after it finishes, the source is still not called ignored and is not deployed again automatically. A legacy v1 record has no such provenance, so its replay does not invent an outcome: it returns neithersourceIgnorednornext, and tells the caller to inspect the server and logs before any deploy. A request with a fresh key follows ordinary reuse rules: an empty or failed slot may start a build, while convergence on a keyless application with an active build never starts another one and may returnsourceIgnored: true.
| 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 is spent. The response text distinguishes a deleted server, a retry past the 15-minute window, and an unavailable previous result — including deletion, detachment, or replacement of the exact recorded Galaxy card. In the last case the response carries no data or previous identifier and never starts a fresh create. The same code is returned on the galaxy path when the key is already spent |
| 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 for 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).
- Valid:
sshPublicKeyvs auto-generation. If you pass your own public key, the platform does not generate a private one, andssh.privateKeyin the response will benull. The password inssh.passwordis still returned.- When
regionin the response differs from the requested one. If the requested zone has run out of IP addresses (Address space exhausted), the platform automatically tries the next zones in the order fromGET /v1/infra/providers/:providerId/regions, and records this in the audit log with theSERVER_ZONE_FALLBACKevent. In Bitrix24 Cloud, a capacity fallback may select another availability zone inside the same public region; in that casedata.regiondoes not change. - Provisioning timeout is 15 minutes. If
statusstays inprovisioninglonger, the platform moves the server toerrorand fills inprovisionErrorandprovisionErrorCode. After that, all that remains is to callDELETEand create a new one. - A new regular machine gets a 60-minute auto-sleep timeout. The actual saved value is returned immediately in
data.sleepAfterMinutesby the create response and later byGET /v1/infra/servers/:id. On reuse and idempotency replay this is the existing record's value, so it can benullor differ from 60. A server created with the schedule marked for new machines keeps this value, but it does not apply while the schedule is assigned — the mode is in therunModeandworkSchedulefields of the sameGET. Inbound HTTP requests and SSH activity reset the timer; cron, background processes, and the application's own outbound requests do not. For the allowed values and deliberate disabling of auto-sleep throughPATCHwithnull, see Configure auto-sleep. - The runtime is installed at the deploy stage. The
runtimeparameter withoutsourceis not accepted inPOST /v1/infra/servers(it will return400 RUNTIME_PARAM_REMOVED). Specifyruntimein the body ofPOST /:id/deploy— for a dedicated virtual machine, the pipeline begins in the orderruntime → stop_existing → clean → download → install. Together withsourcethe runtime is accepted and required: that is the one-shot Galaxy application create, whereruntimeandstartgo in the same request. - Non-ASCII characters in
displayNameanddescriptionfrom Windows PowerShell. Sent without explicit UTF-8 serialization, they are stored as question marks (?): the bytes are lost on the client side, before the request is sent. A ready-to-run call withUTF8.GetBytes— Windows / PowerShell and UTF-8.