POST /v1/call — прямые вызовы Bitrix24 REST через Битрикс24 VibeCode

Детальное руководство по универсальному эндпоинту POST /v1/call, который позволяет вызывать любой REST-метод Битрикс24 через платформу VibeCode. Все примеры кода — Python asyncio (aiohttp).

Статус платформы: бета. Контракт /v1/call зафиксирован по исходному коду официального SDK vibecode-b24-bot (MIT) и официальной документации VibeCode; места, требующие проверки на живом портале, помечены ⚠️.


Оглавление

  1. Что такое POST /v1/call
  2. Авторизация и базовый URL
  3. Контракт запроса и ответа
  4. Скоупы ключа и стартовый запрос GET /v1/me
  5. Обработка ошибок
  6. Лимиты и стратегия ретраев
  7. Пагинация Bitrix24 (start / next / total)
  8. Базовый asyncio-клиент (полный код)
  9. Каталог методов Bitrix24 через /call - 9.1 Сервисные методы - 9.2 Пользователи и структура (user., department.) - 9.3 CRM: лиды, сделки, контакты, компании - 9.4 Дела CRM (crm.activity.*) - 9.5 Телефония (voximplant., telephony.) - 9.6 Задачи (tasks.task.*) - 9.7 Чаты и уведомления (im.*) - 9.8 Календарь и Диск
  10. Параллельные вызовы: asyncio.gather и Semaphore
  11. /call, Entity API и /batch — что когда использовать
  12. Готовый рецепт: звонки менеджеров за месяц
  13. Безопасность
  14. Источники

1. Что такое POST /v1/call

POST /v1/call — «сырой» транспорт к REST API Битрикс24 через платформу VibeCode. Вы передаёте имя метода Bitrix24 и его параметры, платформа выполняет вызов от имени вашего ключа (личный ключ vibe_api_ работает как вебхук пользователя) и возвращает результат.

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

Справочник всех методов Bitrix24: https://apidocs.bitrix24.ru/api-reference/


2. Авторизация и базовый URL

Параметр Значение
Базовый URL https://vibecode.bitrix24.tech/v1
Эндпоинт POST /v1/call
Авторизация заголовок X-Api-Key: vibe_api_... (или vibe_app_...)
Content-Type application/json

Ключ создаётся на портале: https://ваш-портал.bitrix24.ru/vibe/ → «API-ключи» → «Создать API-ключ» (показывается один раз). Права (скоупы) выбираются при создании; /call выполнит только методы, разрешённые скоупами ключа.

import aiohttp, asyncio, os

async def main():
    headers = {"X-Api-Key": os.environ["VIBE_API_KEY"],
               "Content-Type": "application/json"}
    async with aiohttp.ClientSession(headers=headers) as s:
        async with s.post("https://vibecode.bitrix24.tech/v1/call",
                          json={"method": "app.info", "params": {}}) as r:
            print(await r.json())

asyncio.run(main())

3. Контракт запроса и ответа

Тело запроса

{
  "method": "crm.deal.list",
  "params": {
    "filter": { "STAGE_ID": "NEW" },
    "select": ["ID", "TITLE", "OPPORTUNITY"],
    "order":  { "ID": "ASC" },
    "start":  0
  }
}
Поле Тип Обяз. Описание
method string да Имя REST-метода Bitrix24 (например user.get)
params object нет Параметры метода в формате Bitrix24 (по умолчанию {})

⚠️ В отличие от entity-эндпоинтов VibeCode (camelCase), параметры в /call передаются в родном формате метода Bitrix24 — обычно UPPER_CASE-поля (FILTER, STAGE_ID, RESPONSIBLE_ID); у методов tasks.task.* — свой формат (fields, camelCase-поля задачи).

Конверт ответа

Платформа оборачивает ответ в стандартный конверт VibeCode:

{
  "success": true,
  "data": <результат метода Bitrix24>
}

При ошибке:

{
  "success": false,
  "error": { "code": "BITRIX_ERROR", "message": "..." }
}

Правило распаковки (так делает официальный SDK): если success == false — поднять исключение с error.code / error.message; иначе взять data, при его отсутствии result, иначе весь объект:

if not payload.get("success", True):
    err = payload.get("error", {})
    raise ApiError(err.get("code", "UNKNOWN"), err.get("message", str(payload)))
result = payload.get("data", payload.get("result", payload))

⚠️ Внутри data списочные методы Bitrix24 могут сохранять собственную обёртку {"result": [...], "next": 50, "total": 1234} либо платформа разворачивает её до списка — клиент из раздела 8 обрабатывает оба варианта.


4. Скоупы ключа и стартовый запрос GET /v1/me

Документация VibeCode рекомендует начинать работу с GET /v1/me: ответ содержит права ключа, доступные объекты и инструкции платформы. Если прав не хватает — приложение должно явно сообщить, какие скоупы добавить.

Соответствие «метод → требуемый скоуп» (основные):

Скоуп Методы через /call
user user.get, user.current, user.search, user.fields
department department.get, department.fields
crm все crm.*: lead/deal/contact/company/activity/status/…
telephony voximplant.statistic.get, telephony.externalcall.*
task tasks.task.*
im, imbot im.* (сообщения, уведомления, чаты)
calendar calendar.event.*
disk disk.*
async def check_scopes(client):
    me = await client.get("/me")
    print("Портал:", me.get("portal") or me.get("domain"))
    print("Скоупы:", me.get("scopes") or me.get("permissions"))

⚠️ Точная структура ответа /me зависит от версии платформы — выводите её целиком при первом запуске и опирайтесь на фактические поля.


5. Обработка ошибок

HTTP-статусы и коды платформы VibeCode

HTTP Код Когда возникает Что делать
400 INVALID_REQUEST Невалидное тело запроса Исправить JSON
401 TOKEN_MISSING / 401 без тела Нет/неверный X-Api-Key, истёк ключ Проверить/перевыпустить ключ
401 TOKEN_REFRESH_FAILED Не обновился OAuth-токен (vibe_app_) Переавторизация приложения
403 SCOPE_NOT_ALLOWED Методу не хватает скоупа ключа Добавить скоуп при создании ключа
422 BITRIX_ERROR REST-метод Bitrix24 вернул ошибку См. message (ошибка Bitrix24 ниже)
429 rate limit Превышены лимиты Подождать и повторить (раздел 6)
502 BITRIX_UNAVAILABLE Портал Bitrix24 недоступен Ретрай с паузой
503 QUEUE_TIMEOUT Таймаут очереди платформы Ретрай с паузой
500 INTERNAL_ERROR Внутренняя ошибка прокси Ретрай; затем фидбэк платформе

Типовые ошибки самого Bitrix24 (приходят как BITRIX_ERROR)

Текст/код Bitrix24 Причина
ERROR_METHOD_NOT_FOUND Опечатка в method или метод недоступен на тарифе
INVALID_ARG_VALUE / ERROR_ARGUMENT Неверные параметры (params)
insufficient_scope Скоуп есть у ключа, но не у пользователя-владельца
QUERY_LIMIT_EXCEEDED Превышен лимит запросов Bitrix24
class ApiError(Exception):
    def __init__(self, code: str, message: str):
        self.code, self.message = code, message
        super().__init__(f"{code}: {message}")

# точечная реакция:
try:
    users = await b24.call("user.get", {"FILTER": {"ACTIVE": True}})
except ApiError as e:
    if e.code == "SCOPE_NOT_ALLOWED":
        print("Добавьте скоуп 'user' ключу VibeCode")
    elif e.code == "BITRIX_ERROR" and "METHOD_NOT_FOUND" in e.message:
        print("Метод недоступен на вашем тарифе Битрикс24")
    else:
        raise

6. Лимиты и стратегия ретраев

Уровень Лимит Источник
Ключ VibeCode 300 запросов/мин официальная статья Битрикс24
Портал Bitrix24 10 запросов/сек (общий для всех ключей) docs/entity-api
Ответ при превышении HTTP 429 + время повтора в ответе docs FAQ

Рекомендованная стратегия (используется официальным SDK): экспоненциальный backoff min(2**attempt, 30) секунд, до 5 попыток, ретраи для 429 и сетевых ошибок; 4xx-ошибки (кроме 429) не ретраить.

Дополнительно сокращайте число запросов: - POST /v1/batch — до 50 вызовов за 1 единицу лимита (раздел 11); - запрашивайте только нужные поля через select; - для подсчётов используйте aggregate entity-слоя вместо выгрузки записей.


7. Пагинация Bitrix24 (start / next / total)

Списочные методы Bitrix24 возвращают максимум 50 записей за вызов. Управление страницами:

Шаблон полной выгрузки:

async def call_all(b24, method: str, params: dict) -> list:
    """Выгрузить ВСЕ страницы списочного метода Bitrix24."""
    items, start = [], 0
    while True:
        page = await b24.call_raw(method, {**params, "start": start})
        # page — это data из конверта VibeCode; возможны 2 формы:
        if isinstance(page, list):                # платформа развернула result
            items.extend(page)
            return items
        chunk = page.get("result", [])
        if isinstance(chunk, dict):               # tasks.task.list -> {"tasks": [...]}
            chunk = next((v for v in chunk.values() if isinstance(v, list)), [])
        items.extend(chunk)
        if page.get("next") is None or not chunk:
            return items
        start = int(page["next"])

Совет по производительности из практики Bitrix24 REST: на очень больших выборках вместо роста start быстрее фильтровать по ">ID" последней полученной записи с "start": -1 (отключает подсчёт total):

params = {"order": {"ID": "ASC"}, "filter": {">ID": last_id}, "start": -1}

8. Базовый asyncio-клиент (полный код)

Самодостаточный клиент: конверт VibeCode, ретраи 429/5xx/сети, обе формы пагинации. Зависимость: pip install aiohttp.

"""b24call.py — asyncio-клиент POST /v1/call (Битрикс24 VibeCode)."""
from __future__ import annotations
import asyncio, os
from typing import Any, AsyncIterator
import aiohttp

BASE = "https://vibecode.bitrix24.tech/v1"
RETRYABLE_HTTP = {429, 500, 502, 503}

class ApiError(Exception):
    def __init__(self, code: str, message: str):
        self.code, self.message = code, message
        super().__init__(f"{code}: {message}")

class B24:
    def __init__(self, api_key: str, base: str = BASE, max_retries: int = 5,
                 timeout: float = 30.0):
        self._headers = {"X-Api-Key": api_key, "Content-Type": "application/json"}
        self.base, self.max_retries = base.rstrip("/"), max_retries
        self._timeout = aiohttp.ClientTimeout(total=timeout)
        self._session: aiohttp.ClientSession | None = None

    async def __aenter__(self) -> "B24":
        self._session = aiohttp.ClientSession(headers=self._headers,
                                              timeout=self._timeout)
        return self

    async def __aexit__(self, *exc) -> None:
        if self._session:
            await self._session.close()

    # -- транспорт с ретраями ------------------------------------------
    async def _request(self, http: str, path: str, **kw) -> Any:
        assert self._session, "use 'async with B24(...)'"
        for attempt in range(self.max_retries):
            try:
                async with self._session.request(http, self.base + path, **kw) as r:
                    if r.status in RETRYABLE_HTTP and attempt < self.max_retries - 1:
                        await asyncio.sleep(min(2 ** attempt, 30))
                        continue
                    payload = await r.json(content_type=None)
            except aiohttp.ClientError:
                if attempt < self.max_retries - 1:
                    await asyncio.sleep(min(2 ** attempt, 30))
                    continue
                raise
            if isinstance(payload, dict) and not payload.get("success", True):
                err = payload.get("error", {})
                raise ApiError(str(err.get("code", r.status)),
                               err.get("message", str(payload)))
            if isinstance(payload, dict):
                return payload.get("data", payload.get("result", payload))
            return payload
        raise ApiError("MAX_RETRIES", "Exceeded max retries")

    # -- публичный API --------------------------------------------------
    async def get(self, path: str, **params: Any) -> Any:
        return await self._request("GET", path, params=params)

    async def me(self) -> dict:
        return await self.get("/me")

    async def call_raw(self, method: str, params: dict | None = None) -> Any:
        """Один вызов метода Bitrix24: возвращает data как есть."""
        return await self._request("POST", "/call",
                                   json={"method": method, "params": params or {}})

    async def call_iter(self, method: str,
                        params: dict | None = None) -> AsyncIterator[dict]:
        """Постраничная выгрузка списочного метода (следует за next)."""
        params, start = dict(params or {}), 0
        while True:
            page = await self.call_raw(method, {**params, "start": start})
            if isinstance(page, list):
                for it in page:
                    yield it
                return
            chunk = page.get("result", [])
            if isinstance(chunk, dict):
                chunk = next((v for v in chunk.values() if isinstance(v, list)), [])
            for it in chunk:
                yield it
            if page.get("next") is None or not chunk:
                return
            start = int(page["next"])

# Мини-проверка
async def _demo():
    async with B24(os.environ["VIBE_API_KEY"]) as b24:
        print(await b24.call_raw("app.info"))

if __name__ == "__main__":
    asyncio.run(_demo())

Все примеры ниже предполагают async with B24(os.environ["VIBE_API_KEY"]) as b24.


9. Каталог методов Bitrix24 через /call

Каталог покрывает наиболее востребованные методы. Полный справочник: https://apidocs.bitrix24.ru/api-reference/ — любой метод оттуда вызывается тем же шаблоном {"method": ..., "params": ...} при наличии скоупа.

9.1 Сервисные методы

Метод Назначение Параметры
app.info Информация о приложении/ключе
methods Список доступных методов {"full": true} — все
scope Список доступных скоупов {"full": true}
server.time Время сервера портала (часовой пояс!)
profile Профиль пользователя-владельца ключа
methods = await b24.call_raw("methods", {"full": True})
tz_now  = await b24.call_raw("server.time")   # сверка часового пояса портала

9.2 Пользователи и структура (user., department.)

Метод Назначение Ключевые параметры
user.current Текущий пользователь (владелец ключа)
user.get Список/поиск сотрудников, пагинация FILTER, sort, order, ADMIN_MODE
user.search Быстрый поиск по ФИО/email/телефону FILTER: {FIND: "..."}
user.fields Схема полей пользователя
department.get Подразделения (дерево) ID, PARENT

Полезные поля FILTER для user.get: ACTIVE (true/false), UF_DEPARTMENT (ID отдела), WORK_POSITION, EMAIL, USER_TYPE (employee/extranet/email).

# Все активные сотрудники отдела продаж (id отдела = 5)
sales = [u async for u in b24.call_iter(
    "user.get", {"FILTER": {"ACTIVE": True, "UF_DEPARTMENT": 5}})]
for u in sales:
    print(u["ID"], u["LAST_NAME"], u.get("WORK_POSITION"))

# Структура компании
deps = await b24.call_raw("department.get")

9.3 CRM: лиды, сделки, контакты, компании

Единая схема для четырёх сущностей (lead / deal / contact / company):

Шаблон метода Назначение
crm.{e}.list Список: filter, select, order, start
crm.{e}.get Одна запись: {"id": N}
crm.{e}.add Создание: {"fields": {...}, "params": {"REGISTER_SONET_EVENT": "Y"}}
crm.{e}.update Обновление: {"id": N, "fields": {...}}
crm.{e}.delete Удаление: {"id": N}
crm.{e}.fields Схема полей (включая UF_CRM_*)

Операторы фильтра Bitrix24 — префиксы у имени поля: >=, <=, >, <, ! (не равно), % (LIKE), @ (IN, значение-список).

# Сделки в работе дороже 100 000, созданные в мае 2026
deals = [d async for d in b24.call_iter("crm.deal.list", {
    "filter": {
        "CLOSED": "N",
        ">=OPPORTUNITY": 100_000,
        ">=DATE_CREATE": "2026-05-01",
        "<DATE_CREATE":  "2026-06-01",
        "@STAGE_ID": ["NEW", "PREPARATION", "EXECUTING"],
    },
    "select": ["ID", "TITLE", "OPPORTUNITY", "ASSIGNED_BY_ID", "STAGE_ID"],
    "order":  {"OPPORTUNITY": "DESC"},
})]

# Создать сделку и привязать контакт
new_id = await b24.call_raw("crm.deal.add", {"fields": {
    "TITLE": "Поставка API", "OPPORTUNITY": 250_000, "CURRENCY_ID": "RUB",
    "ASSIGNED_BY_ID": 7, "CONTACT_ID": 42,
}})

# Сменить стадию
await b24.call_raw("crm.deal.update",
                   {"id": new_id, "fields": {"STAGE_ID": "WON"}})

# Справочник стадий/статусов
statuses = await b24.call_raw("crm.status.list",
                              {"filter": {"ENTITY_ID": "DEAL_STAGE"}})

9.4 Дела CRM (crm.activity.*)

Дела — звонки, встречи, письма, привязанные к лидам/сделкам/контактам.

Метод Назначение
crm.activity.list Список дел: filter, select, order
crm.activity.get Дело по {"id": N}
crm.activity.fields Схема полей

Ключевые поля: TYPE_ID (1 — встреча, 2 — звонок, 4 — письмо), DIRECTION (1 — входящий, 2 — исходящий), COMPLETED (Y/N), RESPONSIBLE_ID, START_TIME/END_TIME, OWNER_TYPE_ID+OWNER_ID (к какой сущности привязано), PROVIDER_ID (VOXIMPLANT_CALL — звонок телефонии).

# Завершённые исходящие звонки за май 2026
calls = [a async for a in b24.call_iter("crm.activity.list", {
    "filter": {"TYPE_ID": 2, "DIRECTION": 2, "COMPLETED": "Y",
               ">=START_TIME": "2026-05-01", "<START_TIME": "2026-06-01"},
    "select": ["ID", "RESPONSIBLE_ID", "START_TIME", "OWNER_TYPE_ID", "OWNER_ID"],
    "order":  {"START_TIME": "ASC"},
})]

9.5 Телефония (voximplant., telephony.)

Скоуп telephony. Основной метод аналитики:

voximplant.statistic.get — детальная статистика звонков портала.

Параметры: FILTER, SORT, ORDER, start. Поля записи (основные):

Поле Смысл
ID, CALL_ID Идентификаторы звонка
PORTAL_USER_ID Сотрудник, говоривший по звонку
PHONE_NUMBER Номер абонента
CALL_TYPE 1 — исходящий, 2 — входящий, 3 — входящий с переадресацией, 4 — обратный
CALL_DURATION Длительность, сек
CALL_START_DATE Начало звонка (фильтруемое поле)
CALL_FAILED_CODE SIP-код: 200 — успешный, 304 — пропущен, 603 — отклонён
CALL_RECORD_URL Ссылка на запись разговора
CRM_ENTITY_TYPE, CRM_ENTITY_ID Привязка к CRM
# Успешные звонки длиннее минуты за май 2026
stats = [s async for s in b24.call_iter("voximplant.statistic.get", {
    "FILTER": {">=CALL_START_DATE": "2026-05-01",
               "<CALL_START_DATE":  "2026-06-01",
               "CALL_FAILED_CODE": 200,
               ">CALL_DURATION": 60},
    "SORT": "CALL_START_DATE", "ORDER": "ASC",
})]

Регистрация внешних звонков (интеграция своей АТС): telephony.externalcall.register → разговор → telephony.externalcall.finish (+ telephony.externalcall.attachRecord). Для этих сценариев у VibeCode есть и специализированные REST-обёртки POST /v1/calls/register|.../finish|.../transcription — удобнее /call.

9.6 Задачи (tasks.task.*)

Скоуп task. ⚠️ Формат параметров у задач свой: обёртка fields, camelCase-поля, ответ — объект {"tasks": [...]} / {"task": {...}}.

Метод Назначение
tasks.task.list Список: filter, select, order, start
tasks.task.get {"taskId": N, "select": [...]}
tasks.task.add {"fields": {"TITLE": ..., "RESPONSIBLE_ID": ...}}
tasks.task.update {"taskId": N, "fields": {...}}
tasks.task.complete {"taskId": N}

Статусы: 2 — ждёт выполнения, 3 — выполняется, 4 — ждёт контроля, 5 — завершена, 6 — отложена.

# Просроченные незавершённые задачи менеджера 7
overdue = [t async for t in b24.call_iter("tasks.task.list", {
    "filter": {"RESPONSIBLE_ID": 7, "<DEADLINE": "2026-06-12", "!STATUS": 5},
    "select": ["ID", "TITLE", "DEADLINE", "STATUS"],
})]

# Поставить задачу с дедлайном
await b24.call_raw("tasks.task.add", {"fields": {
    "TITLE": "Перезвонить клиенту", "RESPONSIBLE_ID": 7,
    "DEADLINE": "2026-06-15T18:00:00+03:00",
}})

9.7 Чаты и уведомления (im.*)

Скоупы im / imbot.

Метод Назначение
im.message.add Сообщение в диалог: {"DIALOG_ID": "chat123" или "42", "MESSAGE": "..."}
im.notify.system.add Системное уведомление пользователю: {"USER_ID": N, "MESSAGE": "..."}
im.recent.list Список последних диалогов
im.user.get Данные пользователя мессенджера

Форматирование сообщений — BB-коды (не Markdown): [b]жирный[/b], [url=...]ссылка[/url], [user=42][/user] — упоминание.

# Отправить отчёт руководителю в личку (USER_ID=1)
await b24.call_raw("im.notify.system.add", {
    "USER_ID": 1,
    "MESSAGE": "[b]Отчёт по звонкам за май готов[/b] — всего 327 звонков",
})

9.8 Календарь и Диск

Метод Скоуп Назначение
calendar.event.get calendar События: {"type": "user", "ownerId": N, "from": ..., "to": ...}
calendar.event.add calendar Создать событие
disk.storage.getlist disk Список хранилищ
disk.folder.getchildren disk Содержимое папки: {"id": folderId}
events = await b24.call_raw("calendar.event.get", {
    "type": "user", "ownerId": 7,
    "from": "2026-06-01", "to": "2026-06-30",
})

10. Параллельные вызовы: asyncio.gather и Semaphore

Лимит портала — 10 rps на ВСЕ ключи, поэтому параллелизм нужно ограничивать. Шаблон: asyncio.Semaphore + gather.

import asyncio

SEM = asyncio.Semaphore(5)          # максимум 5 одновременных вызовов

async def limited_call(b24, method, params):
    async with SEM:
        return await b24.call_raw(method, params)

async def enrich_deals(b24, deal_ids: list[int]):
    """Параллельно получить карточки сделок (бережно к rate limit)."""
    tasks = [limited_call(b24, "crm.deal.get", {"id": i}) for i in deal_ids]
    return await asyncio.gather(*tasks)

Если запросов десятки — выгоднее не параллелить /call, а упаковать вызовы в один POST /v1/batch (50 вызовов = 1 единица лимита, см. раздел 11).


11. /call, Entity API и /batch — что когда использовать

Критерий POST /v1/call Entity API (/v1/{entity}…) POST /v1/batch
Формат полей родной Bitrix24 (UPPER_CASE) camelCase, автоконвертация camelCase (entity-формат)
Покрытие любой REST-метод Bitrix24 38 сущностей те же 38 сущностей, action: list/get/create/update/delete/fields/search
Пагинация вручную (start/next) авто до 5000 (limit > 50) search-вызовы — авто до 5000
Агрегации нет (считать самим) POST /{entity}/aggregate + groupBy через несколько list-вызовов
Экономия лимита 1 вызов = 1 запрос авто-пагинация дешевле 50 вызовов = 1 единица лимита
Когда выбирать телефония, статусы, im, нестандартные методы; перенос кода с вебхуков типовой CRUD по CRM/задачам, дашборды сборка данных из многих сущностей одним запросом

Формат POST /v1/batch (entity-формат, не сырые методы):

payload = {"calls": [
    {"id": "deals",  "entity": "deals", "action": "list",
     "params": {"filter": {"stageId": "NEW"}, "select": ["id", "title"]}},
    {"id": "users",  "entity": "users", "action": "list", "params": {}},
]}
data = await b24._request("POST", "/batch", json=payload)
deals = data["results"]["deals"]["result"]
errors = data["errors"]               # частичные ошибки по id вызова

Скоупы в batch проверяются на каждый вызов отдельно: вызов без скоупа вернёт SCOPE_NOT_ALLOWED в errors, остальные выполнятся.


12. Готовый рецепт: звонки менеджеров за месяц

Полный сценарий нашего приложения целиком на /call и asyncio (сотрудники и звонки запрашиваются параллельно):

import asyncio, os
from collections import Counter
from datetime import date

from b24call import B24, ApiError          # клиент из раздела 8

async def report(year: int, month: int):
    start = date(year, month, 1)
    end = date(year + month // 12, month % 12 + 1, 1)

    async with B24(os.environ["VIBE_API_KEY"]) as b24:
        await b24.me()                                  # проверка ключа/скоупов

        async def users():
            return {
                str(u["ID"]): f'{u.get("NAME", "")} {u.get("LAST_NAME", "")}'.strip()
                async for u in b24.call_iter("user.get", {"FILTER": {"ACTIVE": True}})
            }

        async def calls():
            try:                                        # телефония…
                return Counter([str(c["PORTAL_USER_ID"])
                    async for c in b24.call_iter("voximplant.statistic.get",
                        {"FILTER": {">=CALL_START_DATE": start.isoformat(),
                                    "<CALL_START_DATE":  end.isoformat()}})])
            except ApiError:                            # …или дела CRM «звонок»
                return Counter([str(a["RESPONSIBLE_ID"])
                    async for a in b24.call_iter("crm.activity.list",
                        {"filter": {"TYPE_ID": 2,
                                    ">=START_TIME": start.isoformat(),
                                    "<START_TIME":  end.isoformat()},
                         "select": ["ID", "RESPONSIBLE_ID"]})])

        names, counts = await asyncio.gather(users(), calls())

    for uid, n in counts.most_common():
        print(f'{names.get(uid, f"user #{uid}"):34} {n:>6}')
    print(f'{"ИТОГО":34} {sum(counts.values()):>6}')

asyncio.run(report(2026, 5))

13. Безопасность


14. Источники

  1. Официальная статья «Битрикс24 Вайбкод» — https://apidocs.bitrix24.ru/ai-tools/vibecode.html (роль /v1/me, типы ключей, лимит 300 req/min, безопасность).
  2. VibeCode «Обзор API» — https://vibecode.bitrix24.tech/docs/entity-api (база, X-Api-Key, конверт ответа, rate limit 10 rps, entity-слой).
  3. VibeCode «Batch API» — https://vibecode.bitrix24.tech/docs/batch (формат /v1/batch, коды ошибок платформы, скоупы per-call).
  4. VibeCode «Телефония» — https://vibecode.bitrix24.tech/docs/telephony (скоуп telephony, обёртки /v1/calls/*, соответствие методам Bitrix24).
  5. SDK vibecode-b24-bot (PyPI, MIT), файл client.py — фактический контракт POST /v1/call ({"method","params"}), конверт {success,data|result,error{code,message}}, ретраи 429 с backoff.
  6. Справочник REST API Битрикс24 — https://apidocs.bitrix24.ru/api-reference/ (имена методов, параметры, поля; всё вызывается через /call).
  7. Персональная часть: GET /v1/me с вашим ключом — curl -H "X-Api-Key: $VIBE_API_KEY" https://vibecode.bitrix24.tech/v1/me.