[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"docs-applications\u002Fsolution-call-result":3,"docs-tabs-applications\u002Fsolution-call-result":6},{"content":4,"lastmod":5},"## Deliver the result of a long-running method\n\n`POST \u002Fv1\u002Fsolution-calls\u002F:callId\u002Fresult`\n\nAccepts the outcome of a long-running contract method call from the solution and takes care of delivering it to the Bitrix24 account. The solution receives the address of this route, a single-use reply token and the deadline as headers on the call itself, so it needs neither a platform key nor any extra setup.\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|-----------|------|:--------:|-------------|\n| `callId` (path) | string | yes | Call id from `X-Vibe-Call-Id`. It is simpler to take the ready-made address from `X-Vibe-Reply-Url` |\n| `Authorization` (header) | string | yes | `Bearer` with the `vcr_…` reply token from `X-Vibe-Reply-Token` of the same call. The token is not valid for any other address |\n\nA platform key `vibe_…` is not accepted on this route.\n\n## Request fields (body)\n\nThe body is JSON with one of two outcomes. The body is limited to 512 KiB, measured in raw bytes before parsing.\n\n| Field | Type | Required | Description |\n|-------|------|:--------:|-------------|\n| `outcome` | string | yes | `completed` — the method succeeded, `failed` — the method ended with an error |\n| `result` | object | with `completed` | The result per the `callbacks.result` schema of the contract version the call was accepted under. Publishing a new version after the call does not affect the validation |\n| `error` | object | no | With `failed` — the error description. An empty object is allowed too: the call still closes as `failed` |\n| `error.code` | string | no | The solution's error code matching `[A-Z0-9_]{1,64}`. A value that does not match the pattern is dropped |\n| `error.message` | string | no | Human-readable text, up to 500 characters. Longer text is truncated |\n| `error.problem` | object | no | Details per RFC 9457: `type`, `title`, `detail`, `instance`, each up to 500 characters. Other keys are dropped |\n\nBody with `failed`:\n\n```json\n{\n  \"outcome\": \"failed\",\n  \"error\": {\n    \"code\": \"INVOICE_LOCKED\",\n    \"message\": \"Invoice is locked by another user\",\n    \"problem\": {\n      \"type\": \"https:\u002F\u002Fexample.com\u002Fproblems\u002Finvoice-locked\",\n      \"title\": \"Invoice locked\",\n      \"detail\": \"Invoice 17 is being edited\",\n      \"instance\": \"\u002Finvoices\u002F17\"\n    }\n  }\n}\n```\n\n## Examples\n\nThe route has a single authorization axis — the reply token from `X-Vibe-Reply-Token`, so there is one example per language: a personal key and an application authorization key get `401 REPLY_TOKEN_INVALID`.\n\n### curl — reply token\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fsolution-calls\u002Fcall_01M31PGZ80V9GJMWJ5N48AATB2\u002Fresult\" \\\n  -H \"Authorization: Bearer vcr_YOUR_REPLY_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"outcome\": \"completed\", \"result\": {\"invoiceNumber\": \"A-17\"}}'\n```\n\n### JavaScript — reply token\n\n```javascript\n\u002F\u002F Headers of the incoming call — from the long-running method handler in the solution.\nconst replyUrl = req.headers['x-vibe-reply-url'];\nconst replyToken = req.headers['x-vibe-reply-token'];\n\nconst res = await fetch(replyUrl, {\n  method: 'POST',\n  headers: {\n    'Authorization': `Bearer ${replyToken}`,\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({ outcome: 'completed', result: { invoiceNumber: 'A-17' } }),\n});\n\nconst body = await res.json();\nif (!body.success) {\n  \u002F\u002F A 4xx closes the call: retrying the same result is pointless.\n  console.error(`result not accepted: ${body.error.code}`);\n}\n```\n\n## Response fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | Always `true` on success |\n| `data.state` | string | The state the call closed with: `completed` for `outcome: completed`, `failed` for `outcome: failed` |\n\n## Response example\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"state\": \"completed\"\n  }\n}\n```\n\n## Error response example\n\n422 — the body is over 512 KiB, the call is closed as `failed`:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"RESULT_INVALID\",\n    \"message\": \"The result body is over the boundary-contract limit; the call is closed as failed\",\n    \"errorCode\": \"SOLUTION_RESULT_TOO_LARGE\",\n    \"limitBytes\": 524288\n  }\n}\n```\n\nThe `error.errorCode` field names the reason — `SOLUTION_RESULT_TOO_LARGE` or `SOLUTION_BAD_RESPONSE` — and the outcome reaches the Bitrix24 account with the same code. The `error.limitBytes` field is present only when the size limit is exceeded. A `409 SOLUTION_CALL_CLOSED` carries the `error.state` field instead — the call state.\n\n## Errors\n\n| HTTP | Code | Description |\n|------|------|-------------|\n| 401 | `REPLY_TOKEN_INVALID` | `Authorization` has no `Bearer` with a token of the form `vcr_…`, including when a platform key is passed instead |\n| 401 | `REPLY_TOKEN_INVALID` | The token matches no call: a token from another call, a non-existent `callId`, or the call has already been removed from the platform after its retention period. Whether the `callId` exists is not disclosed |\n| 409 | `SOLUTION_CALL_CLOSED` | The call is already closed; `error.state` carries its state: `completed`, `failed`, `canceled` or `expired` |\n| 409 | `SOLUTION_CALL_CLOSED` | `error.state` is `accepted`: the call is open but held by a previous attempt to deliver the result that did not finish. Retry later — the platform releases such a call within two minutes |\n| 422 | `RESULT_INVALID` | `error.errorCode` is `SOLUTION_RESULT_TOO_LARGE`: the body is over 512 KiB, `error.limitBytes` is the limit in bytes. The call is closed as `failed` |\n| 422 | `RESULT_INVALID` | `error.errorCode` is `SOLUTION_BAD_RESPONSE`: the body is not JSON, `outcome` is neither `completed` nor `failed`, `result` is missing with `completed`, or `result` does not pass the contract's `callbacks.result` schema. The call is closed as `failed` |\n\nFull list of common API errors — [Errors](\u002Fdocs\u002Ferrors).\n\n## How a long-running call arrives\n\nThe platform invokes a contract method with `x-vibe-async: true` the same way as a regular one: an HTTP request to the solution's route from the contract, through the application tunnel. Five headers set a long-running call apart:\n\n| Header | What it carries |\n|--------|-----------------|\n| `Prefer` | `respond-async` — the platform does not wait for the result in the response to this request |\n| `X-Vibe-Call-Id` | Call id of the form `call_…`. The same id is part of the reply address |\n| `X-Vibe-Reply-Url` | The ready-made address of this route with the `callId` filled in: `https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fsolution-calls\u002Fcall_…\u002Fresult`. Take the address from the header instead of building it yourself |\n| `X-Vibe-Reply-Token` | Reply token of the form `vcr_…` — the only way to authenticate the result. Issued for one call |\n| `X-Vibe-Reply-Deadline` | Deadline in RFC 3339 format, for example `2026-09-21T10:10:00.000Z`. After it the call closes as `expired` and the result is no longer accepted |\n\nThe common contract-call headers arrive here as well: `X-Vibe-Request-Id`, `X-Vibe-Caller-Kind` with the value `contract`, `X-Vibe-Caller-Portal-Id`, `X-Vibe-Portal-User-Id`, `X-Vibe-Operation-Id`, `X-Vibe-Contract-Version` and `Idempotency-Key`. The `X-Vibe-Invocation-Context` header is present only on a call that was made with a context. A contract call carries no `X-Vibe-Caller-Key-Id` or `X-Vibe-Caller-App-Id` — it does not go through an external API key.\n\n**The reply headers exist only on a contract call.** When a business process action calls the solution through the [application external API](\u002Fdocs\u002Fapplications\u002Fexternal-api), `X-Vibe-Reply-Url`, `X-Vibe-Reply-Token` and `X-Vibe-Reply-Deadline` do not arrive. There the action sends the `X-Bitrix24-Event-Token` header, and the solution sends the result as a process event through `POST \u002Fv1\u002Fworkflows\u002Fevent`. The `{$request.header.X-Vibe-Reply-Url}` expression in the contract's `callbacks.result` refers to a header on this path. The result schema under it is shared by both channels: on the external API channel, `returnValues` is built from it. For the requirements for the key that sends the process event and a ready-made handler, see [A long-running method in a business process](\u002Fdocs\u002Fapplications\u002Flong-method).\n\nThe solution can answer such a call in two ways:\n\n| Solution response | What happens |\n|-------------------|--------------|\n| `202` | The platform treats the call as accepted and waits for the result on this route until the deadline from `X-Vibe-Reply-Deadline`. The response body is not read |\n| `200` with a body in the form of this route | The result is accepted right away, without a separate request: the body goes through the same validation as on this route, and the call closes with the outcome `completed` or `failed` |\n\nA `4xx` response closes the call as failed without a result — sending one later is pointless. Any other response (`5xx`, `3xx`, `2xx` other than `200` and `202`), a dropped connection or a timeout is treated by the platform as a transient failure. It retries the delivery at increasing intervals until the deadline from `X-Vibe-Reply-Deadline`, with the same `X-Vibe-Call-Id` and the same reply token, so this route accepts a result computed on the first attempt unconditionally.\n\n## Known specifics\n\n- **Any `4xx` closes the call for good.** A `422` is not an invitation to fix the body and retry: the call has already moved to `failed`, the outcome with the reason code has gone to the Bitrix24 account, and the same token answers `409` from then on. Validate `result` against the `callbacks.result` schema before sending — a fix in the solution's code helps the next call, not this one.\n- **Retry only after a `5xx` or a dropped connection.** The token is single-use per accepted outcome, not per attempt: until the platform has answered `2xx` or `4xx`, the result is not recorded, and a retry with the same address, token and body is mandatory — otherwise the step in the Bitrix24 account waits out the deadline and ends with a timeout. An unfinished attempt holds the call for one to two minutes, and during that time the answer is `409` with `state: accepted`.\n- **The reply token is the secret of one call.** Whoever holds it can close your call with any result. Do not write it to a log, do not show it in the interface and do not pass it on. The platform writes neither the token nor the result body to its own logs.\n- **After the deadline or a cancellation, the Bitrix24 account no longer needs the result.** A `409` with `state: expired` or `state: canceled` is the signal to stop the long-running work: there is no point continuing it, and there is nowhere left to accept its outcome.\n- **A `200` means \"the outcome is recorded\", not \"the Bitrix24 account has received it\".** The platform handles the delivery to the Bitrix24 account itself, with retries while the account is unavailable. The solution does not need to do anything for that.\n\n## See also\n\n- [Application external API](\u002Fdocs\u002Fapplications\u002Fexternal-api)\n- [A long-running method in a business process](\u002Fdocs\u002Fapplications\u002Flong-method)\n- [What the app receives](\u002Fdocs\u002Finfra\u002Fapp-runtime)\n- [Application catalog](\u002Fdocs\u002Fapplications)\n- [Errors](\u002Fdocs\u002Ferrors)\n","2026-10-16",{},1794739460573]