Для AI-агентов: markdown этой страницы — /docs-content/infra/deploy/operation-status.md индекс документации — /llms.txt
Исход операции
GET /v1/infra/operations/:operationId
Возвращает исход конкретного запуска по его идентификатору. Ручка обслуживает два вида операций — выкладку POST /deploy и команду POST /exec. Нужна, когда соединение оборвалось и терминальный ответ до клиента не дошёл: по идентификатору можно узнать, чем всё кончилось, вместо того чтобы запускать вызов заново.
Для команды это единственный способ узнать исход после обрыва. Транспортный таймаут прекращает только ожидание на стороне клиента, сама команда на сервере продолжает выполняться и может завершиться уже после того, как клиент получил ошибку. Поэтому повторный запуск изменяющей команды может применить изменение второй раз.
Ручка только на чтение, побочных действий у неё нет. Каждый запуск адресуется отдельно, включая предыдущие попытки на том же сервере. Исход хранится 7 суток.
Идентификатор есть у обоих видов выкладки, но момент его выдачи различается:
- для отдельной виртуальной машины (
kind: "STANDALONE") запись заводится до конвейера. Если машина спит, во время блокирующего пробуждения строка уже видна какrunningв списке операций сервера, но HTTP-статус, заголовок и SSE-кадр по этому соединению не приходят до завершения wake. Заголовок и первый SSE-кадр приходят до первого шага выкладки; - для galaxy-приложения (
kind: "GALAXY_APP") запись заводится после захвата mutex приложения, при входе в первый шаг. Идентификатор приходит только с терминальным JSON-ответом в заголовке и теле. Если соединение оборвалось раньше, найдите запуск через список операций сервера.
У команды POST /exec запись заводится после захвата блокировки сервера, до отправки агенту. В длительном режиме async=true только STANDALONE возвращает идентификатор в заголовке и теле немедленного 202 после ограниченного ожидания подтверждения запуска; команда продолжает работу независимо. Синхронный exec отдельной машины отправляет заголовок до ожидания завершения. Синхронный exec galaxy-приложения — только с терминальным ответом: при обрыве ожидания идентификатор может не прийти, тогда найдите запуск через список операций сервера с ?kind=exec.
Где взять идентификатор — три канала, все три несёт один и тот же запуск:
- заголовок ответа
X-Vibe-Operation-Id— у команды и у выкладки на уже доступной отдельной машине приходит сразу, у выкладки на спящей отдельной машине задерживается до конца блокирующего пробуждения, у синхронного exec и выкладки galaxy-приложения приходит только с терминальным ответом; длительный exec сasync=trueвозвращает его с202только наSTANDALONE, - первый кадр
event: operationпри?stream=true— у синхронного exec и у выкладки на отдельной машине, но только после возможного блокирующего пробуждения. У длительного exec SSE нет. БраузерныйEventSourceзаголовков ответа не отдаёт, поэтому для него это единственный доконвейерный канал, - поле
operationIdв теле —data.operationIdпри успехе,error.operationIdпри отказе.
Отсутствие идентификатора не означает, что выкладка не запускалась. Связь со спящей отдельной машиной могла оборваться во время блокирующего пробуждения, связь с galaxy могла оборваться до терминального ответа, либо платформа могла не суметь завести запись и продолжить без идентификатора. Не повторяйте выкладку вслепую: сначала запросите GET /v1/infra/servers/:id/operations, затем сверьте состояние сервера и логи.
Длительный exec доступен только отдельной машине. async=true принимает таймаут от 1 до 86400 секунд на STANDALONE. Другие виды сервера получают 400 DURABLE_EXEC_UNSUPPORTED_SERVER_KIND до блокировки, создания операции и отправки команды, без перехода к синхронному исполнению. Exec Galaxy остаётся синхронным с пределом 600 секунд. Работающая длительная задача отдельной машины занимает канал после 202; опрашивайте эту операцию вместо повторного запуска. Неизвестный исход требует проверки и подтверждения остановки. /unstick и удаление лока не снимают ограничение приёма длительной задачи.
Параметры
| Параметр | В | Тип | Обяз. | Описание |
|---|---|---|---|---|
operationId |
path | string | да | Идентификатор операции из ответа POST /deploy или POST /exec |
Примеры
curl — личный ключ
curl -H "X-Api-Key: YOUR_API_KEY" \
https://vibecode.bitrix24.tech/v1/infra/operations/OPERATION_ID
curl — OAuth-приложение
curl -H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
https://vibecode.bitrix24.tech/v1/infra/operations/OPERATION_ID
JavaScript — личный ключ
// Выкладка: после возможного блокирующего wake забираем id из заголовка ДО тела.
// Если связь оборвалась во время wake, ищем id через список операций сервера.
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-приложение
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.stdout |
string (необяз.) | Сохранённый стандартный вывод длительного exec, включая частичный вывод при unknown, если он доступен; только для ключа в области операции, принадлежащего пользователю-создателю |
data.stderr |
string (необяз.) | Сохранённый поток ошибок длительного exec; та же приватная область, что у stdout |
data.duration |
number (необяз.) | Сохранённая длительность exec в миллисекундах; та же приватная область, что у stdout |
data.truncated |
boolean (необяз.) | Достигнут ли общий предел сохранённого stdout+stderr 1 МиБ; та же приватная область, что у stdout |
data.startedAt |
string | Время открытия записи операции, ISO 8601. Для длительного exec не подтверждает запуск процесса |
data.finishedAt |
string | null | Время завершения, ISO 8601. null, пока операция идёт |
data.error |
object | null | { code, message } при status: "failed", иначе null |
Значения data.status:
| Значение | Что означает |
|---|---|
running |
Терминальный исход операции ещё не получен. Длительная команда может выполняться или ожидать подтверждения запуска, в том числе после потери ACK; статус не доказывает, что процесс запущен. Опрашивайте ту же операцию, не отправляйте команду повторно |
succeeded |
У выкладки означает, что она завершилась успешно. У команды означает, что она отработала до конца, а её собственный вердикт лежит в data.exitCode — ненулевой код возврата приходит с этим же статусом, потому что статус отвечает на вопрос «выполнилась», а не «с каким результатом» |
failed |
Причина — в data.error. У выкладки означает, что она провалилась. У команды означает, что она не запускалась: этот статус ставится, только когда платформа может это доказать, — например при EXEC_BUSY, когда агент был занят другой командой. Это доказывает только отказ этой попытке; при EXEC_BUSY до повтора сверьте занявшую канал операцию и прежние попытки с потерянным подтверждением |
unknown |
Платформа не может доказать удалённый исход: транспорт оборвался либо процесс прервался между стартом и записью исхода. Запись операции существует, но она не доказывает ни запуск команды, ни её остановку. Для команды это означает, что она может выполняться на сервере прямо сейчас, поэтому перед повторным запуском изменяющей команды сверьтесь с состоянием сервера в GET /v1/infra/servers/:id и журналом |
Судите по паре data.exitCode + data.status, а не по одному полю. Число приходит только у succeeded и означает, что команда отработала до конца. Само по себе null не означает «исход не доказан»: у succeeded оно означает, что команда отработала, а кода агент не назвал, — повторять её не нужно; у failed — что эта попытка не запускалась; при EXEC_BUSY до повтора всё равно нужно сверить занявшую канал операцию; у running — что попытка ожидает завершения или подтверждения запуска, поэтому опрашивайте ту же операцию; у unknown — что исход не доказан.
Что хранится, а что нет. Запись синхронной команды несёт исход попытки — статус, код возврата и причину отказа — без stdout/stderr. Для длительной команды с зашифрованным сохранённым выводом используйте async=true. Эта ручка отдаёт вывод только когда текущий ключ в области операции принадлежит пользователю-создателю. Ротация ключа того же владельца сохраняет доступ; передача другому владельцу переносит метаданные операции, но не доступ к её приватному выводу. Записи без сохранённой личности создателя отдают только метаданные. Чтение по серверу никогда не отдаёт вывод команды.
Коды отказа длительного exec
Для kind: "exec" и status: "failed" доказано, что команда не запускалась. data.error.code может обозначать отказ приёма, уже возвращённый POST, или отказ до запуска, обнаруженный после HTTP 202. HTTP-статус этого GET остаётся 200; коды описывают записанную попытку команды, а не сбой чтения статуса. Подтвердите статус операции до нового исправленного запроса. При EXEC_BUSY прочитайте список операций сервера с kind=exec и сверьте занявшую канал попытку, включая потерянные подтверждения. Известную выполняющуюся операцию дождитесь; неизвестный исход требует проверки состояния и журналов либо обращения в поддержку вместо нового exec.
| Код | Условие | Действие клиента |
|---|---|---|
EXEC_JOB_STORAGE_UNAVAILABLE |
Не удалось сохранить операцию или её адрес назначения | Проверьте операцию, если получен идентификатор; дождитесь доступности хранения до повтора |
EXEC_BUSY |
Приём исполнения занят | Дождитесь известной работающей задачи; при неизвестном исходе обратитесь в поддержку |
EXEC_JOB_CAPACITY |
Хост уже хранит 64 записи задач | Дождитесь допустимой очистки по сроку хранения или обратитесь в поддержку; автоматического повтора нет |
EXEC_JOB_UNAVAILABLE |
Недоступны приём, хранение или запуск длительных задач | Устраните недоступность с поддержкой; не заменяйте запрос синхронным исполнением |
EXEC_JOB_INVALID |
Агент отклонил данные или окно запуска | Проверьте записанный отказ и запрос; при отказе корректному запросу обратитесь в поддержку |
EXEC_JOB_REJECTED |
Приём отклонён без отдельного кода | Обратитесь в поддержку |
UNKNOWN_TYPE |
Исполнение не поддерживает длительные задачи | Обратитесь в поддержку для обновления поддержки исполнения; синхронного перехода нет |
EXEC_JOB_START_EXPIRED |
Окно запуска истекло до старта пользовательской команды | Новый запуск безопасен после подтверждения этого отказа; при повторном отказе обратитесь в поддержку |
EXEC_WORKDIR_DENIED |
Рабочая директория нарушает правила пути | Выберите разрешённую директорию; правила описаны в exec |
EXEC_WORKDIR_NOT_FOUND |
Рабочая директория отсутствует или недоступна для проверки | Укажите существующую доступную директорию |
EXEC_WORKDIR_NOT_DIR |
Рабочий путь не является директорией | Укажите директорию |
EXEC_FAILED |
Не удалось запустить интерпретатор команды отдельной машины | Проверьте окружение и обратитесь в поддержку до нового запуска |
Истёкший таймаут или потеря процесса отдельной машины могут дать status: "unknown", частичный сохранённый вывод и data.error: null. Это не отказ до запуска и не разрешение на новый exec. Сверьте состояние и логи, подтвердите остановку исполнения и при необходимости обратитесь в поддержку. TTL, разрыв туннеля, /unstick, force и удаление лока не снимают ограничение приёма длительной задачи.
Пример ответа
Выкладка идёт:
{
"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
}
}
Выкладка завершилась успешно:
{
"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
}
}
Выкладка провалилась:
{
"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"
}
}
}
Длительная команда отработала до конца; сохранённые поля показаны создателю через ключ в области операции:
{
"success": true,
"data": {
"operationId": "clz9k2m4x0001qw8h3f7d2n5q",
"kind": "exec",
"serverId": "8f14e45f-ceea-467a-9c8d-2f1c5a6b7e30",
"status": "succeeded",
"step": null,
"exitCode": 0,
"stdout": "maintenance complete\n",
"stderr": "",
"duration": 275000,
"truncated": false,
"startedAt": "2026-08-11T09:20:02.004Z",
"finishedAt": "2026-08-11T09:24:37.815Z",
"error": null
}
}
Ожидание команды оборвалось, исход неизвестен:
{
"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 — операция была, но исход уже не хранится:
{
"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 суток исход перестаёт отдаваться, но сам факт операции сохраняется ещё некоторое время — за счёт этого и возможен различимый ответ
410. Потом запись удаляется, и тот же идентификатор начинает отвечать404. unknown— это ответ, а не ошибка. Запись операции существует, но из-за обрыва транспорта или процесса платформа не может доказать запуск команды или её исход. Сначала сверьте текущее состояние сервера и логи; повторяйте выкладку только после такой сверки.- Лимит опроса — 60 запросов в минуту на ключ. Точное значение — в заголовке
x-ratelimit-limit(потолок делится на реплики). Исход не меняется чаще, чем идёт сама выкладка, поэтому опрашивать раз в несколько секунд достаточно. - Читайте через ключ, которому сейчас принадлежит операция. Ротация или передача ключа переносит метаданные операции на ключ-замену. Сохранённый вывод команды доступен только если этот ключ принадлежит пользователю-создателю: ротация того же владельца сохраняет доступ к выводу, а передача другому владельцу отдаёт только метаданные. Владелец сервера под ключом вне области операции и участник команды разработки с правом настроек читают метаданные через
GET /v1/infra/servers/:id/operations/:operationId.