لوكلاء AI: ملف markdown لهذه الصفحة — /docs-content-en/applications/long-method.md فهرس التوثيق — /llms.txt

مقالات التوثيق متاحة حاليًا باللغة الإنجليزية فقط.

A long-running method in a business process

Difficulty: advanced | Scopes: bizproc | Stack: cURL / JavaScript (Node.js, Express)

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. 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 Bitrix24 itself: Deliver the result of a long-running method.

What you will need

  1. 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.
  2. A platform key for the reply — vibe_api_… with the bizproc scope, in read-and-write mode, bound to the same Bitrix24 account. The requirements are covered below.
  3. A pausable business-process activity in the Bitrix24 account. It calls the application, puts its own eventToken into the request body and falls asleep until the event. Registering such an activity — /v1/bizproc-activities.
  4. Node.js 18 or newer for the examples.

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 — forwarding the call to 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
RequirementThe bizproc scope What happens otherwise403 SCOPE_DENIED
RequirementRead-and-write mode What happens otherwise403 WRITE_BLOCKED_READONLY_KEY. The response carries the address of the page where the mode is switched
RequirementA bound Bitrix24 account What happens otherwise401 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.

In every example $VIBE_URL is the base address https://vibecode.bitrix24.com, $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

  1. 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.
  2. The application stores the eventToken together with the job and answers 202 within the call window. The activity sleeps after that.
  3. 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.
  4. Once finished, the application calls POST /v1/workflows/event with the same eventToken and 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.

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.

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
HeaderX-Vibe-Request-Id What it carriesCall ID. The platform writes the same ID into its own log
HeaderX-Vibe-Caller-Kind What it carriesThe value external-api — the call arrived through this channel rather than from the Bitrix24 interface
HeaderX-Vibe-Caller-Portal-Id What it carriesThe Bitrix24 account the calling key belongs to
HeaderX-Vibe-Caller-Key-Id What it carriesID 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. A full breakdown 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

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

Terminal
curl -X POST "$VIBE_URL/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

javascript
await fetch(`${VIBE_URL}/v1/workflows/activity-log`, {
  method: 'POST',
  headers: {
    'X-Api-Key': VIBE_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    eventToken,
    logMessage: 'Selection collected, computing the discount',
  }),
});
JSON
{
  "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
FieldeventToken Typestring Requiredyes DescriptionThe very token that arrived in the call body at step 1
FieldreturnValues Typeobject Requiredno DescriptionValues of the activity's output parameters. Defaults to an empty object
FieldlogMessage Typestring Requiredno DescriptionAn entry in the process execution log alongside the event

cURL

Terminal
curl -X POST "$VIBE_URL/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

javascript
const res = await fetch(`${VIBE_URL}/v1/workflows/event`, {
  method: 'POST',
  headers: {
    'X-Api-Key': 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}`);
}
JSON
{
  "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.

Limits

The call window at step 1. 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. Computing over a large selection, calling an external system, generating a document or waiting for a person to answer does not fit inside those windows — so the application answers 202 and finishes the work after the answer.

A 202 at 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 at every meaningful boundary: diagnosing a stuck step from the process log is cheaper than from the application logs. The platform does not limit the number of entries per step, and every entry is a separate API call that counts against the common limits.

A failure comes back as an event too. The paused activity waits for POST /v1/workflows/event and 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 failure flag in returnValues rather than with silence.

One eventToken closes one step. Every pause of the process issues a new token, and right after POST /v1/workflows/event the previous one is invalid. The platform does not limit the token's lifetime and passes it to Bitrix24 as is, and an invalid or already-used token is rejected by Bitrix24 with 403 BITRIX_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 corrupted, and the response gives you nothing to tell the two apart.

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.

The scope is checked before the key mode. A key without the bizproc scope and in read-only mode gets 403 SCOPE_DENIED rather than a mode refusal. Open the scope first, then look at the mode.

Reply refusals. The table covers both reply addresses — POST /v1/workflows/event and POST /v1/workflows/activity-log. The refusals of the application call itself at step 1 are in the table on Application external API.

HTTP Code Description
HTTP400 CodeMISSING_PARAMS DescriptioneventToken not provided. On the log write, a request without logMessage gets the same answer
HTTP401 CodeMISSING_API_KEY DescriptionThe header with the key was not provided
HTTP401 CodeINVALID_API_KEY DescriptionThe key is not recognized — no such key exists on the platform
HTTP401 CodeTOKEN_MISSING DescriptionThe key has no Bitrix24 account binding. The response text names the reason and what to do about it
HTTP403 CodeSCOPE_DENIED DescriptionThe key is missing the bizproc scope. Checked first — before the key mode
HTTP403 CodeWRITE_BLOCKED_READONLY_KEY DescriptionThe key is in read-only mode. The details.switchUrl field names the page where the mode is changed
HTTP403 CodeAPP_API_KEY_OUT_OF_SCOPE DescriptionThe reply was sent with an application external API key. That key may only forward calls to the application
HTTP403 CodeBITRIX_ACCESS_DENIED DescriptionBitrix24 rejected the request: the token is invalid, expired, or already used
HTTP409 CodeBIZPROC_MODULE_NOT_ENABLED DescriptionBusiness processes are not available on the Bitrix24 account: the module is not part of the plan or is switched off in the account settings. Ask a Bitrix24 administrator to enable business processes — until then both addresses give the same answer however many times you retry
HTTP422 CodeBITRIX_ERROR DescriptionError on the Bitrix24 side
HTTP429 CodeRATE_LIMITED DescriptionRequest rate limit exceeded. Retry in 1–2 seconds
HTTP502 CodeBITRIX_UNAVAILABLE DescriptionBitrix24 is unavailable

403 — the key is in read-only mode:

JSON
{
  "success": false,
  "error": {
    "code": "WRITE_BLOCKED_READONLY_KEY",
    "message": "Key is in read-only mode. Switch the key to read and write in /keys to allow Bitrix24 data changes.",
    "details": {
      "method": "bizproc.event.send",
      "keyName": "Solution deploy key",
      "currentMode": "READONLY",
      "switchUrl": "/keys"
    }
  }
}

Full list of common API errors — Errors.

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.

javascript
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 = process.env.VIBE_URL ?? 'https://vibecode.bitrix24.com';
const VIBE_API_KEY = process.env.VIBE_API_KEY;
if (!VIBE_API_KEY) throw new Error('The VIBE_API_KEY environment variable is not set');

async function callVibe(path, body) {
  const res = await fetch(`${VIBE_URL}/v1${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);

See also