Для AI-агентов: markdown этой страницы — /docs-content/infra/deploy/operation-status.md индекс документации — /llms.txt

Исход операции

GET /v1/infra/operations/:operationId

Возвращает исход конкретного запуска по его идентификатору. Ручка обслуживает два вида операций — выкладку POST /deploy и команду POST /exec. Нужна, когда соединение оборвалось и терминальный ответ до клиента не дошёл: по идентификатору можно узнать, чем всё кончилось, вместо того чтобы запускать вызов заново.

Для команды это единственный способ узнать исход после обрыва. Транспортный таймаут прекращает только ожидание на стороне клиента, сама команда на сервере продолжает выполняться и может завершиться уже после того, как клиент получил ошибку. Поэтому повторный запуск изменяющей команды может применить изменение второй раз.

Ручка только на чтение, побочных действий у неё нет. Каждый запуск адресуется отдельно, включая предыдущие попытки на том же сервере. Исход хранится 7 суток.

Идентификатор есть у обоих видов выкладки, но момент его выдачи различается:

  • для отдельной виртуальной машины (kind: "STANDALONE") запись заводится до конвейера, заголовок приходит сразу, а в SSE идентификатор дополнительно приходит первым кадром;
  • для galaxy-приложения (kind: "GALAXY_APP") запись заводится после захвата mutex приложения, при входе в первый шаг. Идентификатор приходит только с терминальным JSON-ответом в заголовке и теле. Если соединение оборвалось раньше, найдите запуск через список операций сервера.

У команды POST /exec запись заводится после захвата блокировки сервера, до отправки команды агенту. На отдельной виртуальной машине заголовок с идентификатором уходит раньше, чем начинается ожидание. У galaxy-приложения он приходит только с терминальным ответом: эта ветка не открывает поток и до конца работы в соединение ничего не пишет, поэтому при обрыве ожидания идентификатор до клиента не доезжает — ищите запуск через список операций сервера с параметром ?kind=exec.

Где взять идентификатор — три канала, все три несёт один и тот же запуск:

  • заголовок ответа X-Vibe-Operation-Id — у команды и у выкладки на отдельной машине приходит сразу, у galaxy-приложения только с терминальным ответом в обоих случаях,
  • первый кадр event: operation при ?stream=true — у команды и у выкладки на отдельной машине. Браузерный EventSource заголовков ответа не отдаёт, поэтому для него это единственный ранний канал,
  • поле operationId в теле — data.operationId при успехе, error.operationId при отказе.

Отсутствие идентификатора не означает, что выкладка не запускалась. У galaxy-приложения связь могла оборваться до терминального ответа; кроме того, платформа могла не суметь завести запись, и тогда выкладка продолжится без идентификатора. Не повторяйте её вслепую: сначала запросите GET /v1/infra/servers/:id/operations, затем сверьте состояние сервера и логи.

Параметры

Параметр В Тип Обяз. Описание
operationId path string да Идентификатор операции из ответа POST /deploy или POST /exec

Примеры

curl — личный ключ

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

curl — OAuth-приложение

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

JavaScript — личный ключ

javascript
// Выкладка: забираем идентификатор из заголовка ДО того, как придёт тело
const deploy = await fetch(
  `https://vibecode.bitrix24.tech/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')

// Связь оборвалась — спрашиваем исход вместо повторной выкладки
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/operations/${operationId}`,
  { headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
)

if (res.status === 410) {
  console.log('Операция была, исход больше не хранится')
} else if (res.status === 404) {
  console.log('Такой операции нет')
} else {
  const { data } = await res.json()
  console.log(`Статус: ${data.status}, шаг: ${data.step}`)
}

JavaScript — OAuth-приложение

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

Поля ответа

Поле Тип Описание
success boolean Всегда true при успехе
data.operationId string Идентификатор операции
data.kind string Вид операции: deploy или exec
data.serverId string ID сервера, на котором шёл запуск
data.status string running, succeeded, failed или unknown — см. таблицу ниже
data.step string | null Шаг конвейера, на котором операция находится или на котором остановилась. У kind: "exec" всегда null — у команды шагов нет
data.exitCode number | null Только при kind: "exec". Код возврата команды, если агент его подтвердил, иначе null. Число приходит только вместе со статусом succeeded — см. правило про пару под таблицей статусов
data.startedAt string Время старта, ISO 8601
data.finishedAt string | null Время завершения, ISO 8601. null, пока операция идёт
data.error object | null { code, message } при status: "failed", иначе null

Значения data.status:

Значение Что означает
running Запуск идёт прямо сейчас
succeeded У выкладки означает, что она завершилась успешно. У команды означает, что она отработала до конца, а её собственный вердикт лежит в data.exitCode — ненулевой код возврата приходит с этим же статусом, потому что статус отвечает на вопрос «выполнилась», а не «с каким результатом»
failed Причина — в data.error. У выкладки означает, что она провалилась. У команды означает, что она не запускалась: этот статус ставится, только когда платформа может это доказать, — например при EXEC_BUSY, когда агент был занят другой командой. В таком случае повторный запуск команды безопасен
unknown Платформа не может доказать удалённый исход: транспорт оборвался либо процесс прервался между стартом и записью исхода. Не то же самое, что «запуска не было»: операция точно начиналась. Для команды это означает, что она может выполняться на сервере прямо сейчас, поэтому перед повторным запуском изменяющей команды сверьтесь с состоянием сервера в GET /v1/infra/servers/:id и журналом

Судите по паре data.exitCode + data.status, а не по одному полю. Число приходит только у succeeded и означает, что команда отработала до конца. Само по себе null не означает «исход не доказан»: у succeeded оно означает, что команда отработала, а кода агент не назвал, — повторять её не нужно; у failed — что она не запускалась и повтор безопасен; у running — что она ещё идёт; и только у unknown не доказано ничего.

Что хранится, а что нет. Запись несёт исход попытки — статус, код возврата и причину отказа. Вывод команды (stdout и stderr) в ней не сохраняется. Если вывод длительной команды нужен после обрыва, запускайте её фоновой задачей и читайте журнал через GET /v1/infra/servers/:id/logs — порядок описан в разделе про выполнение команд.

Пример ответа

Выкладка идёт:

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
  }
}

Выкладка завершилась успешно:

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
  }
}

Выкладка провалилась:

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"
    }
  }
}

Команда отработала до конца:

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
  }
}

Ожидание команды оборвалось, исход неизвестен:

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
  }
}

Пример ответа при ошибке

410 — операция была, но исход уже не хранится:

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"
  }
}

Ошибки

HTTP Код Описание
400 INVALID_OPERATION_ID Идентификатор не той формы, которую выдаёт платформа
401 MISSING_API_KEY Не передан заголовок X-Api-Key
401 INVALID_API_KEY Ключ не опознан: такой строки на платформе нет
404 OPERATION_NOT_FOUND Операции нет, она принадлежит другому ключу либо запись уже удалена
410 OPERATION_OUTCOME_EXPIRED Операция ваша, но её исход больше не хранится
429 RATE_LIMITED Превышен лимит опроса

Полный список общих ошибок API — Ошибки.

Известные особенности

  • Чужая операция отвечает так же, как несуществующая — это сделано намеренно. Все три случая (идентификатора нет, идентификатор чужой, запись удалена) дают один и тот же 404. Отдельный ответ на чужую операцию подтверждал бы, что такой идентификатор существует, а идентификатор сам по себе является ключом доступа к записи: подобрав его, можно было бы прочитать чужую выкладку.
  • 410 отличается от 404 только для владельца. «Исход больше не хранится» приходит, когда операция ваша и она точно была, просто прошло больше 7 суток. На чужой или выдуманный идентификатор такой ответ не приходит никогда.
  • Запись живёт дольше, чем хранится исход. Через 7 суток исход перестаёт отдаваться, но сам факт операции сохраняется ещё некоторое время — за счёт этого и возможен различимый ответ 410. Потом запись удаляется, и тот же идентификатор начинает отвечать 404.
  • unknown — это ответ, а не ошибка. Он означает, что операция точно была, но из-за обрыва транспорта или процесса платформа не знает, чем она кончилась. Сначала сверьте текущее состояние сервера и логи; повторяйте выкладку только после такой сверки.
  • Лимит опроса — 60 запросов в минуту на ключ. Точное значение — в заголовке x-ratelimit-limit (потолок делится на реплики). Исход не меняется чаще, чем идёт сама выкладка, поэтому опрашивать раз в несколько секунд достаточно.

Смотрите также