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

Operation outcome

GET /v1/infra/operations/:operationId

Returns the outcome of one specific run by its operation id. The endpoint serves two kinds of operation — a deploy POST /deploy and a command POST /exec. Use it when the connection dropped and the terminal response never reached the client: the id tells you how the run ended instead of forcing a blind retry.

For a command this is the only way to learn the outcome after a drop. A transport timeout ends only the client-side wait. The command itself keeps running on the server and can finish after the client already received an error. That is why re-running a state-changing command may apply the change a second time.

The endpoint is read-only and has no side effects. Every run is addressable on its own, including earlier attempts on the same server. Outcomes are kept for 7 days.

Both deploy kinds receive an id, but at different points:

  • for a standalone virtual machine (kind: "STANDALONE"), the record is opened before the pipeline. If the machine is sleeping, the row is already visible as running in the server operation list while the blocking wake is in progress, but no HTTP status, header, or SSE frame reaches this connection until the wake completes. The header and first SSE frame arrive before the first deploy step;
  • for a Galaxy application (kind: "GALAXY_APP"), the record is opened after the application mutex is acquired, on entry to the first step. The id arrives only with the terminal JSON response, in the header and body. If the connection dropped earlier, find the run through the server operation list.

For a command POST /exec the record is opened after the server lock is acquired and before the command is dispatched to the agent. On a standalone virtual machine the header carrying the id is sent before the wait begins. For a Galaxy application it arrives only with the terminal response: that branch opens no stream and writes nothing into the connection until the run ends, so an interrupted wait never receives the id — find the run through the server operation list with ?kind=exec.

Where the id comes from — three channels, all carried by the same run:

  • the X-Vibe-Operation-Id response header — immediate for a command and for a deploy on an already available standalone machine, delayed until the blocking wake completes for a sleeping standalone deploy, and terminal-response-only for a Galaxy application in both cases;
  • the first event: operation frame with ?stream=true — for a command and for a deploy on a standalone machine, but only after any blocking wake; a browser EventSource does not expose response headers, so this is its only pre-pipeline channel;
  • the operationId field in the body — data.operationId on success, error.operationId on failure.

A missing id does not mean the deploy never started. A sleeping standalone connection may have dropped during the blocking wake, a Galaxy connection may have dropped before the terminal response, or the platform may have failed to open a record and continued without an id. Do not redeploy blindly: first call GET /v1/infra/servers/:id/operations, then reconcile the server state and logs.

Parameters

Parameter In Type Required Description
operationId path string yes Operation ID from the POST /deploy or POST /exec response

Examples

curl — personal key

Terminal
curl -H "X-Api-Key: YOUR_API_KEY" \
  https://vibecode.bitrix24.com/v1/infra/operations/OPERATION_ID

curl — OAuth application

Terminal
curl -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  https://vibecode.bitrix24.com/v1/infra/operations/OPERATION_ID

JavaScript — personal key

javascript
// Deploy: after any blocking wake, take the id from the header BEFORE the body arrives.
// If the connection drops during wake, discover the id through the server operation list.
const deploy = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/deploy`,
  {
    method: 'POST',
    headers: { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
    body: JSON.stringify({ source: { content: archiveBase64 }, start: 'node index.js' }),
  }
)
const operationId = deploy.headers.get('X-Vibe-Operation-Id')

// The connection dropped — ask for the outcome instead of redeploying
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/operations/${operationId}`,
  { headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
)

if (res.status === 410) {
  console.log('The operation existed, its outcome is no longer stored')
} else if (res.status === 404) {
  console.log('No such operation')
} else {
  const { data } = await res.json()
  console.log(`Status: ${data.status}, step: ${data.step}`)
}

JavaScript — OAuth application

javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/operations/${operationId}`,
  {
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
    },
  }
)

Response fields

Field Type Description
success boolean Always true on success
data.operationId string Operation ID
data.kind string Operation kind: deploy or exec
data.serverId string ID of the server the deploy ran on
data.status string running, succeeded, failed or unknown — see the table below
data.step string | null Pipeline step the operation is on, or stopped at. Always null for kind: "exec" — a command has no steps
data.exitCode number | null kind: "exec" only. Exit status of the command when the agent proved one, otherwise null. A number arrives only together with status: "succeeded" — see the pair rule below the status table
data.startedAt string Start time, ISO 8601
data.finishedAt string | null Finish time, ISO 8601. null while the operation is running
data.error object | null { code, message } when status: "failed", otherwise null

Values of data.status:

Value Meaning
running The run is in progress right now
succeeded For a deploy it means it finished successfully. For a command it means it ran to completion, and its own verdict is in data.exitCode — a non-zero exit status arrives with this status too, because the status answers "did it run", not "what was the result"
failed The reason is in data.error. For a deploy it means the deploy failed. For a command it means the command never ran: the status is set only when the platform can prove that — for example on EXEC_BUSY, where the agent was busy with another command. Retrying the command is safe in that case
unknown The platform cannot prove the remote outcome: the transport was lost or the process died between the start and the outcome write. Not the same as "there was no run": the operation definitely started. For a command it means the command may be executing on the server right now, so reconcile the server state in GET /v1/infra/servers/:id and the logs before re-running a state-changing command

Judge by the pair data.exitCode + data.status, never by one field alone. A number arrives only with succeeded and means the command ran to completion. null on its own does NOT mean "the outcome is not proven": with succeeded it means the command ran but the agent named no code, so do not re-run it; with failed it means the command never started and a retry is safe; with running it means the run is still in flight; and only with unknown is nothing proven.

What is stored and what is not. The record carries the outcome of the attempt — status, exit status and the reason for a refusal. The command output (stdout and stderr) is not stored in it. If the output of a long command is needed after a drop, run it as a background job and read the journal through GET /v1/infra/servers/:id/logs — the procedure is described in the section on running commands.

Response example

Deploy in progress:

JSON
{
  "success": true,
  "data": {
    "operationId": "clz9k2m4x0001qw8h3f7d2n5p",
    "kind": "deploy",
    "serverId": "8f14e45f-ceea-467a-9c8d-2f1c5a6b7e30",
    "status": "running",
    "step": "install",
    "startedAt": "2026-08-11T09:14:22.117Z",
    "finishedAt": null,
    "error": null
  }
}

Deploy finished successfully:

JSON
{
  "success": true,
  "data": {
    "operationId": "clz9k2m4x0001qw8h3f7d2n5p",
    "kind": "deploy",
    "serverId": "8f14e45f-ceea-467a-9c8d-2f1c5a6b7e30",
    "status": "succeeded",
    "step": null,
    "startedAt": "2026-08-11T09:14:22.117Z",
    "finishedAt": "2026-08-11T09:18:04.902Z",
    "error": null
  }
}

Deploy failed:

JSON
{
  "success": true,
  "data": {
    "operationId": "clz9k2m4x0001qw8h3f7d2n5p",
    "kind": "deploy",
    "serverId": "8f14e45f-ceea-467a-9c8d-2f1c5a6b7e30",
    "status": "failed",
    "step": "healthcheck",
    "startedAt": "2026-08-11T09:14:22.117Z",
    "finishedAt": "2026-08-11T09:17:41.338Z",
    "error": {
      "code": "DEPLOY_STEP_FAILED",
      "message": "connect ECONNREFUSED 127.0.0.1:3000"
    }
  }
}

The command ran to completion:

JSON
{
  "success": true,
  "data": {
    "operationId": "clz9k2m4x0001qw8h3f7d2n5q",
    "kind": "exec",
    "serverId": "8f14e45f-ceea-467a-9c8d-2f1c5a6b7e30",
    "status": "succeeded",
    "step": null,
    "exitCode": 0,
    "startedAt": "2026-08-11T09:20:02.004Z",
    "finishedAt": "2026-08-11T09:24:37.815Z",
    "error": null
  }
}

The wait for the command was interrupted, the outcome is unknown:

JSON
{
  "success": true,
  "data": {
    "operationId": "clz9k2m4x0001qw8h3f7d2n5r",
    "kind": "exec",
    "serverId": "8f14e45f-ceea-467a-9c8d-2f1c5a6b7e30",
    "status": "unknown",
    "step": null,
    "exitCode": null,
    "startedAt": "2026-08-11T09:31:10.220Z",
    "finishedAt": "2026-08-11T09:36:41.905Z",
    "error": null
  }
}

Error response example

410 — the operation existed, but its outcome is no longer stored:

JSON
{
  "success": false,
  "error": {
    "code": "OPERATION_OUTCOME_EXPIRED",
    "message": "The outcome of this deploy is no longer stored (kept for 7 days).",
    "serverId": "8f14e45f-ceea-467a-9c8d-2f1c5a6b7e30",
    "startedAt": "2026-07-30T11:02:19.400Z"
  }
}

Errors

HTTP Code Description
400 INVALID_OPERATION_ID The ID is not in the format the platform hands out
401 MISSING_API_KEY The X-Api-Key header is missing
401 INVALID_API_KEY The key is not recognized — no such key exists on the platform
404 OPERATION_NOT_FOUND No such operation, it belongs to a different key, or the record has been removed
410 OPERATION_OUTCOME_EXPIRED The operation is yours, but its outcome is no longer stored
429 RATE_LIMITED Polling limit exceeded

The full list of common API errors — Errors.

Known specifics

  • Someone else's operation answers exactly like a non-existent one, and that is deliberate. All three cases (no such id, an id belonging to another key, a removed record) return the same 404. A distinct answer for someone else's operation would confirm that the id exists, and the id is itself the access key to the record: guessing one would let you read someone else's deploy.
  • 410 differs from 404 only for the owner. "The outcome is no longer stored" appears when the operation is yours and definitely ran, more than 7 days ago. An id that belongs to someone else, or a made-up one, never receives that answer.
  • The record outlives the outcome. After 7 days the outcome stops being served, but the fact that the operation existed is kept for a while longer — that is what makes the distinguishable 410 possible at all. The record is removed afterwards, and the same id starts answering 404.
  • unknown is an answer, not an error. The operation definitely ran, but a transport or process loss means the platform does not know how it ended. Reconcile the current server state and logs first; redeploy only after that check.
  • The polling limit is 60 requests per minute per key. The exact value arrives in the x-ratelimit-limit header (the cap is divided across replicas). An outcome does not change faster than the deploy itself, so polling every few seconds is enough.
  • Read an operation started by another key through the server. This endpoint answers only the creator key. The server owner under another of their keys, and a development team member with the settings permission, read the same outcome through GET /v1/infra/servers/:id/operations/:operationId.

See also