Для 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_START_WINDOW |
УсловиеЧасы сервера расходятся с часами платформы: окно запуска вне допуска агента | Действие клиентаСверьте время на сервере и повторите запрос; при повторном отказе обратитесь в поддержку |
Код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 | Код | Описание |
|---|---|---|
| HTTP400 | КодINVALID_OPERATION_ID |
ОписаниеИдентификатор не той формы, которую выдаёт платформа |
| HTTP401 | КодMISSING_API_KEY |
ОписаниеНе передан заголовок X-Api-Key |
| HTTP401 | КодINVALID_API_KEY |
ОписаниеКлюч не опознан: такой строки на платформе нет |
| HTTP404 | КодOPERATION_NOT_FOUND |
ОписаниеОперации нет, она принадлежит другому ключу либо запись уже удалена |
| HTTP410 | КодOPERATION_OUTCOME_EXPIRED |
ОписаниеОперация ваша, но её исход больше не хранится |
| HTTP429 | КодRATE_LIMITED |
ОписаниеПревышен лимит опроса |
Полный список общих ошибок API — Ошибки.
Известные особенности
- Чужая операция отвечает так же, как несуществующая — это сделано намеренно. Все три случая (идентификатора нет, идентификатор чужой, запись удалена) дают один и тот же
404. Отдельный ответ на чужую операцию подтверждал бы, что такой идентификатор существует, а идентификатор сам по себе является ключом доступа к записи: подобрав его, можно было бы прочитать чужую выкладку. - 410 отличается от 404 только для владельца. «Исход больше не хранится» означает, что запись операции принадлежала вашему ключу, а окно исхода истекло через семь суток после терминального исхода. Это не доказывает запуск команды или выкладки: запись операции бывает и у отклонённой попытки. На чужой или выдуманный идентификатор такой ответ не приходит никогда.
- Запись живёт дольше, чем хранится исход. Через 7 суток исход перестаёт отдаваться, но сам факт операции сохраняется ещё некоторое время — за счёт этого и возможен различимый ответ
410. Потом запись удаляется, и тот же идентификатор начинает отвечать404. unknown— это ответ, а не ошибка. Запись операции существует, но из-за обрыва транспорта или процесса платформа не может доказать запуск команды или её исход. Сначала сверьте текущее состояние сервера и логи; повторяйте выкладку только после такой сверки.- Лимит опроса — 60 запросов в минуту на ключ. Действующее значение — в заголовке
x-ratelimit-limit. Исход не меняется чаще, чем идёт сама выкладка, поэтому опрашивать раз в несколько секунд достаточно. - Читайте через ключ, которому сейчас принадлежит операция. Ротация или передача ключа переносит метаданные операции на ключ-замену. Сохранённый вывод команды доступен только если этот ключ принадлежит пользователю-создателю: ротация того же владельца сохраняет доступ к выводу, а передача другому владельцу отдаёт только метаданные. Владелец сервера под ключом вне области операции и участник команды разработки с правом настроек читают метаданные через
GET /v1/infra/servers/:id/operations/:operationId.