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