Для 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.
Работа, которая в эти окна не помещается, — расчёт по большой выборке, обращение к внешней системе, генерация документа, ожидание ответа человека — требует другой формы. Приложение принимает задачу, отвечает роботу сразу и досылает результат тогда, когда он готов. Бизнес-процесс всё это время спит на своём шаге, а его состояние держит портал.
Долгий метод по контракту решения устроен иначе — там адрес ответа и одноразовый токен приезжают заголовками вызова, а платформа доставляет исход на портал сама. Этот рецепт про бизнес-процесс, где ответом распоряжается само решение: Отправить результат долгого метода.
Что понадобится
- Приложение с включённым внешним API и выпущенным ключом. Переключатель и выдача ключа — в карточке приложения, условия и отказы описаны на странице Внешний API приложения. Этим ключом бизнес-процесс вызывает приложение.
- Ключ платформы для ответа —
vibe_api_…со скоупомbizproc, в режиме чтения и записи, привязанный к тому же порталу. Его создаёте вы сами и сами передаёте приложению переменной окружения — платформа ключ не выдаёт. Требования разобраны в разделе Ключ для ответа. - Приостанавливаемое действие бизнес-процесса на портале. Оно вызывает приложение, кладёт в тело запроса свой
eventTokenи засыпает до события. Регистрация такого действия —/v1/bizproc-activities. - Node.js 18 или новее для примеров.
Во всех примерах $VIBE_API_KEY — ключ платформы для ответа из пункта 2, а YOUR_APP_EXTERNAL_API_KEY — ключ внешнего API из пункта 1.
Как устроено решение
- Действие бизнес-процесса вызывает приложение по адресу внешнего API и передаёт в теле запроса
eventToken— одноразовый ключ своего шага. - Приложение сохраняет
eventTokenвместе с задачей и отвечает202в пределах окна вызова. Действие после этого спит. - Пока работа идёт, приложение пишет в журнал процесса через
POST /v1/workflows/activity-log. Состояние процесса это не меняет. - Закончив, приложение вызывает
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
// Обработчик маршрута приложения, который вызывает действие бизнес-процесса.
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
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
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: 'Выборка собрана, считаем скидку',
}),
});
{
"success": true,
"data": true
}
Полное описание полей и отказов — Записать в журнал процесса.
Шаг 3. Отправить результат в процесс
Работа закончена — приложение отправляет событие с тем же eventToken. Объект returnValues попадает в выходные параметры действия, и бизнес-процесс продолжает работу.
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
eventToken |
string | да | Тот самый токен, который приехал в теле вызова на шаге 1 |
returnValues |
object | нет | Значения выходных параметров действия. По умолчанию — пустой объект |
logMessage |
string | нет | Запись в журнал выполнения процесса вместе с событием |
cURL
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
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}`);
}
{
"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 — ключ переведён в режим чтения:
{
"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 и новее.
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, а не отказ по режиму. Сначала откройте скоуп, потом проверяйте режим.