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

Отправить результат долгого метода

POST /v1/solution-calls/:callId/result

Принимает от решения исход вызова долгого метода по контракту и сам доставляет его на портал Битрикс24. Адрес этого маршрута, одноразовый токен ответа и срок решение получает заголовками вместе с самим вызовом, поэтому ни ключ платформы, ни отдельная настройка ему не нужны.

Как приходит долгий вызов

Метод контракта с x-vibe-async: true платформа вызывает так же, как обычный: HTTP-запросом на маршрут решения из контракта, по туннелю приложения. Долгий вызов отличают пять заголовков:

Заголовок Что несёт
Prefer respond-async — платформа не ждёт результат в ответе на этот запрос
X-Vibe-Call-Id Идентификатор вызова вида call_…. Он же стоит в адресе ответа
X-Vibe-Reply-Url Готовый адрес этого маршрута с подставленным callId: https://vibecode.bitrix24.tech/v1/solution-calls/call_…/result. Берите адрес из заголовка, а не собирайте сами
X-Vibe-Reply-Token Токен ответа вида vcr_… — единственный способ аутентифицировать результат. Выпускается на один вызов
X-Vibe-Reply-Deadline Срок в формате RFC 3339, например 2026-09-21T10:10:00.000Z. После него вызов закрывается как expired, и результат уже не принимается

Общие заголовки вызова по контракту приходят и здесь: X-Vibe-Request-Id, X-Vibe-Caller-Kind со значением contract, X-Vibe-Caller-Portal-Id, X-Vibe-Portal-User-Id, X-Vibe-Operation-Id, X-Vibe-Contract-Version и Idempotency-Key. Заголовок X-Vibe-Invocation-Context есть только у вызова с переданным контекстом. Заголовков X-Vibe-Caller-Key-Id и X-Vibe-Caller-App-Id у вызова по контракту нет — он идёт не по ключу внешнего API.

Ответить на такой вызов решение может двумя способами:

Ответ решения Что происходит
202 Платформа считает вызов принятым и ждёт результат на этом маршруте до срока из X-Vibe-Reply-Deadline. Тело ответа не читается
200 с телом по форме этого маршрута Результат принимается сразу, без отдельного запроса: тело проходит ту же проверку, что и на этом маршруте, и вызов закрывается исходом completed либо failed

Ответ 4xx закрывает вызов как неуспешный без результата — присылать его потом бессмысленно. Остальные ответы — 5xx, 3xx, 2xx кроме 200 и 202 — вместе с обрывом соединения и таймаутом платформа считает временным сбоем и повторяет доставку с нарастающей паузой до срока из X-Vibe-Reply-Deadline — с тем же X-Vibe-Call-Id и тем же токеном ответа, так что результат, посчитанный по первой попытке, этот маршрут принимает без оговорок.

Параметры

Параметр Тип Обяз. Описание
callId (path) string да Идентификатор вызова из X-Vibe-Call-Id. Проще взять готовый адрес из X-Vibe-Reply-Url

Аутентификация — заголовок Authorization: Bearer с токеном ответа из X-Vibe-Reply-Token того же вызова. Ключ платформы vibe_… на этом маршруте не принимается, а токен ответа не открывает ни одного другого адреса.

Поля запроса (body)

Тело — JSON с одним из двух исходов. Размер тела — до 512 КиБ, считается по сырым байтам до разбора.

Поле Тип Обяз. Описание
outcome string да completed — метод выполнен, failed — метод завершился ошибкой
result object при completed Результат по схеме callbacks.result той версии контракта, по которой вызов принят. Публикация новой версии после вызова на проверку не влияет
error object нет При failed — описание ошибки. Допустим и пустой объект: вызов всё равно закрывается как failed
error.code string нет Код ошибки решения по шаблону [A-Z0-9_]{1,64}. Значение вне шаблона отбрасывается
error.message string нет Текст для человека, до 500 символов. Лишнее обрезается
error.problem object нет Подробности по RFC 9457: type, title, detail, instance, каждое до 500 символов. Другие ключи отбрасываются

Тело при failed:

JSON
{
  "outcome": "failed",
  "error": {
    "code": "INVOICE_LOCKED",
    "message": "Invoice is locked by another user",
    "problem": {
      "type": "https://example.com/problems/invoice-locked",
      "title": "Invoice locked",
      "detail": "Invoice 17 is being edited",
      "instance": "/invoices/17"
    }
  }
}

Примеры

Ось авторизации у маршрута одна — токен ответа из X-Vibe-Reply-Token, поэтому пример один на язык: личный ключ и ключ авторизации приложения получают 401 REPLY_TOKEN_INVALID.

curl — токен ответа

Terminal
curl -X POST "https://vibecode.bitrix24.tech/v1/solution-calls/call_01M31PGZ80V9GJMWJ5N48AATB2/result" \
  -H "Authorization: Bearer YOUR_REPLY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"outcome": "completed", "result": {"invoiceNumber": "A-17"}}'

JavaScript — токен ответа

javascript
// Заголовки входящего вызова — из обработчика долгого метода в решении.
const replyUrl = req.headers['x-vibe-reply-url'];
const replyToken = req.headers['x-vibe-reply-token'];

const res = await fetch(replyUrl, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${replyToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ outcome: 'completed', result: { invoiceNumber: 'A-17' } }),
});

const body = await res.json();
if (!body.success) {
  // 4xx закрывает вызов: повторять тот же результат бессмысленно.
  console.error(`результат не принят: ${body.error.code}`);
}

Поля ответа

Поле Тип Описание
success boolean Всегда true при успехе
data.state string Состояние, в которое закрылся вызов: completed при outcome: completed, failed при outcome: failed

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

JSON
{
  "success": true,
  "data": {
    "state": "completed"
  }
}

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

422 — тело больше 512 КиБ, вызов закрыт как failed:

JSON
{
  "success": false,
  "error": {
    "code": "RESULT_INVALID",
    "message": "The result body is over the boundary-contract limit; the call is closed as failed",
    "errorCode": "SOLUTION_RESULT_TOO_LARGE",
    "limitBytes": 524288
  }
}

Поле error.errorCode называет причину — SOLUTION_RESULT_TOO_LARGE или SOLUTION_BAD_RESPONSE, и с тем же кодом исход уходит на портал. Поле error.limitBytes приходит только при превышении размера. У 409 SOLUTION_CALL_CLOSED вместо них поле error.state — состояние вызова.

Ошибки

HTTP Код Описание
401 REPLY_TOKEN_INVALID В Authorization нет Bearer с токеном вида vcr_…, в том числе когда вместо него передан ключ платформы
401 REPLY_TOKEN_INVALID Токен не подходит ни к одному вызову: чужой токен, несуществующий callId или вызов уже удалён с платформы по сроку хранения. Существует ли callId, ответ не раскрывает
409 SOLUTION_CALL_CLOSED Вызов уже закрыт, error.state несёт его состояние: completed, failed, canceled или expired
409 SOLUTION_CALL_CLOSED error.state равен accepted: вызов открыт, но занят предыдущей попыткой отправить результат, которая не завершилась. Повторите позже — платформа отпускает такой вызов в пределах двух минут
422 RESULT_INVALID error.errorCode равен SOLUTION_RESULT_TOO_LARGE: тело больше 512 КиБ, error.limitBytes — предел в байтах. Вызов закрыт как failed
422 RESULT_INVALID error.errorCode равен SOLUTION_BAD_RESPONSE: тело не JSON, outcome вне completed и failed, при completed нет result либо result не проходит схему callbacks.result контракта. Вызов закрыт как failed

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

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

  • Любой 4xx закрывает вызов окончательно. 422 — не приглашение исправить тело и повторить: вызов уже переведён в failed, исход с кодом причины ушёл на портал, и тот же токен дальше отвечает 409. Проверяйте result по схеме callbacks.result до отправки — правка кода решения поможет следующему вызову, а не этому.
  • Повторять нужно только после 5xx и обрыва соединения. Токен одноразов на принятый исход, а не на попытку: пока платформа не ответила 2xx или 4xx, результат не записан, и повтор с тем же адресом, токеном и телом обязателен — иначе шаг на портале дождётся срока и завершится по таймауту. Незавершённая попытка держит вызов одну-две минуты, и на это время ответ — 409 со state: accepted.
  • Токен ответа — секрет одного вызова. Тот, у кого он есть, закроет ваш вызов любым результатом. Не пишите его в журнал, не показывайте в интерфейсе и не передавайте дальше. Платформа в свои журналы не пишет ни токен, ни тело результата.
  • После срока или отмены результат порталу не нужен. 409 со state: expired или state: canceled — сигнал прервать долгую работу: продолжать её незачем, а её итог уже некуда принять.
  • Ответ 200 означает «исход записан», а не «портал его получил». Доставку на портал платформа ведёт сама, с повторами при недоступности портала. Решению для этого ничего делать не нужно.

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