For AI agents: markdown of this page — /docs-content-en/applications/long-method.md documentation index — /llms.txt
A long-running method in a business process
A recipe for an application whose work does not fit inside the call window: the application takes the job from a paused business-process activity in Bitrix24, answers straight away and delivers the result in a separate request once it is done.
Base URL: https://vibecode.bitrix24.com/v1 | Authorization: X-Api-Key | Reply scope: bizproc
When you need it | What you will need | How the solution works | Step 1 | Step 2 | Step 3 | The reply key | Errors | Limits | Full code
The step examples show individual calls and rely on variables declared earlier in the scenario. A handler ready to run is in the Full code section.
When you need it
A call through the application external API lives for 30 seconds — that is the overall limit, past which the caller gets 503 APP_API_TIMEOUT. The application itself has to start answering earlier: the platform stops waiting for its response at the twenty-fifth second and returns 503 APP_API_UNAVAILABLE to the caller.
Work that does not fit inside those windows — a computation over a large selection, a request to an external system, document generation, waiting for a person to answer — needs a different shape. The application takes the job, answers the automation rule straight away and delivers the result once it is ready. The business process sleeps on its step for all that time, and the Bitrix24 account keeps its state.
A long-running method under a solution contract works differently — there the reply address and a single-use token arrive as call headers, and the platform delivers the outcome to the Bitrix24 account itself. This recipe is about a business process, where the reply is up to the solution: Deliver the result of a long-running method.
What you will need
- An application with the external API switched on and a key issued. The switch and the key issuance live in the application card, and the conditions and refusals are described on the Application external API page. The business process calls the application with that key.
- A platform key for the reply —
vibe_api_…with thebizprocscope, in read-and-write mode, bound to the same Bitrix24 account. You create it yourself and hand it to the application through an environment variable — the platform does not issue it. The requirements are covered in The reply key. - A pausable business-process activity in the Bitrix24 account. It calls the application, puts its own
eventTokeninto the request body and falls asleep until the event. Registering such an activity —/v1/bizproc-activities. - Node.js 18 or newer for the examples.
In every example $VIBE_API_KEY is the platform reply key from item 2, and YOUR_APP_EXTERNAL_API_KEY is the external API key from item 1.
How the solution works
- The business-process activity calls the application at the external API address and passes its
eventToken— the single-use key of its own step — in the request body. - The application stores the
eventTokentogether with the job and answers202within the call window. The activity sleeps after that. - While the work runs, the application writes into the process log through
POST /v1/workflows/activity-log. That does not change the state of the process. - Once finished, the application calls
POST /v1/workflows/eventwith the sameeventTokenand the values for the activity's output parameters. The process wakes up and moves on.
The application needs no Bitrix24 credentials at any step: the platform reaches the account with the stored credentials of the key owner.
Step 1. Take the call and answer straight away
The business-process automation rule sends a request to the application address:
Address: https://vibecode.bitrix24.com/v1/applications/:applicationId/api/<your-path>
Method: POST
Header: X-Api-Key: YOUR_APP_EXTERNAL_API_KEY
In the body the activity passes its eventToken and everything the application needs for the work. The activity puts the field there, not the platform — the author of the activity chooses its name, and in the examples below it is called eventToken.
On top of the body, the platform adds headers the caller cannot forge — incoming names with the X-Vibe- prefix are stripped:
| Header | What it carries |
|---|---|
X-Vibe-Request-Id |
Call ID. The platform writes the same ID into its own log |
X-Vibe-Caller-Kind |
The value external-api — the call arrived through this channel rather than from the Bitrix24 interface |
X-Vibe-Caller-Portal-Id |
The Bitrix24 account the calling key belongs to |
X-Vibe-Caller-Key-Id |
ID of the external API key the call was made with |
There are no headers with a reply address, a reply token or a deadline on this path — those belong to a solution-contract call. Nor are there headers carrying an employee identity: an external call carries no session. The full account of what reaches the application — What the application receives.
A 202 response tells the automation rule that the job is accepted. The response body is up to the application: the automation rule puts top-level values into business-process variables.
JavaScript
// The application route handler that the business-process activity calls.
app.post('/long-method', async (req, res) => {
const { eventToken, dealId } = req.body;
if (!eventToken) {
res.status(400).json({ accepted: false, reason: 'EVENT_TOKEN_REQUIRED' });
return;
}
// Put the job on a queue and hand control back AT ONCE: the answer has to
// leave within the call window, and the work will take longer.
queue.push({ eventToken, dealId });
res.status(202).json({ accepted: true });
});
Step 2. Write an interim step into the process log
While the work runs, the application reports into the execution log of the business process. The entry is visible to whoever looks at the process in the Bitrix24 account, and it does not change the state of the process — the activity stays paused.
cURL
curl -X POST https://vibecode.bitrix24.com/v1/workflows/activity-log \
-H "X-Api-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"eventToken":"xxx.yyy.zzz","logMessage":"Selection collected, computing the discount"}'
JavaScript
await fetch('https://vibecode.bitrix24.com/v1/workflows/activity-log', {
method: 'POST',
headers: {
'X-Api-Key': process.env.VIBE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
eventToken,
logMessage: 'Selection collected, computing the discount',
}),
});
{
"success": true,
"data": true
}
The full description of the fields and refusals — Write to the process log.
Step 3. Send the result into the process
The work is done — the application sends an event with the same eventToken. The returnValues object lands in the activity's output parameters, and the business process carries on.
| Field | Type | Required | Description |
|---|---|---|---|
eventToken |
string | yes | The very token that arrived in the call body at step 1 |
returnValues |
object | no | Values of the activity's output parameters. Defaults to an empty object |
logMessage |
string | no | An entry in the process execution log alongside the event |
cURL
curl -X POST https://vibecode.bitrix24.com/v1/workflows/event \
-H "X-Api-Key: $VIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"eventToken":"xxx.yyy.zzz","returnValues":{"discount":12.5,"verdict":"approve"},"logMessage":"Discount computed"}'
JavaScript
const res = await fetch('https://vibecode.bitrix24.com/v1/workflows/event', {
method: 'POST',
headers: {
'X-Api-Key': process.env.VIBE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
eventToken,
returnValues: { discount: 12.5, verdict: 'approve' },
logMessage: 'Discount computed',
}),
});
const body = await res.json();
if (!body.success) {
console.error(`the event was not accepted: ${body.error.code}`);
}
{
"success": true,
"data": true
}
The data field set to true means Bitrix24 accepted the event and the process resumed. The full description of the fields and refusals — Send an event to a process.
The reply key
Answer with your own platform key, not with the key the application was called with. The external API key is locked to an allow-list of routes: exactly one platform address is open to it — the pass-through into the application. On POST /v1/workflows/event and POST /v1/workflows/activity-log it gets 403 APP_API_KEY_OUT_OF_SCOPE before the handler, and no setting changes that.
A platform key vibe_api_… works here — the same one the solution already uses to call the Vibecode API. The platform neither issues such a key nor injects it into the application. A person creates it in the API Keys section and hands it to the application through an environment variable at deploy time. The variable name is yours to choose, VIBE_API_KEY by convention: the application reads it from its own environment and sends the value in the X-Api-Key header. There is no key created automatically "for the server", and a 401 MISSING_API_KEY from the application means the variable never arrived. Three requirements apply to the key:
| Requirement | What happens otherwise |
|---|---|
The bizproc scope |
403 SCOPE_DENIED |
| Read-and-write mode | 403 WRITE_BLOCKED_READONLY_KEY. The response carries the address of the page where the mode is switched |
| A bound Bitrix24 account | 401 TOKEN_MISSING. An application authorization key vibe_app_… presented without an employee session token gets the same answer: its account credentials live in the session rather than on the key |
The reply reaches Bitrix24 with the rights of the key owner, not of the employee who started the business process. The platform performs the event and the log entry with the owner's stored credentials, so choose the key owner deliberately: they need rights over the processes the solution answers.
The key's current scopes and the state of its account binding are shown by GET /v1/me. Where the key mode is switched — Keys and authorization.
Errors
The table covers both reply addresses — POST /v1/workflows/event and POST /v1/workflows/activity-log. The refusals of the pass-through call at step 1 are in the table on Application external API.
| HTTP | Code | Description |
|---|---|---|
| 400 | MISSING_PARAMS |
eventToken not provided. On the log write, a request without logMessage gets the same answer |
| 401 | MISSING_API_KEY |
The header with the key was not provided |
| 401 | INVALID_API_KEY |
The key is not recognized — no such key exists on the platform |
| 401 | TOKEN_MISSING |
The key has no portal binding. The response text names the reason and what to do about it |
| 403 | SCOPE_DENIED |
The key is missing the bizproc scope. Checked first — before the key mode |
| 403 | WRITE_BLOCKED_READONLY_KEY |
The key is in read-only mode. The details.switchUrl field names the page where the mode is changed |
| 403 | APP_API_KEY_OUT_OF_SCOPE |
The reply was sent with an application external API key. Only the pass-through into the application is open to that key |
| 403 | BITRIX_ACCESS_DENIED |
Bitrix24 rejected the request: the token is invalid, expired, or already used |
| 422 | BITRIX_ERROR |
Error on the Bitrix24 side |
| 429 | RATE_LIMITED |
Request rate limit exceeded. Retry in 1–2 seconds |
| 502 | BITRIX_UNAVAILABLE |
Bitrix24 is unavailable |
Full list of common API errors — Errors.
Error response example
403 — the key is in read-only mode:
{
"success": false,
"error": {
"code": "WRITE_BLOCKED_READONLY_KEY",
"message": "Key is in read-only mode. Switch to read+write in /keys to enable writes.",
"details": {
"method": "bizproc.event.send",
"keyName": "Solution deploy key",
"currentMode": "READONLY",
"switchUrl": "/keys"
}
}
}
Limits
| Limit | Value |
|---|---|
| The application's answer window at step 1 | 25 seconds for the application to answer, inside an overall call limit of 30 seconds |
eventToken lifetime |
The platform does not limit it and passes the token to Bitrix24 as is. An invalid token is rejected by Bitrix24 — 403 BITRIX_ACCESS_DENIED |
Reusing an eventToken |
The token is single-use: every pause of the process issues a new one, and after POST /v1/workflows/event the previous one is invalid |
| Log entries per step | The platform does not limit them. Every entry is a separate API call and counts against the common limits |
Full code
The handler takes the activity's call, answers 202 and finishes the job in the background, reporting into the process log. Every variable is declared inside it, and it runs as is on Node.js 18 and newer.
import express from 'express';
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true })); // the automation rule sends form fields, not JSON
const VIBE_URL = 'https://vibecode.bitrix24.com/v1';
const VIBE_API_KEY = process.env.VIBE_API_KEY;
async function callVibe(path, body) {
const res = await fetch(`${VIBE_URL}${path}`, {
method: 'POST',
headers: {
'X-Api-Key': VIBE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const payload = await res.json();
if (!payload.success) {
throw new Error(`${path}: ${payload.error.code} — ${payload.error.message}`);
}
return payload.data;
}
async function runLongWork(eventToken, dealId) {
try {
await callVibe('/workflows/activity-log', {
eventToken,
logMessage: `Computing the discount for deal ${dealId}`,
});
const discount = await computeDiscount(dealId); // your long-running work
await callVibe('/workflows/event', {
eventToken,
returnValues: { discount, verdict: discount > 0 ? 'approve' : 'reject' },
logMessage: 'Discount computed',
});
} catch (err) {
// The process sleeps and waits for an event. A failure has to come back as an
// event too, otherwise the step stays paused until the deadline set in Bitrix24.
await callVibe('/workflows/event', {
eventToken,
returnValues: { discount: 0, verdict: 'error' },
logMessage: `The computation failed: ${err.message}`,
}).catch(() => {});
}
}
app.post('/long-method', (req, res) => {
const { eventToken, dealId } = req.body;
if (!eventToken || !dealId) {
res.status(400).json({ accepted: false, reason: 'EVENT_TOKEN_AND_DEAL_REQUIRED' });
return;
}
// The answer leaves AT ONCE, the work runs after it.
res.status(202).json({ accepted: true });
void runLongWork(eventToken, String(dealId));
});
async function computeDiscount(dealId) {
await new Promise((resolve) => setTimeout(resolve, 60_000));
return 12.5;
}
app.listen(process.env.PORT || 3000);
Known specifics
- A failure comes back as an event too. The paused activity waits for
POST /v1/workflows/eventand knows nothing about the application having crashed. While no event arrives, the step stays paused. So a failed computation answers with an event carrying a refusal marker inreturnValuesrather than with silence. - A
202at step 1 does not guarantee the work has started. The automation rule gets an acknowledgement of receipt, and from there the only signal about progress is the entries in the process log. Write an entry on every meaningful boundary: unpicking a stuck step from the process log is cheaper than from the application logs. - The reply key is not kept next to the calling key. The external API key sits in the automation rule settings in the Bitrix24 account, the reply key sits in the application's environment variables. Revoking one leaves the other alone, and reissuing the external API key does not break replies already in flight.
- One
eventTokencloses one step. The token becomes invalid right afterPOST /v1/workflows/event, and an already-used one is rejected by Bitrix24 withBITRIX_ACCESS_DENIED. So a blind retry after a dropped connection is unsafe: the same code arrives both when the first request got through and when the token was spoiled, and the response gives you nothing to tell the two apart. - The scope is checked before the key mode. A key without the
bizprocscope and in read-only mode gets403 SCOPE_DENIEDrather than a mode refusal. Open the scope first, then look at the mode.