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

Долгий метод в бизнес-процессе

Рецепт для приложения, чья работа не укладывается в окно вызова: приложение принимает задачу от приостановленного действия бизнес-процесса Битрикс24, отвечает сразу и досылает результат отдельным запросом, когда закончит.

Базовый URL: https://vibecode.bitrix24.tech/v1 | Авторизация: X-Api-Key | Скоуп ответа: bizproc

Когда это нужно | Что понадобится | Как устроено решение | Шаг 1 | Шаг 2 | Шаг 3 | Ключ для ответа | Ошибки | Ограничения | Полный код

Примеры шагов показывают отдельные вызовы и опираются на переменные, объявленные выше по сценарию. Готовый к запуску обработчик — в разделе Полный код.

Когда это нужно

Вызов через внешний API приложения живёт 30 секунд — это общий предел, за которым вызывающий получает 503 APP_API_TIMEOUT. Само приложение обязано начать отвечать раньше: платформа перестаёт ждать его ответ на двадцать пятой секунде и отдаёт вызывающему 503 APP_API_UNAVAILABLE.

Работа, которая в эти окна не помещается, — расчёт по большой выборке, обращение к внешней системе, генерация документа, ожидание ответа человека — требует другой формы. Приложение принимает задачу, отвечает роботу сразу и досылает результат тогда, когда он готов. Бизнес-процесс всё это время спит на своём шаге, а его состояние держит портал.

Долгий метод по контракту решения устроен иначе — там адрес ответа и одноразовый токен приезжают заголовками вызова, а платформа доставляет исход на портал сама. Этот рецепт про бизнес-процесс, где ответом распоряжается само решение: Отправить результат долгого метода.

Что понадобится

  1. Приложение с включённым внешним API и выпущенным ключом. Переключатель и выдача ключа — в карточке приложения, условия и отказы описаны на странице Внешний API приложения. Этим ключом бизнес-процесс вызывает приложение.
  2. Ключ платформы для ответа — vibe_api_… со скоупом bizproc, в режиме чтения и записи, привязанный к тому же порталу. Его создаёте вы сами и сами передаёте приложению переменной окружения — платформа ключ не выдаёт. Требования разобраны в разделе Ключ для ответа.
  3. Приостанавливаемое действие бизнес-процесса на портале. Оно вызывает приложение, кладёт в тело запроса свой eventToken и засыпает до события. Регистрация такого действия — /v1/bizproc-activities.
  4. Node.js 18 или новее для примеров.

Во всех примерах $VIBE_API_KEY — ключ платформы для ответа из пункта 2, а YOUR_APP_EXTERNAL_API_KEY — ключ внешнего API из пункта 1.

Как устроено решение

  1. Действие бизнес-процесса вызывает приложение по адресу внешнего API и передаёт в теле запроса eventToken — одноразовый ключ своего шага.
  2. Приложение сохраняет eventToken вместе с задачей и отвечает 202 в пределах окна вызова. Действие после этого спит.
  3. Пока работа идёт, приложение пишет в журнал процесса через POST /v1/workflows/activity-log. Состояние процесса это не меняет.
  4. Закончив, приложение вызывает POST /v1/workflows/event с тем же eventToken и значениями для выходных параметров действия. Процесс просыпается и идёт дальше.

Портальные учётные данные приложению не нужны ни на одном шаге: платформа обращается к порталу сохранёнными данными владельца ключа.

Шаг 1. Принять вызов и ответить сразу

Робот бизнес-процесса отправляет запрос на адрес приложения:

Адрес:      https://vibecode.bitrix24.tech/v1/applications/:applicationId/api/<ваш-путь>
Метод:      POST
Заголовок:  X-Api-Key: YOUR_APP_EXTERNAL_API_KEY

В теле действие передаёт свой eventToken и всё, что нужно приложению для работы. Поле кладёт действие, а не платформа — имя поля выбирает автор действия, и в примерах ниже оно называется eventToken.

К телу платформа добавляет заголовки, которые вызывающий подделать не может — входящие имена с префиксом X-Vibe- срезаются:

Заголовок Что несёт
X-Vibe-Request-Id Идентификатор вызова. Тот же идентификатор платформа пишет в свой журнал
X-Vibe-Caller-Kind Значение external-api — вызов пришёл этим каналом, а не из интерфейса Битрикс24
X-Vibe-Caller-Portal-Id Портал, которому принадлежит ключ вызова
X-Vibe-Caller-Key-Id Идентификатор ключа внешнего API, которым сделан вызов

Заголовков с адресом ответа, токеном ответа и сроком на этом пути нет — они принадлежат вызову по контракту решения. Нет здесь и заголовков с личностью сотрудника: внешний вызов сессии не несёт. Полный разбор того, что доезжает до приложения, — Что получает приложение.

Ответ 202 говорит роботу, что задача принята. Тело ответа при этом остаётся за приложением: робот раскладывает по переменным бизнес-процесса значения верхнего уровня.

JavaScript

javascript
// Обработчик маршрута приложения, который вызывает действие бизнес-процесса.
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;
  }

  // Задачу кладём в очередь и возвращаем управление СРАЗУ: ответить надо
  // в пределах окна вызова, а работа займёт больше.
  queue.push({ eventToken, dealId });

  res.status(202).json({ accepted: true });
});

Шаг 2. Записать промежуточный шаг в журнал процесса

Пока работа идёт, приложение отчитывается в журнал выполнения бизнес-процесса. Запись видна тому, кто смотрит процесс на портале, и состояние процесса не меняет — действие остаётся приостановленным.

cURL

Terminal
curl -X POST https://vibecode.bitrix24.tech/v1/workflows/activity-log \
  -H "X-Api-Key: $VIBE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"eventToken":"xxx.yyy.zzz","logMessage":"Выборка собрана, считаем скидку"}'

JavaScript

javascript
await fetch('https://vibecode.bitrix24.tech/v1/workflows/activity-log', {
  method: 'POST',
  headers: {
    'X-Api-Key': process.env.VIBE_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    eventToken,
    logMessage: 'Выборка собрана, считаем скидку',
  }),
});
JSON
{
  "success": true,
  "data": true
}

Полное описание полей и отказов — Записать в журнал процесса.

Шаг 3. Отправить результат в процесс

Работа закончена — приложение отправляет событие с тем же eventToken. Объект returnValues попадает в выходные параметры действия, и бизнес-процесс продолжает работу.

Поле Тип Обяз. Описание
eventToken string да Тот самый токен, который приехал в теле вызова на шаге 1
returnValues object нет Значения выходных параметров действия. По умолчанию — пустой объект
logMessage string нет Запись в журнал выполнения процесса вместе с событием

cURL

Terminal
curl -X POST https://vibecode.bitrix24.tech/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":"Скидка рассчитана"}'

JavaScript

javascript
const res = await fetch('https://vibecode.bitrix24.tech/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: 'Скидка рассчитана',
  }),
});

const body = await res.json();
if (!body.success) {
  console.error(`событие не принято: ${body.error.code}`);
}
JSON
{
  "success": true,
  "data": true
}

Поле data со значением true означает, что портал принял событие и выполнение процесса возобновлено. Полное описание полей и отказов — Отправить событие в процесс.

Ключ для ответа

Отвечать надо своим ключом платформы, а не тем ключом, которым приложение вызвали. Ключ внешнего API заперт перечнем маршрутов: ему открыт ровно один адрес платформы — проброс в приложение. На POST /v1/workflows/event и POST /v1/workflows/activity-log он получает 403 APP_API_KEY_OUT_OF_SCOPE ещё до обработчика, и никакая настройка этого не меняет.

Годится ключ платформы vibe_api_… — тот же, которым решение уже обращается к API Вайбкод. Платформа такой ключ не выдаёт и сама в приложение не подставляет. Его создаёт человек в разделе Ключи API и передаёт приложению переменной окружения при развёртывании. Имя переменной выбираете вы, по соглашению — VIBE_API_KEY: приложение читает её из своего окружения и отправляет значение заголовком X-Api-Key. Автоматически заведённого ключа «на сервер» не существует, и 401 MISSING_API_KEY от приложения означает, что переменная не доехала. Три требования к ключу:

Требование Что будет иначе
Скоуп bizproc 403 SCOPE_DENIED
Режим чтения и записи 403 WRITE_BLOCKED_READONLY_KEY. Ответ несёт адрес страницы, где режим переключается
Привязка к порталу 401 TOKEN_MISSING. Так же отвечает ключ авторизации приложения vibe_app_…, предъявленный без токена сессии сотрудника: у него портальные данные живут в сессии, а не на ключе

Ответ уходит на портал правами владельца ключа, а не того сотрудника, который запустил бизнес-процесс. Событие и запись в журнал платформа выполняет сохранёнными учётными данными владельца, поэтому выбирайте владельца ключа осознанно: у него должны быть права на те процессы, которым решение отвечает.

Текущий набор скоупов ключа и состояние его привязки к порталу показывает GET /v1/me. Где переключается режим ключа — Ключи и авторизация.

Ошибки

Таблица собрана по двум адресам ответа — POST /v1/workflows/event и POST /v1/workflows/activity-log. Отказы самого проксирующего вызова на шаге 1 — в таблице Внешний API приложения.

HTTP Код Описание
400 MISSING_PARAMS Не передан eventToken. На записи в журнал так же отвечает запрос без logMessage
401 MISSING_API_KEY Заголовок с ключом не передан
401 INVALID_API_KEY Ключ не опознан: такой строки на платформе нет
401 TOKEN_MISSING У ключа нет привязки к порталу. Текст ответа называет причину и то, что с ней делать
403 SCOPE_DENIED Ключу не хватает скоупа bizproc. Проверяется первым — раньше режима ключа
403 WRITE_BLOCKED_READONLY_KEY Ключ переведён в режим чтения. Поле details.switchUrl называет страницу, где режим меняется
403 APP_API_KEY_OUT_OF_SCOPE Ответ отправлен ключом внешнего API приложения. Этому ключу открыт только проброс в приложение
403 BITRIX_ACCESS_DENIED Битрикс24 отклонил запрос: токен недействителен, истёк или уже использован
422 BITRIX_ERROR Ошибка на стороне Битрикс24
429 RATE_LIMITED Превышен лимит запросов. Повторите через 1–2 секунды
502 BITRIX_UNAVAILABLE Битрикс24 недоступен

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

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

403 — ключ переведён в режим чтения:

JSON
{
  "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"
    }
  }
}

Ограничения

Ограничение Значение
Окно ответа приложения на шаге 1 25 секунд на ответ приложения внутри общего предела вызова в 30 секунд
Срок жизни eventToken Платформа его не ограничивает и передаёт токен в Битрикс24 как есть. Недействительный токен отклоняет Битрикс24 — 403 BITRIX_ACCESS_DENIED
Повторное использование eventToken Токен одноразовый: каждая приостановка процесса выдаёт новый, и после POST /v1/workflows/event прежний недействителен
Записей в журнал на один шаг Платформа их не ограничивает. Каждая запись — отдельный вызов API и расходуется по общим лимитам

Полный код

Обработчик принимает вызов действия, отвечает 202 и доводит задачу в фоне, отчитываясь в журнал процесса. Все переменные объявлены внутри, запускается как есть на Node.js 18 и новее.

javascript
import express from 'express';

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true })); // робот шлёт поля формы, не JSON

const VIBE_URL = 'https://vibecode.bitrix24.tech/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: `Считаем скидку по сделке ${dealId}`,
    });

    const discount = await computeDiscount(dealId); // ваша долгая работа

    await callVibe('/workflows/event', {
      eventToken,
      returnValues: { discount, verdict: discount > 0 ? 'approve' : 'reject' },
      logMessage: 'Скидка рассчитана',
    });
  } catch (err) {
    // Процесс спит и ждёт события. Ошибку тоже надо вернуть событием,
    // иначе шаг останется приостановленным до срока, заданного на портале.
    await callVibe('/workflows/event', {
      eventToken,
      returnValues: { discount: 0, verdict: 'error' },
      logMessage: `Расчёт не удался: ${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;
  }

  // Ответ уходит СРАЗУ, работа идёт после него.
  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);

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

  • Ошибку тоже возвращают событием. Приостановленное действие ждёт POST /v1/workflows/event и ничего не знает о том, что приложение упало. Пока события нет, шаг остаётся приостановленным. Поэтому неудачный расчёт отвечает событием с признаком отказа в returnValues, а не молчанием.
  • Ответ 202 на шаге 1 не гарантирует, что работа началась. Робот получает подтверждение приёма, и дальше единственный сигнал о ходе работы — записи в журнале процесса. Заводите запись на каждой значимой границе: разбирать застрявший шаг по журналу дешевле, чем по логам приложения.
  • Ключ ответа не хранится рядом с ключом вызова. Ключ внешнего API лежит в настройках робота на портале, ключ ответа — в переменных окружения приложения. Отзыв одного другого не трогает, и перевыпуск ключа внешнего API идущие ответы не ломает.
  • Один eventToken закрывает один шаг. Токен становится недействительным сразу после POST /v1/workflows/event, и уже использованный Битрикс24 отклоняет кодом BITRIX_ACCESS_DENIED. Поэтому повтор после сетевого обрыва небезопасен вслепую: тот же код придёт и когда первый запрос дошёл, и когда токен был испорчен, а различить эти два случая по ответу нечем.
  • Скоуп проверяется раньше режима ключа. Ключ без скоупа bizproc и в режиме чтения получит 403 SCOPE_DENIED, а не отказ по режиму. Сначала откройте скоуп, потом проверяйте режим.

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