Детальное руководство по универсальному эндпоинту POST /v1/call,
который позволяет вызывать любой REST-метод Битрикс24 через платформу
VibeCode. Все примеры кода — Python asyncio (aiohttp).
Статус платформы: бета. Контракт
/v1/callзафиксирован по исходному коду официального SDKvibecode-b24-bot(MIT) и официальной документации VibeCode; места, требующие проверки на живом портале, помечены ⚠️.
POST /v1/call — «сырой» транспорт к REST API Битрикс24 через платформу
VibeCode. Вы передаёте имя метода Bitrix24 и его параметры, платформа
выполняет вызов от имени вашего ключа (личный ключ vibe_api_ работает как
вебхук пользователя) и возвращает результат.
Когда это нужно:
voximplant.statistic.get, crm.status.list, im.notify.system.add);">=DATE_CREATE");Справочник всех методов Bitrix24: https://apidocs.bitrix24.ru/api-reference/
| Параметр | Значение |
|---|---|
| Базовый 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())
{
"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 обрабатывает оба варианта.
Документация 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 зависит от версии платформы — выводите
её целиком при первом запуске и опирайтесь на фактические поля.
| 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 |
Внутренняя ошибка прокси | Ретрай; затем фидбэк платформе |
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
| Уровень | Лимит | Источник |
|---|---|---|
| Ключ 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-слоя вместо выгрузки записей.
Списочные методы Bitrix24 возвращают максимум 50 записей за вызов. Управление страницами:
params передаётся start — смещение (0, 50, 100, …);next (смещение следующей страницы,
отсутствует на последней) и total (всего записей по фильтру).Шаблон полной выгрузки:
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}
Самодостаточный клиент: конверт 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.
Каталог покрывает наиболее востребованные методы. Полный справочник:
https://apidocs.bitrix24.ru/api-reference/ — любой метод оттуда вызывается
тем же шаблоном {"method": ..., "params": ...} при наличии скоупа.
| Метод | Назначение | Параметры |
|---|---|---|
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") # сверка часового пояса портала
| Метод | Назначение | Ключевые параметры |
|---|---|---|
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")
Единая схема для четырёх сущностей (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"}})
Дела — звонки, встречи, письма, привязанные к лидам/сделкам/контактам.
| Метод | Назначение |
|---|---|
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"},
})]
Скоуп 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.
Скоуп 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",
}})
Скоупы 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 звонков",
})
| Метод | Скоуп | Назначение |
|---|---|---|
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 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).
| Критерий | 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, остальные выполнятся.
Полный сценарий нашего приложения целиком на /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))
VIBE_API_KEY); не хранить в коде,
репозитории, скриншотах и чатах. Скомпрометированный ключ — отозвать в
/vibe/ портала и создать новый.user + telephony (или crm).vibe_api_ действует от имени владельца со всеми его
правами в Bitrix24 — пишущие методы (*.add, *.update, *.delete)
выполняются по-настоящему; тестируйте на тестовых данных.X-Api-Key и тела ответов с персональными данными./v1/me, типы ключей, лимит 300 req/min, безопасность).X-Api-Key, конверт ответа, rate limit 10 rps, entity-слой)./v1/batch, коды ошибок платформы, скоупы per-call).telephony, обёртки /v1/calls/*, соответствие методам Bitrix24).vibecode-b24-bot (PyPI, MIT), файл client.py — фактический
контракт POST /v1/call ({"method","params"}), конверт
{success,data|result,error{code,message}}, ретраи 429 с backoff./call).GET /v1/me с вашим ключом —
curl -H "X-Api-Key: $VIBE_API_KEY" https://vibecode.bitrix24.tech/v1/me.