chore: add bundled skills bitrix24 and sendforsign

This commit is contained in:
ilya-bov
2026-03-23 15:31:31 +03:00
parent 5d3b1218ab
commit eab828b65e
33 changed files with 6756 additions and 0 deletions

View File

@@ -0,0 +1,436 @@
# OpenClaw Skills для Битрикс24
## Архитектура скиллов
OpenClaw подключается к порталу Битрикс24 через REST API под правами текущего пользователя. Скиллы сгруппированы по функциональным доменам и используют реальные API-методы Битрикс24.
---
## Группа 1: Коммуникации (im.*)
### Скилл: `bitrix24-messaging`
**Описание:** Чтение и отправка сообщений в личных и групповых чатах, каналах. Работа от имени пользователя.
**Возможности:**
- Отправка сообщений в 1-1 чаты, групповые чаты, каналы
- Чтение последних диалогов и истории сообщений
- Поиск по сообщениям в чатах
- Отправка уведомлений (системных и персональных)
- Создание новых чатов и управление участниками
- Отслеживание статуса «вам пишут»
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `im.message.add` | Отправка сообщения от пользователя | Основной метод отправки |
| `im.recent.list` | Список последних диалогов | Дайджест, обзор чатов |
| `im.recent.get` | Получить список чатов | Быстрый обзор |
| `im.dialog.get` | Информация о чате | Контекст диалога |
| `im.dialog.messages.search` | Поиск сообщений в чате | Поиск по переписке |
| `im.dialog.users.list` | Участники диалога | Кто в чате |
| `im.chat.add` | Создать новый чат | Создание рабочих чатов |
| `im.chat.user.add` | Добавить пользователя в чат | Управление участниками |
| `im.chat.user.delete` | Удалить из чата | Управление участниками |
| `im.notify.system.add` | Системное уведомление | Алерты и напоминания |
| `im.notify.personal.add` | Персональное уведомление | Личные напоминания |
| `im.dialog.read.all` | Прочитать все чаты | Массовое прочтение |
| `im.message.share` | Создать сущность из сообщения | Задача/событие из чата |
| `im.dialog.writing` | Статус «вам пишут» | UX |
**Ограничения прав:**
- Видит только чаты, в которых участвует пользователь
- Не может читать чужие личные переписки
- Каналы — только те, на которые подписан
**Примеры использования в сценариях:**
- Утренний дайджест пропущенных сообщений
- Маршрутизация запросов между отделами
- Публикация объявлений в каналах
- Отправка напоминаний коллегам
---
### Скилл: `bitrix24-users`
**Описание:** Работа со структурой компании, поиск пользователей, информация о подразделениях.
**Возможности:**
- Получение информации о текущем пользователе
- Поиск сотрудников по имени, должности, подразделению
- Получение структуры подразделений
- Список коллег и подчинённых
- Руководители подразделений
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `user.current` | Текущий пользователь | Контекст текущего юзера |
| `im.user.get` | Данные пользователя | Информация о коллеге |
| `im.search.user.list` | Поиск пользователей | Найти сотрудника |
| `im.department.employees.get` | Сотрудники отдела | Список команды |
| `im.department.managers.get` | Руководители отдела | Кто руководитель |
| `im.department.colleagues.list` | Коллеги/подчинённые | Для руководителей |
| `im.department.get` | Данные подразделения | Структура |
| `department.get` | Список подразделений | Оргструктура |
| `im.user.status.get` | Статус пользователя | Онлайн/офлайн |
| `profile` | Базовая информация | Быстрый профиль |
---
## Группа 2: Задачи и проекты (tasks.*)
### Скилл: `bitrix24-tasks`
**Описание:** Полная работа с задачами — создание, обновление, завершение, комментирование. Доступ к своим задачам, задачам подчинённых и задачам в проектах/группах.
**Возможности:**
- Создание задач с полным набором параметров (ответственный, дедлайн, приоритет, проект)
- Обновление и завершение задач
- Делегирование задач другому пользователю
- Работа с чек-листами задач
- Комментирование задач
- Просмотр истории изменений
- Фильтрация и сортировка задач
- Добавление в избранное
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `tasks.task.add` | Создать задачу | Основной метод создания |
| `tasks.task.update` | Обновить задачу | Изменение полей |
| `tasks.task.delete` | Удалить задачу | Удаление |
| `tasks.task.complete` | Завершить задачу | Закрытие |
| `tasks.task.renew` | Возобновить задачу | Переоткрытие |
| `tasks.task.delegate` | Делегировать задачу | Смена ответственного |
| `tasks.task.list` | Список задач с фильтрами | Отчёты, обзоры, стендапы |
| `tasks.task.getFields` | Поля задачи | Структура данных |
| `tasks.task.favorite.add` | В избранное | Закладки |
| `task.commentitem.add` | Добавить комментарий | Обсуждение в задаче |
| `task.commentitem.getlist` | Список комментариев | Чтение обсуждений |
| `task.checklistitem.add` | Пункт чек-листа | Детализация |
| `task.checklistitem.getlist` | Список чек-листа | Прогресс |
| `task.checklistitem.update` | Обновить чек-лист | Отметка выполнения |
| `task.logitem.list` | История изменений | Аудит, ретро |
**Ключевые поля задачи:**
- `TITLE` — название
- `DESCRIPTION` — описание
- `RESPONSIBLE_ID` — ответственный
- `CREATED_BY` — постановщик
- `DEADLINE` — крайний срок
- `PRIORITY` — приоритет (0=низкий, 1=средний, 2=высокий)
- `GROUP_ID` — проект/группа
- `PARENT_ID` — родительская задача (подзадачи)
- `STATUS` — статус (2=ждёт, 3=в работе, 4=ожидает контроля, 5=завершена)
- `TAGS` — теги
- `ACCOMPLICES` — соисполнители
- `AUDITORS` — наблюдатели
**Ограничения прав:**
- Свои задачи: полный доступ
- Задачи подчинённых: чтение и редактирование
- Задачи в проектах/группах: в соответствии с ролью в группе
- Чужие задачи вне проектов: нет доступа
**Примеры использования в сценариях:**
- Стендап-бот для проектов
- Декомпозиция задач на подзадачи
- Контроль просроченных задач
- Создание задач из обсуждений в чатах
- Еженедельные отчёты по прогрессу
---
### Скилл: `bitrix24-projects`
**Описание:** Работа с проектами (рабочими группами/коллабами) — управление участниками и контекст проекта.
**Возможности:**
- Добавление участников в проект
- Приглашение пользователей в группу
- Контекст проекта для задач
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `sonet_group.user.add` | Добавить в группу | Управление командой |
| `sonet_group.user.invite` | Пригласить в группу | Приглашения |
---
## Группа 3: CRM (crm.*)
### Скилл: `bitrix24-crm-deals`
**Описание:** Работа со сделками — создание, обновление, фильтрация, анализ воронки.
**Возможности:**
- Создание и обновление сделок
- Получение списка сделок с фильтрами и сортировкой
- Получение полей сделки (включая пользовательские)
- Связывание сделок с контактами
- Работа с направлениями (воронками) сделок
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `crm.deal.add` | Создать сделку | Новая сделка |
| `crm.deal.update` | Обновить сделку | Смена стадии, суммы |
| `crm.deal.get` | Получить сделку | Детали сделки |
| `crm.deal.list` | Список сделок | Воронка, отчёты |
| `crm.deal.fields` | Поля сделки | Структура данных |
| `crm.deal.contact.add` | Привязать контакт | Связи |
| `crm.deal.contact.items.get` | Контакты сделки | Кто участвует |
| `crm.deal.userfield.list` | Пользовательские поля | Кастомные данные |
| `crm.dealcategory.fields` | Поля направлений | Воронки |
| `crm.category.fields` | Информация о воронках | Настройки |
---
### Скилл: `bitrix24-crm-contacts`
**Описание:** Работа с контактами и компаниями в CRM.
**Возможности:**
- Создание и обновление контактов
- Поиск и фильтрация контактов
- Работа с компаниями
- Связи контакт-компания
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `crm.contact.add` | Создать контакт | Новый контакт |
| `crm.contact.list` | Список контактов | Поиск, фильтрация |
| `crm.contact.fields` | Поля контакта | Структура |
| `crm.contact.company.fields` | Связь контакт-компания | Связи |
| `crm.company.fields` | Поля компании | Структура |
---
### Скилл: `bitrix24-crm-leads`
**Описание:** Работа с лидами — создание, квалификация, конвертация.
**Возможности:**
- Создание и обновление лидов
- Фильтрация и квалификация
- Связи лид-контакт
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `crm.lead.add` | Создать лид | Новый лид |
| `crm.lead.list` | Список лидов | Квалификация, отчёты |
| `crm.lead.fields` | Поля лида | Структура |
| `crm.lead.contact.items.get` | Контакты лида | Связи |
| `crm.lead.userfield.list` | Пользовательские поля | Кастомные данные |
---
### Скилл: `bitrix24-crm-activities`
**Описание:** Работа с делами и таймлайном CRM — звонки, встречи, комментарии, универсальные дела.
**Возможности:**
- Создание дел (звонки, письма, встречи)
- Универсальные дела (todo) в таймлайне
- Комментарии в таймлайне
- Лог-записи
- Обновление дедлайнов и описаний
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `crm.activity.add` | Создать дело | Звонок, встреча, письмо |
| `crm.activity.list` | Список дел | Обзор активностей |
| `crm.activity.todo.add` | Универсальное дело | Todo в таймлайне |
| `crm.activity.todo.update` | Обновить todo | Редактирование |
| `crm.activity.todo.updateDeadline` | Сменить дедлайн | Перенос сроков |
| `crm.activity.todo.updateDescription` | Обновить описание | Уточнение |
| `crm.timeline.comment.add` | Комментарий в таймлайне | Заметки к сделке |
| `crm.timeline.comment.update` | Обновить комментарий | Правки |
| `crm.timeline.logmessage.add` | Лог-запись | Логирование событий |
| `crm.livefeedmessage.add` | Сообщение в ленту CRM | Оповещения |
| `crm.item.list` | Универсальные элементы | Смарт-процессы |
| `crm.item.delete` | Удалить элемент | Очистка |
**Ограничения прав CRM:**
- Доступ определяется ролью пользователя в CRM
- Менеджер видит только свои сделки/лиды
- Руководитель отдела — сделки отдела
- Директор — всё
- Пользовательские роли через настройки прав
---
## Группа 4: Календарь (calendar.*)
### Скилл: `bitrix24-calendar`
**Описание:** Управление событиями календаря — создание встреч, просмотр расписания, поиск свободных слотов.
**Возможности:**
- Создание событий в календаре (встречи, напоминания)
- Получение списка событий за период
- Получение ближайших событий
- Обновление и удаление событий
- Работа с ресурсами (переговорные и т.д.)
- Настройки календаря
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `calendar.event.add` | Создать событие | Новая встреча |
| `calendar.event.get` | Список событий | Расписание на день/неделю |
| `calendar.event.get.nearest` | Ближайшие события | Что скоро |
| `calendar.event.update` | Обновить событие | Перенос, изменение |
| `calendar.event.delete` | Удалить событие | Отмена |
| `calendar.section.get` | Список календарей | Какие календари есть |
| `calendar.section.add` | Создать календарь | Новый календарь |
| `calendar.resource.list` | Список ресурсов | Переговорные |
| `calendar.resource.update` | Обновить ресурс | Управление ресурсами |
| `calendar.settings.get` | Настройки | Конфигурация |
| `calendar.user.settings.get` | Пользовательские настройки | Персонализация |
**Ключевые поля события:**
- `name` — название
- `description` — описание
- `from` / `to` — начало и конец
- `attendees` — участники (массив ID пользователей)
- `location` — место/переговорная
- `importance` — важность
- `is_meeting` — встреча с участниками
- `rrule` — правило повторения
**Примеры использования в сценариях:**
- Поиск свободных слотов для встречи
- Утренний брифинг с расписанием дня
- Автоматическое создание встреч после согласования
- Напоминания о предстоящих событиях
---
## Группа 5: Файлы (disk.*)
### Скилл: `bitrix24-files`
**Описание:** Работа с файлами на Диске Битрикс24 — чтение, загрузка, поиск, организация.
**Возможности:**
- Загрузка файлов в хранилище и папки
- Чтение содержимого папок
- Копирование и перемещение файлов
- Создание папок
- Получение публичных ссылок
- Работа с прикреплёнными файлами из чатов
**API-методы:**
| Метод | Описание | Использование |
|-------|----------|---------------|
| `disk.storage.getchildren` | Файлы в корне хранилища | Обзор файлов |
| `disk.storage.uploadfile` | Загрузить в корень | Новый файл |
| `disk.storage.addfolder` | Создать папку в корне | Организация |
| `disk.folder.getchildren` | Содержимое папки | Обзор |
| `disk.folder.uploadfile` | Загрузить в папку | Новый файл |
| `disk.folder.addsubfolder` | Создать подпапку | Структура |
| `disk.folder.get` | Информация о папке | Метаданные |
| `disk.folder.getexternallink` | Публичная ссылка | Поделиться |
| `disk.folder.moveto` | Переместить папку | Реорганизация |
| `disk.file.get` | Информация о файле | Метаданные |
| `disk.file.copyto` | Копировать файл | Дублирование |
| `disk.file.rename` | Переименовать | Переименование |
| `disk.file.delete` | Удалить файл | Очистка |
| `disk.attachedObject.get` | Прикреплённый файл | Файлы из чатов |
**Примеры использования в сценариях:**
- Поиск документов по описанию
- Каталог файлов проекта
- Автоматическая рассылка отчётов
- Загрузка сгенерированных файлов (КП, отчёты)
---
## Сводная таблица скиллов
| Скилл | Группа | Кол-во методов | Основные сценарии |
|-------|--------|---------------|-------------------|
| `bitrix24-messaging` | Коммуникации | 14 | Дайджесты, рассылки, маршрутизация |
| `bitrix24-users` | Коммуникации | 10 | Поиск коллег, оргструктура |
| `bitrix24-tasks` | Задачи | 15 | Стендапы, контроль, автоматизация |
| `bitrix24-projects` | Задачи | 2 | Управление командой проекта |
| `bitrix24-crm-deals` | CRM | 10 | Воронка, сделки, отчёты |
| `bitrix24-crm-contacts` | CRM | 5 | Клиентская база |
| `bitrix24-crm-leads` | CRM | 5 | Квалификация, лидогенерация |
| `bitrix24-crm-activities` | CRM | 12 | Дела, таймлайн, логирование |
| `bitrix24-calendar` | Календарь | 11 | Встречи, расписание, ресурсы |
| `bitrix24-files` | Файлы | 14 | Документы, отчёты, хранилище |
**Итого: 10 скиллов, ~98 API-методов**
---
## Рекомендуемая группировка для конечного пользователя
Для простоты восприятия скиллы можно объединить в 5 пользовательских блоков:
1. **Чаты и коммуникации** = `bitrix24-messaging` + `bitrix24-users`
2. **Задачи и проекты** = `bitrix24-tasks` + `bitrix24-projects`
3. **CRM** = `bitrix24-crm-deals` + `bitrix24-crm-contacts` + `bitrix24-crm-leads` + `bitrix24-crm-activities`
4. **Календарь** = `bitrix24-calendar`
5. **Файлы** = `bitrix24-files`
---
## Матрица «Сценарий → Скиллы»
| Сценарий | Чаты | Задачи | CRM | Календарь | Файлы |
|----------|------|--------|-----|-----------|-------|
| Утренний брифинг по сделкам | ✅ | | ✅ | ✅ | |
| Подготовка к встрече с клиентом | ✅ | | ✅ | | ✅ |
| Обогащение CRM из переписки | ✅ | | ✅ | | |
| Анализ воронки продаж | | | ✅ | | |
| Напоминания о забытых клиентах | ✅ | | ✅ | | |
| Генерация КП | ✅ | | ✅ | | ✅ |
| Дашборд руководителя | ✅ | ✅ | ✅ | ✅ | |
| Контроль задач подчинённых | ✅ | ✅ | | | |
| Еженедельный отчёт | ✅ | ✅ | ✅ | | ✅ |
| Распределение задач | ✅ | ✅ | | | |
| Стендап-бот | ✅ | ✅ | | | |
| Задачи из обсуждений | ✅ | ✅ | | | |
| Декомпозиция задач | | ✅ | | | |
| Статус проекта | ✅ | ✅ | | | ✅ |
| Ретроспектива | ✅ | ✅ | | | |
| Дайджест пропущенных | ✅ | | | | |
| Мониторинг каналов | ✅ | ✅ | | | |
| Маршрутизация сообщений | ✅ | | | | |
| Объявление в канале | ✅ | | | | |
| Планирование встречи | ✅ | | | ✅ | |
| Подготовка к совещанию | ✅ | ✅ | | ✅ | ✅ |
| Протокол встречи | ✅ | ✅ | | ✅ | |
| Онбординг | ✅ | ✅ | | ✅ | |
| Сбор обратной связи | ✅ | | | | |
| Дни рождения | ✅ | | | ✅ | |
| Контент-план | ✅ | ✅ | | | |
| Рассылка по CRM | ✅ | | ✅ | | |
| Поиск документа | | | | | ✅ |
| Каталог файлов проекта | | ✅ | | | ✅ |
| Рассылка отчётов | ✅ | | | | ✅ |
| Квалификация лидов | | | ✅ | | |
| Отчёт по воронке | | | ✅ | | ✅ |
| Мониторинг дублей | | | ✅ | | |
| Персональный брифинг | ✅ | ✅ | ✅ | ✅ | |
| Второй мозг | ✅ | | | | ✅ |
| Трекер привычек | ✅ | | | ✅ | |
| AI-секретарь | ✅ | ✅ | ✅ | ✅ | ✅ |

View File

@@ -0,0 +1,593 @@
---
name: bitrix24
description: >
Work with Bitrix24 (Битрикс24) via REST API and MCP documentation server. Triggers on:
CRM — "сделки", "контакты", "лиды", "воронка", "клиенты", "deals", "contacts", "leads", "pipeline";
Tasks — "задачи", "мои задачи", "просроченные", "создай задачу", "tasks", "overdue", "to-do";
Calendar — "расписание", "встречи", "календарь", "schedule", "meetings", "events";
Chat — "чаты", "сообщения", "уведомления", "написать", "notifications", "messages";
Channels — "каналы", "канал", "объявления", "подписчики", "channels", "announcements", "subscribers";
Open Lines — "открытые линии", "поддержка", "обращения", "клиентские чаты", "операторы",
"омниканал", "виджет чата", "open lines", "support", "customer chat", "helpdesk", "operator";
Projects — "проекты", "рабочие группы", "projects", "workgroups";
Time — "рабочее время", "кто на работе", "учёт времени", "timeman", "work status";
Drive — "файлы", "документы", "диск", "files", "documents", "drive";
Structure — "сотрудники", "отделы", "структура", "подчинённые", "departments", "employees", "org structure";
Feed — "лента", "новости", "объявления", "feed", "announcements";
Scenarios — "утренний брифинг", "morning briefing", "еженедельный отчёт", "weekly report",
"статус команды", "что у меня сегодня", "итоги дня", "план на день", "воронка продаж",
"расскажи про клиента", "подготовь к встрече", "как работает отдел".
metadata:
openclaw:
requires:
bins:
- python3
mcp:
- url: https://mcp-dev.bitrix24.tech/mcp
transport: streamable_http
tools:
- bitrix-search
- bitrix-app-development-doc-details
- bitrix-method-details
- bitrix-article-details
- bitrix-event-details
emoji: "B24"
homepage: https://github.com/rsvbitrix/bitrix24-skill
aliases:
- Bitrix24
- bitrix24
- Bitrix
- bitrix
- b24
- Битрикс24
- битрикс24
- Битрикс
- битрикс
tags:
- bitrix24
- bitrix
- b24
- crm
- tasks
- calendar
- drive
- chat
- messenger
- im
- webhook
- oauth
- mcp
- Битрикс24
- CRM
- задачи
- чат
- проекты
- группы
- лента
- рабочее время
- timeman
- socialnetwork
- feed
- projects
- workgroups
- org structure
- smart process
- смарт-процесс
- products
- товары
- каталог
- quotes
- предложения
- invoices
- счета
- open lines
- openlines
- imopenlines
- открытые линии
- поддержка
- обращения
- операторы
- омниканал
- helpdesk
- landing
- sites
- сайты
- лендинги
---
# Bitrix24
## STOP — Read These Rules Before Doing Anything
You are talking to a business person (company director), NOT a developer. They do not know what an API is. They do not want to see technical details. Every violation of these rules makes the user angry.
### Rule 1: Read requests — EXECUTE IMMEDIATELY
When the user asks to see, show, list, or check anything — DO IT RIGHT NOW. Do not ask questions. Do not ask for confirmation. Do not offer choices. Just call the Bitrix24 methods and show the result.
User says "дай расписание на среду" → you IMMEDIATELY:
1. Call `user.current` to get user ID and timezone
2. Call `calendar.event.get` for that date (read `references/calendar.md` for exact syntax)
3. Call `tasks.task.list` with deadline filter for that date (read `references/tasks.md`)
4. Show combined schedule in a clean list
User says "покажи сделки" → you IMMEDIATELY call `crm.deal.list` and show results.
User says "мои задачи" → you IMMEDIATELY call `tasks.task.list` and show results.
### Rule 2: NEVER show technical details
These words are FORBIDDEN in your replies to the user:
API, REST, webhook, MCP, endpoint, scope, token, curl, JSON, method, parameter, SDK, OAuth, calendar.event.get, tasks.task.list, crm.deal.list, bitrix24_call.py, config.json
WRONG replies (NEVER do this):
- "Используем ваш webhook URL bitrix24.team/rest/5/..." — FORBIDDEN
- "Вызовем calendar.get или calendar.event.get..." — FORBIDDEN
- "Пришлите экспорт календаря (ICS/CSV)..." — FORBIDDEN
- "Подтвердите часовой пояс..." — FORBIDDEN
- "Подтвердите источник данных..." — FORBIDDEN
- "Как предпочитаете продолжить?" — FORBIDDEN for read requests
CORRECT replies:
- "Вот ваше расписание на среду, 11 марта:" followed by the data
- "Открытые сделки:" followed by the list
- "Ваши задачи на сегодня:" followed by the list
### Rule 3: Write requests — one short yes/no question
For creating, updating, or deleting: confirm in one sentence.
- CORRECT: "Создать сделку «Поставка» на 500 000 ₽?"
- WRONG: "Вызовем crm.deal.add с параметрами..."
### Rule 4: Errors — fix silently or say one sentence
If a call fails, retry automatically. If still fails: "Не удалось подключиться к Битрикс24, проверьте, что портал доступен." Nothing else.
### Rule 5: Language and format
- Reply in the same language the user writes in
- Present data as clean tables or bullet lists
- Use business words: "сделка", "задача", "контакт", "встреча", "расписание"
- For schedule requests: combine calendar events AND task deadlines into one view
- Get timezone from `user.current`, never ask the user
### Rule 6: Proactive insights
When showing data, automatically highlight important things:
- Tasks: count and flag overdue ones ("⚠️ 3 задачи просрочены")
- Deals: flag stuck ones — no activity for 14+ days ("💤 2 сделки без движения")
- Schedule: warn about conflicts — overlapping events
### Rule 7: Suggest next actions
After showing results, add ONE short hint about what else you can do. Keep it to one line.
- After schedule: "Могу перенести встречу или добавить задачу."
- After tasks: "Могу отметить задачу выполненной или создать новую."
- After deals: "Могу показать детали по сделке или создать новую."
- After contacts: "Могу найти сделки этого контакта или добавить задачу."
### Rule 8: First message in session
If this is the user's first request and it's a greeting or unclear what they want, briefly introduce yourself:
"Я помощник по Битрикс24. Могу показать расписание, задачи, сделки, контакты или отчёт по команде. Что интересно?"
## Ready-Made Scenarios
Use these when the user's request matches. Execute ALL calls, then present combined result.
### Morning briefing ("что у меня сегодня?", "утренний брифинг", "дай обзор")
Use batch call for speed:
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'calendar=calendar.event.get.nearest?type=user&ownerId=<ID>&forCurrentUser=Y&days=1' \
--cmd 'tasks=tasks.task.list?filter[RESPONSIBLE_ID]=<ID>&filter[!STATUS]=5&filter[<=DEADLINE]=<today_end>' \
--cmd 'deals=crm.deal.list?filter[ASSIGNED_BY_ID]=<ID>&filter[STAGE_SEMANTIC_ID]=P&select[]=ID&select[]=TITLE&select[]=OPPORTUNITY&select[]=STAGE_ID' \
--json
```
Present as:
- 📅 Встречи сегодня (from calendar)
- ✅ Задачи на сегодня + просроченные (from tasks, flag overdue)
- 💰 Активные сделки (from deals, flag stuck)
### Weekly report ("итоги недели", "еженедельный отчёт")
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'done=tasks.task.list?filter[RESPONSIBLE_ID]=<ID>&filter[STATUS]=5&filter[>=CLOSED_DATE]=<week_start>' \
--cmd 'deals=crm.deal.list?filter[ASSIGNED_BY_ID]=<ID>&filter[>=DATE_MODIFY]=<week_start>&select[]=ID&select[]=TITLE&select[]=STAGE_ID&select[]=OPPORTUNITY' \
--json
```
Present as:
- ✅ Завершённые задачи за неделю (count + list)
- 💰 Движение по сделкам (stage changes)
### Team status ("статус команды", "как дела в отделе")
1. Get department: `department.get` with user's department
2. Get employees: `im.department.employees.get`
3. Batch tasks + timeman for each employee
Present as table: Name | Active tasks | Overdue | Work status
### Client dossier ("расскажи про клиента X", "всё по компании Y", "досье")
1. Find contact/company by name → `crm.contact.list` filter `%LAST_NAME` or `crm.company.list` filter `%TITLE`
2. Batch:
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'deals=crm.deal.list?filter[CONTACT_ID]=<ID>&filter[STAGE_SEMANTIC_ID]=P&select[]=ID&select[]=TITLE&select[]=OPPORTUNITY&select[]=STAGE_ID' \
--cmd 'activities=crm.activity.list?filter[OWNER_TYPE_ID]=3&filter[OWNER_ID]=<ID>&select[]=ID&select[]=SUBJECT&select[]=DEADLINE&order[DEADLINE]=desc' \
--json
```
Present as:
- 👤 Контакт — имя, компания, телефон, email
- 💰 Сделки — список с суммами и стадиями
- 📋 Последние действия — звонки, письма, встречи
- 💡 Подсказка: "Могу создать задачу по этому клиенту или запланировать звонок."
### Meeting prep ("подготовь к встрече", "что за встреча в 14:00")
1. Get today's events → `calendar.event.get` for today
2. Find the matching event by time or name
3. Get attendee info → `user.get` for each attendee ID
4. Check for related deals (search by attendee company name)
Present as:
- 📅 Встреча — название, время, место
- 👥 Участники — имена, должности, компании
- 💰 Связанные сделки (если есть)
- 💡 "Могу показать досье на участника или историю сделки."
### Day results ("итоги дня", "что я сделал", "мой отчёт за день")
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'tasks=tasks.task.list?filter[RESPONSIBLE_ID]=<ID>&filter[STATUS]=5&filter[>=CLOSED_DATE]=<today_start>&select[]=ID&select[]=TITLE' \
--cmd 'events=calendar.event.get?type=user&ownerId=<ID>&from=<today_start>&to=<today_end>' \
--json
```
Also call `crm.stagehistory.list` with `filter[>=CREATED_TIME]=<today_start>` for deal movements.
Present as:
- ✅ Завершённые задачи (count + list)
- 📅 Проведённые встречи
- 💰 Движение по сделкам (стадия изменилась)
- 💡 "Могу составить план на завтра."
### Sales pipeline ("воронка", "как работает отдел продаж", "продажи")
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'active=crm.deal.list?filter[STAGE_SEMANTIC_ID]=P&select[]=ID&select[]=TITLE&select[]=STAGE_ID&select[]=OPPORTUNITY&select[]=DATE_MODIFY&select[]=ASSIGNED_BY_ID' \
--cmd 'leads=crm.lead.list?filter[>=DATE_CREATE]=<week_start>&select[]=ID&select[]=TITLE&select[]=SOURCE_ID&select[]=DATE_CREATE' \
--json
```
Present as:
- 📊 Воронка — сделки по стадиям с суммами
- 💤 Зависшие — без движения 14+ дней
- 🆕 Новые лиды за неделю
- 💡 "Могу показать детали по сделке или назначить задачу менеджеру."
### Cross-domain search ("найди...", "кто отвечает за...", "все по теме...")
When user searches for something, search across multiple entities in parallel:
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'contacts=crm.contact.list?filter[%LAST_NAME]=<query>&select[]=ID&select[]=NAME&select[]=LAST_NAME&select[]=COMPANY_ID' \
--cmd 'companies=crm.company.list?filter[%TITLE]=<query>&select[]=ID&select[]=TITLE' \
--cmd 'deals=crm.deal.list?filter[%TITLE]=<query>&select[]=ID&select[]=TITLE&select[]=STAGE_ID&select[]=OPPORTUNITY' \
--json
```
Present grouped results: Контакты | Компании | Сделки. If only one match — show full details immediately.
---
## Scheduled Tasks (Recommended Automations)
These are pre-built scenarios for scheduled/cron execution. The user can activate them via OpenClaw scheduled tasks.
### Day plan (daily, workdays 08:30)
Build a structured day plan from calendar events and tasks:
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'events=calendar.event.get?type=user&ownerId=<ID>&from=<today_start>&to=<today_end>' \
--cmd 'tasks=tasks.task.list?filter[RESPONSIBLE_ID]=<ID>&filter[<=DEADLINE]=<today_end>&filter[<REAL_STATUS]=5&select[]=ID&select[]=TITLE&select[]=DEADLINE&select[]=STATUS&order[DEADLINE]=asc' \
--json
```
Output format:
```
📋 План на день — <date>
📅 Встречи:
09:00 Планёрка
14:00 Звонок с ООО «Рога и копыта»
16:30 Обзор проекта
✅ Задачи (дедлайн сегодня):
• Подготовить КП для клиента
• Отправить отчёт
⚠️ Просроченные:
• Согласовать договор (дедлайн был 5 марта)
```
### Morning briefing (daily, workdays 09:00)
Day plan (above) PLUS active deals summary and new leads from yesterday:
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'events=calendar.event.get?type=user&ownerId=<ID>&from=<today_start>&to=<today_end>' \
--cmd 'tasks=tasks.task.list?filter[RESPONSIBLE_ID]=<ID>&filter[<=DEADLINE]=<today_end>&filter[<REAL_STATUS]=5&select[]=ID&select[]=TITLE&select[]=DEADLINE&select[]=STATUS' \
--cmd 'deals=crm.deal.list?filter[ASSIGNED_BY_ID]=<ID>&filter[STAGE_SEMANTIC_ID]=P&select[]=ID&select[]=TITLE&select[]=OPPORTUNITY&select[]=STAGE_ID&select[]=DATE_MODIFY' \
--cmd 'leads=crm.lead.list?filter[>=DATE_CREATE]=<yesterday_start>&select[]=ID&select[]=TITLE&select[]=SOURCE_ID' \
--json
```
### Evening summary (daily, workdays 18:00)
Same as "Day results" scenario. Summarize completed tasks, past meetings, deal movements.
### Weekly report (Friday 17:00)
Same as "Weekly report" scenario. Tasks completed + deal pipeline changes for the week.
### Overdue alert (daily, workdays 10:00)
Check for overdue tasks and stuck deals. Send ONLY if there are problems (no spam when all is clean):
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'overdue=tasks.task.list?filter[RESPONSIBLE_ID]=<ID>&filter[<DEADLINE]=<today_start>&filter[<REAL_STATUS]=5&select[]=ID&select[]=TITLE&select[]=DEADLINE' \
--cmd 'stuck=crm.deal.list?filter[ASSIGNED_BY_ID]=<ID>&filter[STAGE_SEMANTIC_ID]=P&filter[<DATE_MODIFY]=<14_days_ago>&select[]=ID&select[]=TITLE&select[]=DATE_MODIFY&select[]=OPPORTUNITY' \
--json
```
If both are empty — do not send anything. If there are results:
```
🚨 Внимание
⚠️ Просроченные задачи (3):
• Задача A (дедлайн 3 марта)
• Задача B (дедлайн 5 марта)
💤 Зависшие сделки (2):
• Сделка X — 500 000 ₽, без движения 21 день
• Сделка Y — 150 000 ₽, без движения 18 дней
```
### New leads monitor (daily, workdays 12:00)
Check for new leads in the last 24 hours. Send only if there are new leads:
```bash
python3 scripts/bitrix24_call.py crm.lead.list \
--param 'filter[>=DATE_CREATE]=<24h_ago>' \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=SOURCE_ID' \
--param 'select[]=NAME' \
--param 'select[]=LAST_NAME' \
--json
```
---
## Setup
The only thing needed is a webhook URL. When the user provides one, save it and verify:
```bash
python3 scripts/bitrix24_call.py user.current --url "<webhook>" --json
```
This saves the webhook to config and calls `user.current` to verify it works. It also caches user_id and timezone in the config for faster subsequent calls. After that, all calls use the saved config automatically.
If the webhook is not configured yet and you need to set it up, read `references/access.md`.
## Making REST Calls
```bash
python3 scripts/bitrix24_call.py <method> --json
```
Examples:
```bash
python3 scripts/bitrix24_call.py user.current --json
python3 scripts/bitrix24_call.py crm.deal.list \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=STAGE_ID' \
--json
```
### Parameters from JSON file
For complex parameters (nested objects, arrays, multi-file uploads), use `--params-file` instead of multiple `--param` flags. This avoids shell escaping issues:
```bash
echo '{"filter": {">=DATE_CREATE": "2025-01-01", "%TITLE": "client"}, "select": ["ID", "TITLE"]}' > /tmp/params.json
python3 scripts/bitrix24_call.py crm.deal.list --params-file /tmp/params.json --json
```
### Auto-pagination
For `.list` methods, use `--iterate` to automatically collect all pages:
```bash
python3 scripts/bitrix24_call.py crm.deal.list \
--param 'filter[STAGE_SEMANTIC_ID]=P' \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--iterate --json
```
Use `--max-items N` to cap the total number of items collected.
### Dry-run mode
Preview what would be called without executing:
```bash
python3 scripts/bitrix24_call.py crm.deal.add \
--param 'fields[TITLE]=Test' \
--dry-run --json
```
### Operation safety
Methods are automatically classified by suffix:
| Type | Suffixes | Required flag |
|------|----------|---------------|
| Read | `.list`, `.get`, `.current`, `.fields` | — |
| Write | `.add`, `.update`, `.set`, `.start`, `.complete`, `.attach`, `.send` | `--confirm-write` |
| Destructive | `.delete`, `.remove`, `.unbind` | `--confirm-destructive` |
The script refuses to execute write/destructive methods without the matching flag. This prevents accidental data changes. When writing scenarios, always include the flag:
```bash
python3 scripts/bitrix24_call.py crm.deal.add \
--param 'fields[TITLE]=New deal' \
--confirm-write --json
```
If calls fail, read `references/troubleshooting.md` and run `scripts/check_webhook.py --json`.
## Batch Calls (Multiple Methods in One Request)
For scenarios that need 2+ methods (schedule, briefing, reports), use batch to reduce HTTP calls:
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'tasks=tasks.task.list?filter[RESPONSIBLE_ID]=5' \
--cmd 'deals=crm.deal.list?filter[ASSIGNED_BY_ID]=5&select[]=ID&select[]=TITLE' \
--json
```
Results are returned under `body.result.result` keyed by command name. Use batch whenever you need data from 2+ domains.
### Cross-command references ($result)
In batch, use `$result[name]` to pass the output of one command into another. This allows chaining — e.g., create a company and immediately create a contact linked to it:
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'company=crm.company.add?fields[TITLE]=Acme Corp' \
--cmd 'contact=crm.contact.add?fields[NAME]=John&fields[COMPANY_ID]=$result[company]' \
--cmd 'deal=crm.deal.add?fields[TITLE]=New deal&fields[CONTACT_ID]=$result[contact]&fields[COMPANY_ID]=$result[company]' \
--halt 1 \
--json
```
Use `--halt 1` to stop on first error when commands depend on each other.
**Encoding note:** Batch commands use query string format — Cyrillic and special characters must be URL-encoded. For complex values, prefer `--params-file` with the regular `bitrix24_call.py` instead of batch.
## User ID and Timezone Cache
After the first `user.current` call, user_id and timezone are saved to config. To use cached values without calling `user.current` again:
```python
from bitrix24_config import get_cached_user
user = get_cached_user() # returns {"user_id": 5, "timezone": "Europe/Kaliningrad"} or None
```
If cache is empty, call `user.current` first — it auto-populates the cache.
## Finding the Right Method
When the exact method name is unknown, use MCP docs in this order:
1. `bitrix-search` to find the method, event, or article title.
2. `bitrix-method-details` for REST methods.
3. `bitrix-event-details` for event docs.
4. `bitrix-article-details` for regular documentation articles.
5. `bitrix-app-development-doc-details` for OAuth, install callbacks, BX24 SDK topics.
Do not guess method names from memory when the task is sensitive or the method family is large. Search first.
Then read the domain reference that matches the task:
- `references/crm.md`
- `references/smartprocess.md`
- `references/products.md`
- `references/quotes.md`
- `references/tasks.md`
- `references/chat.md`
- `references/channels.md`
- `references/openlines.md`
- `references/calendar.md`
- `references/drive.md`
- `references/files.md`
- `references/users.md`
- `references/projects.md`
- `references/feed.md`
- `references/timeman.md`
- `references/sites.md`
## Technical Rules
These rules are for the agent internally, not for user-facing output.
- Start with `user.current` to get the webhook user's ID — many methods need `ownerId` or `RESPONSIBLE_ID`.
- Do not invent method names. There is no `calendar.get`, `tasks.list`, etc. Always use exact names from the reference files or MCP search. When unsure, search MCP first.
- Prefer server-side filtering with `filter[...]` and narrow output with `select[]`.
- Filter operators are prefixes on the key: `>=DEADLINE`, `!STATUS`, `>OPPORTUNITY`. Not on the value.
- Use `*.fields` or user-field discovery methods before writing custom fields.
- Expect pagination on list methods via `start` (page size = 50).
- Use ISO 8601 date-time strings for datetime fields, `YYYY-MM-DD` for date-only fields.
- Treat `ACCESS_DENIED`, `insufficient_scope`, `QUERY_LIMIT_EXCEEDED`, and `expired_token` as normal operational cases.
- For `imbot.*`, persist and reuse the same `CLIENT_ID`.
- When a call fails, run `scripts/check_webhook.py --json` before asking the user.
- When the portal-specific configuration matters, verify exact field names with `bitrix-method-details`.
## API Module Restrictions
Not all Bitrix24 REST modules work as expected through a webhook. Some methods exist only for external system integration. Before using methods from these modules, understand their limitations:
- **Telephony (`voximplant.*`, `telephony.*`):** Does NOT make real calls. `telephony.externalcall.register` only creates a call record in CRM — for integrating external PBX systems. Tell the user the REST API cannot initiate voice connections.
- **Mail services (`mailservice.*`):** Configures SMTP/IMAP server settings, cannot send or read emails. No REST API exists for actual email operations.
- **SMS providers (`messageservice.*`):** Registers SMS providers, does not send messages directly. Requires a pre-configured external provider.
- **Connectors (`imconnector.*`):** Infrastructure for connecting external messengers to Open Lines. Requires an external server handler. Useless without a configured integration.
- **Widget embedding (`placement.*`, `userfieldtype.*`):** Registers UI widgets and custom field types. Only works in Marketplace application context, not via webhook.
- **Event handlers (`event.*`):** Registers webhook handlers for events. Requires an external HTTP server to receive notifications.
- **Business processes (`bizproc.*`):** `bizproc.workflow.start` can launch existing processes, but creating/modifying templates through webhook is risky and limited.
If the user requests something from these modules — do not refuse. Explain what the method actually does and what it does NOT do. Let the user decide.
## Domain References
- `references/access.md` — webhook setup, OAuth, install callbacks.
- `references/troubleshooting.md` — diagnostics and self-repair.
- `references/mcp-workflow.md` — MCP tool selection and query patterns.
- `references/crm.md` — deals, contacts, leads, companies, activities.
- `references/smartprocess.md` — smart processes, funnels, stages, universal crm.item API.
- `references/products.md` — product catalog, product rows on deals/quotes/invoices.
- `references/quotes.md` — quotes (commercial proposals), smart invoices.
- `references/tasks.md` — tasks, checklists, comments, planner.
- `references/chat.md` — im, imbot, notifications, dialog history.
- `references/channels.md` — channels (каналы), announcements, subscribers, broadcast messaging.
- `references/openlines.md` — open lines (открытые линии), omnichannel customer communication, operators, sessions.
- `references/calendar.md` — sections, events, attendees, availability.
- `references/drive.md` — storage, folders, files, external links.
- `references/files.md` — file uploads: base64 inline for CRM, disk+attach for tasks.
- `references/users.md` — users, departments, org-structure, subordinates.
- `references/projects.md` — workgroups, projects, scrum, membership.
- `references/feed.md` — activity stream, feed posts, comments.
- `references/timeman.md` — time tracking, work day, absence reports, task time.
- `references/sites.md` — landing pages, sites, blocks, publishing.
Read only the reference file that matches the current task.
Note: Bitrix24 has no REST API for reading or sending emails. `mailservice.*` only configures SMTP/IMAP services.

View File

@@ -0,0 +1,6 @@
{
"ownerId": "kn7f9mnerv85jphv00w8303ms981kgvq",
"slug": "bitrix24",
"version": "0.15.5",
"publishedAt": 1773265717446
}

View File

@@ -0,0 +1,116 @@
# Access and Auth
## Webhook Setup
1. In Bitrix24 open `Developer resources -> Other -> Inbound webhook`.
2. Create a webhook and copy its URL.
3. Save it:
```bash
python3 scripts/bitrix24_call.py user.current --url "<webhook>" --json
```
This saves the webhook to `~/.config/bitrix24-skill/config.json` and verifies it works in one step.
Expected format:
```text
https://your-portal.bitrix24.ru/rest/<user_id>/<webhook>/
```
After that, the skill reuses the saved webhook automatically for all calls.
To replace an existing webhook:
```bash
python3 scripts/save_webhook.py --url "<new-webhook>" --force --check
```
## Agent Setup Behavior
When a user asks for setup help or a REST call fails:
1. Check saved config with `scripts/check_webhook.py --json`
2. If the user already shared a webhook in the conversation, save it and retry
3. Only ask the user for a webhook if no saved config exists
Mask the webhook secret in user-facing output.
## Permissions
Grant the permission groups that match the methods you will call.
Recommended full-coverage set:
- CRM
- Tasks
- Calendar
- Disk or Drive
- IM or Chat
- User and department access
## `CLIENT_ID` For Bot Integrations
For `imbot` integrations, Bitrix24 bot registration requires `CLIENT_ID`.
- Provide `CLIENT_ID` when registering the bot
- Persist it as part of the bot credentials
- Pass the same `CLIENT_ID` into all later `imbot.*` calls
- Treat `CLIENT_ID` as a secret
## Official MCP Docs Endpoint
```text
https://mcp-dev.bitrix24.tech/mcp
```
Tools exposed by the server:
- `bitrix-search`
- `bitrix-app-development-doc-details`
- `bitrix-method-details`
- `bitrix-article-details`
- `bitrix-event-details`
## When To Use OAuth Instead Of A Webhook
Use a webhook when:
- you are connecting one portal quickly
- the integration is admin-managed
- you want the shortest setup path
Use OAuth when:
- your service lives outside Bitrix24
- users connect their own portals to your service
- you need renewable tokens instead of a fixed webhook secret
Key official docs:
- Full OAuth: `https://apidocs.bitrix24.ru/settings/oauth/index.html`
- REST call overview: `https://apidocs.bitrix24.ru/sdk/bx24-js-sdk/how-to-call-rest-methods/index.html`
- Install callback: `https://apidocs.bitrix24.ru/settings/app-installation/mass-market-apps/installation-callback.html`
## OAuth Facts From MCP Docs
- Authorization server: `https://oauth.bitrix24.tech/`
- Authorization starts at `https://portal.bitrix24.com/oauth/authorize/`
- Temporary authorization `code` is valid for 30 seconds
- Token exchange at `https://oauth.bitrix24.tech/oauth/token/`
- Returns `access_token`, `refresh_token`, `client_endpoint`, `server_endpoint`, `scope`
Useful MCP titles for auth topics:
- `Полный протокол авторизации OAuth 2.0`
- `Упрощенный вариант получения токенов OAuth 2.0`
- `Вызов методов REST`
- `Callback установки`
## Install Callback For UI-Less Apps
If you build a local or UI-less app, Bitrix24 can POST OAuth credentials to an install callback URL. That flow is documented in `Callback установки`.
- Save the received `access_token` and `refresh_token`
- Refresh access tokens on your backend
- Do not rely on browser-side JS install helpers for the callback flow

View File

@@ -0,0 +1,107 @@
# Calendar
Use this file for personal calendars, group calendars, meetings, sections, recurrence, and availability checks.
## Core Methods
- `calendar.event.get` — list events in a date range (requires `type` and `ownerId`)
- `calendar.event.get.nearest` — list upcoming events (simplest way to get schedule)
- `calendar.event.getbyid` — get one event by ID
- `calendar.event.add` — create event
- `calendar.event.update` — update event
- `calendar.event.delete` — delete event
- `calendar.section.get` — list calendars (sections)
- `calendar.section.add` — create calendar
- `calendar.accessibility.get` — check user availability
- `calendar.meeting.status.get` — get current user's meeting participation status
- `calendar.resource.list` — list calendar resources
There is NO `calendar.get` or `calendar.list` method. Always use the full method names above.
## Critical: Required Parameters
`calendar.event.get` requires two mandatory parameters:
- `type` — one of: `user`, `group`, `company_calendar`
- `ownerId` — user ID for `user` type, group ID for `group`, `0` for `company_calendar`
Without these, the call fails with `ERROR_METHOD_NOT_FOUND` or similar.
`from` and `to` use date format `YYYY-MM-DD` (not datetime). Defaults: `from` = 1 month ago, `to` = 3 months ahead.
## Common Use Cases
### Show schedule via batch (preferred — one HTTP call)
Combine calendar + tasks in one request:
```bash
python3 scripts/bitrix24_batch.py \
--cmd 'events=calendar.event.get?type=user&ownerId=1&from=2026-03-11&to=2026-03-11' \
--cmd 'tasks=tasks.task.list?filter[RESPONSIBLE_ID]=1&filter[>=DEADLINE]=2026-03-11T00:00:00&filter[<=DEADLINE]=2026-03-11T23:59:59&select[]=ID&select[]=TITLE&select[]=DEADLINE&select[]=STATUS' \
--json
```
### Show user's schedule for a specific day
Get user ID from cached config or `user.current`, then query events:
```bash
python3 scripts/bitrix24_call.py user.current --json
python3 scripts/bitrix24_call.py calendar.event.get \
--param 'type=user' \
--param 'ownerId=1' \
--param 'from=2026-03-10' \
--param 'to=2026-03-10' \
--json
```
### Show upcoming events (easiest for "what's on my schedule")
```bash
python3 scripts/bitrix24_call.py calendar.event.get.nearest \
--param 'type=user' \
--param 'ownerId=1' \
--param 'days=7' \
--param 'forCurrentUser=true' \
--json
```
### Check availability before scheduling
```bash
python3 scripts/bitrix24_call.py calendar.accessibility.get \
--param 'users[]=1' \
--param 'users[]=2' \
--param 'from=2026-03-10' \
--param 'to=2026-03-11' \
--json
```
### Create an event
```bash
python3 scripts/bitrix24_call.py calendar.event.add \
--param 'type=user' \
--param 'ownerId=1' \
--param 'name=Team Meeting' \
--param 'from=2026-03-10T10:00:00' \
--param 'to=2026-03-10T11:00:00' \
--json
```
## Working Rules
- Always pass `type` and `ownerId` for `calendar.event.get`.
- Use `calendar.event.get.nearest` for "show my schedule" — it needs fewer parameters.
- Get user ID from `user.current` first if you don't know it.
- Check `calendar.accessibility.get` before proposing meeting slots.
- For read-only requests, execute immediately — do not ask permission.
- One retry on first failure, then report blocker.
## Good MCP Queries
- `calendar event get nearest`
- `calendar accessibility`
- `calendar section`
- `calendar resource booking`

View File

@@ -0,0 +1,202 @@
# Channels (Каналы)
Use this file for Bitrix24 channels — broadcast-style chats where only owners/managers post and subscribers read.
Channels are a special chat type (`type: openChannel`, `ENTITY_TYPE: ANNOUNCEMENT`).
They use standard `im.*` methods — there is no separate `im.channel.*` API.
## How Channels Differ From Chats
| Feature | Chat | Channel |
|---------|------|---------|
| Who can post | All members | Only owner + managers |
| `type` in API | `chat` / `open` | `openChannel` |
| Create with | `TYPE=CHAT` or `TYPE=OPEN` | `ENTITY_TYPE=ANNOUNCEMENT` |
| List only these | `SKIP_CHAT=N` | `ONLY_CHANNEL=Y` |
| Join | By invite | Self-subscribe (open) or invite |
## Core Methods
All use the same `im.*` family:
Create & manage:
- `im.chat.add` — create channel (with `ENTITY_TYPE=ANNOUNCEMENT`)
- `im.chat.updateTitle` — rename channel
- `im.chat.setOwner` — transfer ownership
- `im.chat.mute` — mute/unmute notifications
List & find:
- `im.recent.list` — list subscribed channels (`ONLY_CHANNEL=Y`)
- `im.dialog.get` — get channel info (returns `type: openChannel`)
- `im.counters.get` — unread counters (includes channel counters)
Messages:
- `im.message.add` — post to channel (owner/managers only)
- `im.dialog.messages.get` — read channel history
- `im.dialog.messages.search` — search messages in channel
Subscribers:
- `im.chat.user.add` — add subscribers
- `im.chat.user.delete` — remove subscribers
- `im.chat.user.list` — list subscribers
- `im.chat.leave` — unsubscribe (current user leaves)
Pin & hide:
- `im.recent.pin` — pin channel at top of list
- `im.recent.hide` — hide channel from recent list
## Common Use Cases
### Create a channel
```bash
python3 scripts/bitrix24_call.py im.chat.add \
--param 'TYPE=OPEN' \
--param 'ENTITY_TYPE=ANNOUNCEMENT' \
--param 'TITLE=Company News' \
--param 'DESCRIPTION=Official company announcements' \
--param 'USERS[]=1' \
--param 'USERS[]=2' \
--param 'MESSAGE=Welcome to the channel!' \
--json
```
Returns channel chat ID. Creator becomes the owner.
### List all channels
```bash
python3 scripts/bitrix24_call.py im.recent.list \
--param 'ONLY_CHANNEL=Y' \
--param 'LIMIT=50' \
--json
```
Filter channels with `ONLY_CHANNEL=Y`. Each result contains `type: openChannel`.
### Post to a channel
```bash
python3 scripts/bitrix24_call.py im.message.add \
--param 'DIALOG_ID=chat42' \
--param 'MESSAGE=Important update: new office hours starting Monday' \
--json
```
Only the channel owner and managers can post. Regular subscribers get `ACCESS_ERROR`.
### Read channel messages
```bash
python3 scripts/bitrix24_call.py im.dialog.messages.get \
--param 'DIALOG_ID=chat42' \
--param 'LIMIT=20' \
--json
```
### Get channel info
```bash
python3 scripts/bitrix24_call.py im.dialog.get \
--param 'DIALOG_ID=chat42' \
--json
```
Returns object with `type: openChannel`, owner, name, description, subscriber count.
### Add subscribers to channel
```bash
python3 scripts/bitrix24_call.py im.chat.user.add \
--param 'CHAT_ID=42' \
--param 'USERS[]=5' \
--param 'USERS[]=6' \
--param 'USERS[]=7' \
--json
```
### List channel subscribers
```bash
python3 scripts/bitrix24_call.py im.chat.user.list \
--param 'CHAT_ID=42' \
--json
```
### Unsubscribe from channel
```bash
python3 scripts/bitrix24_call.py im.chat.leave \
--param 'CHAT_ID=42' \
--json
```
### Rename a channel
```bash
python3 scripts/bitrix24_call.py im.chat.updateTitle \
--param 'CHAT_ID=42' \
--param 'TITLE=Company Updates 2026' \
--json
```
### Mute channel notifications
```bash
python3 scripts/bitrix24_call.py im.chat.mute \
--param 'CHAT_ID=42' \
--param 'MUTE=Y' \
--json
```
`MUTE=Y` to mute, `MUTE=N` to unmute.
### Pin channel at top of chat list
```bash
python3 scripts/bitrix24_call.py im.recent.pin \
--param 'DIALOG_ID=chat42' \
--param 'PIN=Y' \
--json
```
## Working Rules
- Channels have NO separate API — use `im.*` methods with `ENTITY_TYPE=ANNOUNCEMENT`.
- Identify channels by `type: openChannel` in responses from `im.dialog.get` or `im.recent.list`.
- Only owner and managers can post (`im.message.add`). Subscribers get `ACCESS_ERROR`.
- To make someone a manager, use `im.chat.setOwner` (transfers ownership) — there is no separate "set manager" method via REST.
- Use `ONLY_CHANNEL=Y` in `im.recent.list` to filter channels from regular chats.
- Channel `DIALOG_ID` format: `chatXXX` (same as regular group chats).
- Subscribers can leave with `im.chat.leave`, but cannot be re-added without owner action.
- Channel types in API: `openChannel` (public), `channel` (private), `generalChannel` (company-wide).
## Known Limitations
### Comments and threads
Comments on channel posts are a UI feature not exposed via REST API. When reading messages with `im.dialog.messages.get`, no thread/comment fields are returned (`REPLY_ID`, `THREAD_ID`, `parent_message_id` — none of these are present). `im.message.add` accepts `REPLY_ID` for sending a reply, but replies are not retrievable via API. If Bitrix24 adds REST methods for threads, we can use them immediately.
### Channel discovery
`im.recent.list` with `ONLY_CHANNEL=Y` returns only channels the current user is **subscribed to**. There is no REST API method to discover channels the user is NOT subscribed to (new or available channels on the portal).
`im.search.chat.list` does NOT search channels — it only returns regular chats (`chat`, `open`, `calendar`, `tasks`, `sonetGroup` types). Searching for a known channel name returns zero channel results.
To discover new channels, users must use the Bitrix24 UI. Once subscribed, the channel appears in `im.recent.list`.
These limitations are current as of March 2026. Check the MCP documentation server (`https://mcp-dev.bitrix24.tech/mcp`) for updates — use `bitrix-search` with queries like `im thread`, `im channel list` to see if new methods have appeared.
## Good MCP Queries
- `im chat add`
- `im recent list`
- `im dialog get`
- `im chat user add`
- `im chat mute`
- `im recent pin`

View File

@@ -0,0 +1,140 @@
# Chat and Notifications
Use this file for messenger dialogs, chats, history, notifications, and file delivery into chats.
> **Channels** (каналы / объявления) — see `references/channels.md`. Channels use the same `im.*` methods but with `ENTITY_TYPE=ANNOUNCEMENT` and `type: openChannel`.
## Separate `im.*` From `imbot.*`
Use `im.*` for normal IM REST methods (webhook-compatible):
- `im.message.add` — send message
- `im.message.update` / `im.message.delete`
- `im.message.share` — create entity (task/event/post) from a message
- `im.chat.add` / `im.chat.get` / `im.chat.update`
- `im.chat.user.add` / `im.chat.user.delete` / `im.chat.user.list`
- `im.dialog.get` / `im.dialog.messages.get`
- `im.dialog.messages.search` — search messages in a specific chat
- `im.dialog.users.list` — list dialog participants
- `im.dialog.read.all` — mark all chats as read
- `im.recent.list` / `im.recent.get`
- `im.dialog.writing` — typing indicator
Use `imbot.*` for bot scenarios (requires `CLIENT_ID`):
- `imbot.message.add` / `imbot.message.update` / `imbot.message.delete`
- `imbot.chat.add` / `imbot.dialog.get`
- `imbot.chat.sendTyping`
Do not mix `im.*` and `imbot.*` — pick the family that matches the integration.
## Notifications
- `im.notify.system.add` — system notification (app context only)
- `im.notify.personal.add` — personal notification (app context only)
- `im.notify.read` — mark notification as read
Important: `im.notify.system.add` and `im.notify.personal.add` work only through an application, not plain webhooks. If you get auth errors, this is likely the reason.
## Dialog Addressing
- `123` — direct dialog with user 123
- `chat456` — group chat 456
- `sg789` — group or project chat
## Common Use Cases
### Send a message to a chat
```bash
python3 scripts/bitrix24_call.py im.message.add \
--param 'DIALOG_ID=chat42' \
--param 'MESSAGE=Hello team' \
--json
```
### Read dialog history
```bash
python3 scripts/bitrix24_call.py im.dialog.messages.get \
--param 'DIALOG_ID=chat42' \
--param 'LIMIT=20' \
--json
```
### Send a Disk file to chat
```bash
python3 scripts/bitrix24_call.py im.disk.file.commit \
--param 'CHAT_ID=42' \
--param 'FILE_ID[]=5249' \
--param 'MESSAGE=Project files' \
--json
```
### Create a group chat
```bash
python3 scripts/bitrix24_call.py im.chat.add \
--param 'TYPE=CHAT' \
--param 'TITLE=Project discussion' \
--param 'USERS[]=1' \
--param 'USERS[]=2' \
--json
```
### Search messages in a chat
```bash
python3 scripts/bitrix24_call.py im.dialog.messages.search \
--param 'CHAT_ID=42' \
--param 'SEARCH_MESSAGE=contract' \
--param 'LIMIT=20' \
--json
```
Supports date filters: `DATE_FROM`, `DATE_TO` (ISO 8601), `DATE` (single day).
Search string must be longer than 2 characters. Returns messages, users, and files.
### Create task from a chat message
```bash
python3 scripts/bitrix24_call.py im.message.share \
--param 'MESSAGE_ID=34261' \
--param 'DIALOG_ID=chat42' \
--param 'TYPE=TASK' \
--json
```
`TYPE` values: `TASK` (task), `CALEND` (calendar event), `POST` (feed post), `CHAT` (forward to chat).
Get `MESSAGE_ID` from `im.dialog.messages.get` or `im.dialog.messages.search`.
### Mark all chats as read
```bash
python3 scripts/bitrix24_call.py im.dialog.read.all --json
```
No parameters needed. Marks all dialogs as read for the current user.
## `CLIENT_ID` for Bots
For `imbot.*` methods:
- Provide `CLIENT_ID` when registering the bot
- Persist it and reuse in every `imbot.*` call
- Treat `CLIENT_ID` as a secret
## Formatting
Bitrix24 chat uses BB-code. Do not double-convert if Markdown is already converted to BB-code.
## Good MCP Queries
- `im message add chat`
- `im message share`
- `im dialog messages search`
- `im dialog read all`
- `imbot message`
- `im dialog messages get`
- `im disk file commit`

View File

@@ -0,0 +1,228 @@
# CRM
Use this file for deals, contacts, companies, leads, activities, and modern CRM item APIs.
## Core Methods
Deals:
- `crm.deal.list` / `crm.deal.get` / `crm.deal.add` / `crm.deal.update` / `crm.deal.delete`
- `crm.deal.fields` — field schema
- `crm.deal.contact.add` / `crm.deal.contact.items.get`
Contacts:
- `crm.contact.list` / `crm.contact.get` / `crm.contact.add` / `crm.contact.update` / `crm.contact.delete`
- `crm.contact.fields`
Companies:
- `crm.company.list` / `crm.company.get` / `crm.company.add` / `crm.company.update` / `crm.company.delete`
Leads:
- `crm.lead.list` / `crm.lead.get` / `crm.lead.add` / `crm.lead.update` / `crm.lead.delete`
- `crm.lead.fields`
Activities (classic):
- `crm.activity.list` / `crm.activity.add` / `crm.activity.update` / `crm.activity.delete`
Timeline — Todo (universal activities in deal/lead/contact timeline):
- `crm.activity.todo.add` — create a todo item in timeline
- `crm.activity.todo.update` — update todo
- `crm.activity.todo.updateDeadline` — change deadline only
- `crm.activity.todo.updateDescription` — change description only
Timeline — Comments & Log:
- `crm.timeline.comment.add` — add comment to entity timeline
- `crm.timeline.comment.update` — edit existing comment
- `crm.timeline.logmessage.add` — add log entry to timeline (for recording events)
CRM Feed:
- `crm.livefeedmessage.add` — post message to CRM activity stream
Stage History:
- `crm.stagehistory.list` — history of stage transitions (deals, leads, invoices)
Modern generalized APIs (smart processes, dynamic types):
- `crm.item.list` / `crm.item.add` / `crm.item.update` / `crm.item.delete`
- `crm.item.batchImport`
## Filter Syntax
CRM list methods use prefix operators:
- `>OPPORTUNITY` — greater than
- `>=DATE_CREATE` — on or after
- `=STAGE_ID` — equals (default without prefix)
- `!STATUS_ID` — not equal
Example: `filter[>OPPORTUNITY]=10000` returns deals with opportunity above 10000.
## Common Use Cases
### List deals with filter
```bash
python3 scripts/bitrix24_call.py crm.deal.list \
--param 'filter[>OPPORTUNITY]=10000' \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=OPPORTUNITY' \
--param 'select[]=STAGE_ID' \
--json
```
### Create a deal
```bash
python3 scripts/bitrix24_call.py crm.deal.add \
--param 'fields[TITLE]=New Deal' \
--param 'fields[OPPORTUNITY]=50000' \
--param 'fields[CURRENCY_ID]=RUB' \
--json
```
### Get field schema before writing
```bash
python3 scripts/bitrix24_call.py crm.deal.fields --json
```
### Add activity to a deal
```bash
python3 scripts/bitrix24_call.py crm.activity.add \
--param 'fields[OWNER_TYPE_ID]=2' \
--param 'fields[OWNER_ID]=123' \
--param 'fields[TYPE_ID]=2' \
--param 'fields[SUBJECT]=Follow-up call' \
--json
```
### Stuck deals (no activity for 14+ days)
Deals in active pipeline with no recent modification — useful for proactive "💤" warnings:
```bash
python3 scripts/bitrix24_call.py crm.deal.list \
--param 'filter[ASSIGNED_BY_ID]=1' \
--param 'filter[STAGE_SEMANTIC_ID]=P' \
--param 'filter[<DATE_MODIFY]=2026-02-22' \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=STAGE_ID' \
--param 'select[]=DATE_MODIFY' \
--param 'select[]=OPPORTUNITY' \
--json
```
`STAGE_SEMANTIC_ID=P` = in progress (active pipeline). `<DATE_MODIFY` = last modified before 14 days ago.
### Add todo to a deal timeline
```bash
python3 scripts/bitrix24_call.py crm.activity.todo.add \
--param 'ownerTypeId=2' \
--param 'ownerId=123' \
--param 'deadline=2026-03-15T15:00:00' \
--param 'title=Follow up with client' \
--param 'description=Call to discuss proposal' \
--param 'responsibleId=5' \
--json
```
`ownerTypeId`: 1=lead, 2=deal, 3=contact, 4=company.
Optional `pingOffsets` (array of minutes for reminders): `--param 'pingOffsets[]=0' --param 'pingOffsets[]=15'`
### Add comment to deal timeline
```bash
python3 scripts/bitrix24_call.py crm.timeline.comment.add \
--param 'fields[ENTITY_ID]=123' \
--param 'fields[ENTITY_TYPE]=deal' \
--param 'fields[COMMENT]=Client confirmed budget approval' \
--json
```
`ENTITY_TYPE` values: `deal`, `lead`, `contact`, `company`, `order`, `quote` (string, lowercase).
### Add log entry to timeline
```bash
python3 scripts/bitrix24_call.py crm.timeline.logmessage.add \
--param 'fields[entityTypeId]=2' \
--param 'fields[entityId]=123' \
--param 'fields[title]=Price changed' \
--param 'fields[text]=Price updated from 100k to 120k after negotiation' \
--param 'fields[iconCode]=info' \
--json
```
Note: `crm.timeline.logmessage.add` uses camelCase field names (`entityTypeId`), not UPPER_CASE.
### Post message to CRM feed
```bash
python3 scripts/bitrix24_call.py crm.livefeedmessage.add \
--param 'fields[POST_TITLE]=Deal update' \
--param 'fields[MESSAGE]=Contract signed with Company X' \
--param 'fields[ENTITYTYPEID]=2' \
--param 'fields[ENTITYID]=123' \
--json
```
### Get stage history for deals
```bash
python3 scripts/bitrix24_call.py crm.stagehistory.list \
--param 'entityTypeId=2' \
--param 'filter[>=CREATED_TIME]=2026-03-01T00:00:00' \
--param 'select[]=OWNER_ID' \
--param 'select[]=STAGE_ID' \
--param 'select[]=CREATED_TIME' \
--json
```
Returns items with `TYPE_ID`: 1=created, 2=intermediate stage, 3=final stage, 5=pipeline change.
`STAGE_SEMANTIC_ID`: P=in progress, S=won, F=lost.
## Entity Type IDs
| ID | Entity |
|----|--------|
| 1 | Lead |
| 2 | Deal |
| 3 | Contact |
| 4 | Company |
| 5 | Invoice (old) |
| 7 | Quote |
| 31 | Smart Invoice (new) |
| 128+ | Custom smart processes |
## Working Rules
- Read `*.fields` before writing custom or portal-specific fields.
- Do not hardcode stage names across portals — pipelines and categories vary.
- Use classic `crm.deal.*` for built-in entities, `crm.item.*` for smart processes.
- Always use `select[]` to limit response size.
- Pagination: page size is 50, use `start=0`, `start=50`, etc.
## Good MCP Queries
- `crm deal list add update`
- `crm contact company`
- `crm lead fields`
- `crm activity`
- `crm activity todo add`
- `crm timeline comment`
- `crm timeline logmessage`
- `crm livefeedmessage`
- `crm stagehistory`
- `crm item smart process`

View File

@@ -0,0 +1,118 @@
# Drive and Disk
Use this file for storage, folder, file, and external-link operations.
## Core Methods
Storages:
- `disk.storage.getlist` — list all storages
- `disk.storage.get` — get storage by ID
- `disk.storage.getchildren` — list root contents
- `disk.storage.addfolder` — create folder in root
- `disk.storage.uploadfile` — upload file to root
Folders:
- `disk.folder.get` — folder metadata
- `disk.folder.getchildren` — list folder contents
- `disk.folder.addsubfolder` — create subfolder
- `disk.folder.uploadfile` — upload file to folder
- `disk.folder.getexternallink` — public link
- `disk.folder.moveto` — move folder
- `disk.folder.deletetree` — delete folder permanently
Files:
- `disk.file.get` — file metadata (includes `DOWNLOAD_URL`)
- `disk.file.copyto` — copy to folder
- `disk.file.moveto` — move to folder
- `disk.file.rename` — rename file
- `disk.file.uploadversion` — upload new version
- `disk.file.delete` — delete permanently
Attached objects (files linked to tasks, chats, CRM entities):
- `disk.attachedObject.get` — get info about an attached file (from task, chat, etc.)
Chat handoff:
- `im.disk.file.commit` — send existing Disk file to chat
## Common Use Cases
### List all storages
```bash
python3 scripts/bitrix24_call.py disk.storage.getlist --json
```
### Browse storage contents
```bash
python3 scripts/bitrix24_call.py disk.storage.getchildren \
--param 'id=1' \
--json
```
### Browse folder contents
```bash
python3 scripts/bitrix24_call.py disk.folder.getchildren \
--param 'id=42' \
--json
```
### Get file metadata
```bash
python3 scripts/bitrix24_call.py disk.file.get \
--param 'id=9043' \
--json
```
### Upload file to a folder
```bash
python3 scripts/bitrix24_call.py disk.folder.uploadfile \
--param 'id=42' \
--param 'data[NAME]=document.txt' \
--param 'fileContent[0]=document.txt' \
--param 'fileContent[1]=<base64_content>' \
--json
```
### Get public link for a folder
```bash
python3 scripts/bitrix24_call.py disk.folder.getexternallink \
--param 'id=42' \
--json
```
### Get attached file info (from task, chat, etc.)
```bash
python3 scripts/bitrix24_call.py disk.attachedObject.get \
--param 'id=495' \
--json
```
Returns `OBJECT_ID` (disk file ID), `NAME`, `SIZE`, `DOWNLOAD_URL`, `MODULE_ID`, `ENTITY_TYPE`, `ENTITY_ID`.
Get the attachment ID from methods like `tasks.task.get` (UF_TASK_WEBDAV_FILES) or chat message files.
## Working Rules
- Find file IDs from `disk.storage.getchildren` or `disk.folder.getchildren`.
- Use `disk.file.get` to inspect metadata before download or move.
- `DOWNLOAD_URL` requires authentication — it's not a public link.
- Use `disk.folder.getexternallink` for sharing.
- Use `disk.attachedObject.get` to get info about files attached to tasks or messages.
## Good MCP Queries
- `disk storage folder file`
- `disk file upload version`
- `disk external link`
- `disk attachedObject get`
- `im disk file commit`

View File

@@ -0,0 +1,112 @@
# Activity Stream (Feed)
Use this file for the Bitrix24 news feed — posting, reading, commenting, and sharing messages in the company feed.
Scope: `log`
## Core Methods
- `log.blogpost.get` — read feed posts (filter by `POST_ID` or `LOG_RIGHTS`)
- `log.blogpost.add` — post a message to the feed
- `log.blogpost.update` — update a feed post
- `log.blogpost.share` — add recipients to an existing post
- `log.blogcomment.add` — add comment to a feed post
- `log.blogcomment.user.get` — get comments by user for a post
Also related (CRM-specific):
- `crm.livefeedmessage.add` — post to CRM entity feed
## Recipients (DEST / LOG_RIGHTS)
Feed messages use recipient codes:
- `UA` — all authorized users
- `U<id>` — specific user (e.g. `U1`, `U42`)
- `SG<id>` — workgroup/project (e.g. `SG15`)
- `DR<id>` — department including subdepartments (e.g. `DR5`)
Default recipient: `['UA']` (everyone).
## Common Use Cases
### Read recent feed posts
```bash
python3 scripts/bitrix24_call.py log.blogpost.get --json
```
### Read a specific post
```bash
python3 scripts/bitrix24_call.py log.blogpost.get \
--param 'POST_ID=755' \
--json
```
### Read posts visible to a specific group
```bash
python3 scripts/bitrix24_call.py log.blogpost.get \
--param 'LOG_RIGHTS=SG15' \
--json
```
### Post a message to the feed
```bash
python3 scripts/bitrix24_call.py log.blogpost.add \
--param 'POST_MESSAGE=Hello team!' \
--param 'DEST[]=UA' \
--json
```
### Post to a specific department
```bash
python3 scripts/bitrix24_call.py log.blogpost.add \
--param 'POST_TITLE=Department Update' \
--param 'POST_MESSAGE=Monthly results are ready.' \
--param 'DEST[]=DR5' \
--json
```
### Post to a project group
```bash
python3 scripts/bitrix24_call.py log.blogpost.add \
--param 'POST_MESSAGE=Sprint review tomorrow at 10:00' \
--param 'DEST[]=SG15' \
--json
```
### Add a comment
```bash
python3 scripts/bitrix24_call.py log.blogcomment.add \
--param 'POST_ID=755' \
--param 'TEXT=Great work!' \
--json
```
### Share a post with additional recipients
```bash
python3 scripts/bitrix24_call.py log.blogpost.share \
--param 'POST_ID=755' \
--param 'DEST[]=U42' \
--json
```
## Working Rules
- `log.blogpost.get` without `POST_ID` returns recent posts for current user.
- Posts are visible only to specified recipients. Use `DEST` to control visibility.
- Comments inherit visibility from the parent post.
- `IMPORTANT=Y` flag makes a post pinnable with an optional expiration date.
## Good MCP Queries
- `log blogpost add get`
- `log blogcomment`
- `crm livefeedmessage`

View File

@@ -0,0 +1,116 @@
# File Uploads
Bitrix24 REST API has two methods for uploading files. Which one to use depends on the entity.
## Method 1: Inline Base64 (CRM entities)
CRM entity fields (leads, deals, contacts, companies) accept files directly as `["filename", "base64_content"]`.
### Single file in a CRM field
```bash
# Encode file to base64
B64=$(python3 -c "import base64, sys; print(base64.b64encode(open(sys.argv[1],'rb').read()).decode())" /path/to/photo.jpg)
# Attach to contact
python3 scripts/bitrix24_call.py crm.contact.update \
--param 'ID=123' \
--param "fields[PHOTO][0]=photo.jpg" \
--param "fields[PHOTO][1]=$B64" \
--confirm-write \
--json
```
### Multiple files in a user field
For UF (user field) that accepts multiple files, pass an array of `[name, base64]` pairs.
Use `--params-file` for this — it's cleaner than inline params:
```json
{
"ID": 123,
"fields": {
"UF_CRM_FILES": [
["doc1.pdf", "<base64>"],
["doc2.pdf", "<base64>"]
]
}
}
```
```bash
python3 scripts/bitrix24_call.py crm.deal.update \
--params-file /tmp/deal_files.json \
--confirm-write \
--json
```
## Method 2: Disk Upload + Attach (Tasks and other entities)
Tasks do NOT accept base64 directly. Use the two-step process:
### Step 1: Upload file to Disk
First, find a folder to upload to:
```bash
# List storage root (user's personal storage)
python3 scripts/bitrix24_call.py disk.storage.getchildren \
--param 'id=1' \
--json
```
Then upload:
```bash
B64=$(python3 -c "import base64, sys; print(base64.b64encode(open(sys.argv[1],'rb').read()).decode())" /path/to/report.pdf)
python3 scripts/bitrix24_call.py disk.folder.uploadfile \
--param 'id=FOLDER_ID' \
--param 'fileContent[0]=report.pdf' \
--param "fileContent[1]=$B64" \
--param 'data[NAME]=report.pdf' \
--confirm-write \
--json
```
The response contains the disk file object with `ID`.
### Step 2: Attach to task
```bash
python3 scripts/bitrix24_call.py tasks.task.files.attach \
--param 'taskId=456' \
--param 'fileId=DISK_FILE_ID' \
--confirm-write \
--json
```
Note: the method is `tasks.task.files.attach` (with `s` in `files`), not `tasks.task.file.attach`.
## Chat file uploads
For sending files in chat, use the disk upload + `im.disk.file.commit` approach. See `references/chat.md` for details.
## Size limits
- Inline base64 in CRM fields: limited by POST request size (typically ~30 MB after encoding)
- Disk uploads: subject to portal disk quota
- Attachment objects in Open Lines: max 30 KB (metadata only, not file content)
## When to use which method
| Entity | Method | Notes |
|--------|--------|-------|
| CRM lead/deal/contact/company | Inline base64 | Pass `["name", "base64"]` in field |
| Task | Disk + attach | `disk.folder.uploadfile``tasks.task.files.attach` |
| Chat message | Disk + commit | `disk.folder.uploadfile``im.disk.file.commit` |
| Feed post | Disk + attach | Upload first, reference disk file ID |
## Good MCP Queries
- `disk folder uploadfile`
- `tasks task files attach`
- `im disk file commit`
- `crm contact photo`

View File

@@ -0,0 +1,82 @@
# MCP Workflow
Use MCP as the source of truth for Bitrix24 docs.
## Connection Facts
- Endpoint: `https://mcp-dev.bitrix24.tech/mcp`
- This is a streamable MCP endpoint, not a regular documentation page
- Without `Accept: text/event-stream`, the endpoint rejected requests with `406 Not Acceptable`
- `initialize` reported:
- protocol version `2025-03-26`
- server `b24-dev-mcp`
- version `0.2.0`
## Recommended Order
1. Search.
2. Resolve the exact method, event, or article title.
3. Fetch details.
4. Only then make the REST call or write the example.
## Search Tool
Use `bitrix-search` first.
Helpful arguments:
- `query`: natural-language search text
- `limit`: cap the number of matches
- `doc_type`: narrow the result space
Supported `doc_type` values confirmed from MCP:
- `method`
- `event`
- `other`
- `app_development_docs`
## Detail Tools
- `bitrix-method-details`: exact REST method name only
- `bitrix-event-details`: exact event name only
- `bitrix-article-details`: exact non-method article title
- `bitrix-app-development-doc-details`: exact app-development title
## Practical Search Patterns
Use queries close to the user task:
- `crm deal add`
- `task checklist`
- `task comment`
- `im message chat`
- `calendar event section`
- `disk storage folder file`
- `user department structure`
- `oauth install callback`
Then pass the exact name from search into the appropriate details tool.
## Patterns Confirmed During Analysis
The current MCP server returned strong matches for these families:
- CRM: `crm.deal.*`, `crm.contact.*`, `crm.company.*`, `crm.lead.*`, `crm.activity.*`, `crm.item.*`
- Tasks: `tasks.task.*`, `task.checklistitem.*`, `task.commentitem.*`, `task.planner.getlist`
- Chat: `im.message.*`, `im.chat.*`, `im.dialog.*`, `im.recent.*`, `im.notify.*`, `imbot.*`
- Calendar: `calendar.event.*`, `calendar.section.*`, `calendar.accessibility.get`
- Drive: `disk.storage.*`, `disk.folder.*`, `disk.file.*`, `im.disk.file.commit`
- Users and org structure: `user.*`, `department.*`, `im.department.*`, `im.search.*`
## Avoid Stale Memory
Do not trust memory alone when:
- a method family is large
- a scenario is scope-sensitive
- a method may be deprecated
- an example mixes `im.*` and `imbot.*`
- a field name is portal-specific
Re-run `bitrix-method-details` in those cases.

View File

@@ -0,0 +1,370 @@
# Open Lines (Открытые линии)
Use this file for Bitrix24 Open Lines — omnichannel customer communication (website chat, Telegram, WhatsApp, Facebook, VK, etc.). Open Lines connect external messaging channels to the portal and route incoming conversations to operators.
Scope: `imopenlines`. All methods require this scope in the webhook.
## Core Concepts
- **Line (config)** — an Open Line configuration: name, CRM binding, auto-actions, queue of operators
- **Session** — one conversation between a customer and operator(s), from first message to close
- **Operator** — a portal user who answers customer messages
- **Dialog** — the chat between operator and customer within a session
- **Connector** — external channel (Telegram, WhatsApp, Viber, Facebook, website widget, etc.)
## Line Management
### List all lines
```bash
python3 scripts/bitrix24_call.py imopenlines.config.list.get --json
```
With filter and queue info:
```bash
python3 scripts/bitrix24_call.py imopenlines.config.list.get \
--param 'PARAMS[filter][ACTIVE]=Y' \
--param 'PARAMS[select][]=ID' \
--param 'PARAMS[select][]=LINE_NAME' \
--param 'PARAMS[select][]=ACTIVE' \
--param 'PARAMS[select][]=CRM' \
--param 'OPTIONS[QUEUE]=Y' \
--json
```
Supports pagination (`next`, `total`). Fields available for select/filter/order: see `imopenlines.config.add` docs.
### Get line by ID
```bash
python3 scripts/bitrix24_call.py imopenlines.config.get \
--param 'CONFIG_ID=1' \
--param 'WITH_QUEUE=Y' \
--param 'SHOW_OFFLINE=Y' \
--json
```
- `CONFIG_ID` (required) — line ID
- `WITH_QUEUE` — include operator queue (Y/N, default Y)
- `SHOW_OFFLINE` — include offline operators (Y/N, default Y)
### Create a line
```bash
python3 scripts/bitrix24_call.py imopenlines.config.add \
--param 'PARAMS[LINE_NAME]=Support' \
--json
```
Key fields in `PARAMS`: `LINE_NAME`, `CRM` (Y/N), `CRM_CREATE` (lead creation mode), `ACTIVE` (Y/N), and many others. Check MCP for full field list.
### Update a line
```bash
python3 scripts/bitrix24_call.py imopenlines.config.update \
--param 'CONFIG_ID=1' \
--param 'PARAMS[LINE_NAME]=New Name' \
--param 'PARAMS[CRM]=Y' \
--json
```
- `CONFIG_ID` (required) — line ID
- `PARAMS` — same fields as `imopenlines.config.add`
### Delete a line
```bash
python3 scripts/bitrix24_call.py imopenlines.config.delete \
--param 'CONFIG_ID=1' \
--json
```
### Get public page URL
```bash
python3 scripts/bitrix24_call.py imopenlines.config.path.get --json
```
Returns the URL of the portal's public Open Lines page.
## Operator Actions
Operators manage conversations via `CHAT_ID` — the chat identifier for the dialog.
### Accept a dialog
```bash
python3 scripts/bitrix24_call.py imopenlines.operator.answer \
--param 'CHAT_ID=2024' \
--json
```
Current operator takes the dialog.
### Skip a dialog
```bash
python3 scripts/bitrix24_call.py imopenlines.operator.skip \
--param 'CHAT_ID=2024' \
--json
```
Current operator skips — dialog goes back to queue.
### Transfer a dialog
```bash
python3 scripts/bitrix24_call.py imopenlines.operator.transfer \
--param 'CHAT_ID=2024' \
--param 'TRANSFER_ID=5' \
--json
```
- `TRANSFER_ID` — user ID to transfer to an operator, OR `queue<LINE_ID>` to transfer to another line (e.g., `queue3`)
### Finish a dialog (own)
```bash
python3 scripts/bitrix24_call.py imopenlines.operator.finish \
--param 'CHAT_ID=2024' \
--json
```
### Finish another operator's dialog
```bash
python3 scripts/bitrix24_call.py imopenlines.operator.another.finish \
--param 'CHAT_ID=2024' \
--json
```
Errors: `ACCESS_DENIED`, `CHAT_TYPE` (not an open line), `CHAT_ID` (invalid).
### Intercept a dialog
```bash
python3 scripts/bitrix24_call.py imopenlines.session.intercept \
--param 'CHAT_ID=2024' \
--json
```
Current operator takes over a dialog that already has another operator.
## Session Management
### Start a session
```bash
python3 scripts/bitrix24_call.py imopenlines.session.start \
--param 'CHAT_ID=2024' \
--json
```
### Pin/unpin dialog to operator
```bash
# Pin
python3 scripts/bitrix24_call.py imopenlines.session.mode.pin \
--param 'CHAT_ID=2024' \
--param 'ACTIVATE=Y' \
--json
# Unpin
python3 scripts/bitrix24_call.py imopenlines.session.mode.pin \
--param 'CHAT_ID=2024' \
--param 'ACTIVATE=N' \
--json
```
### Pin/unpin all dialogs
```bash
# Pin all
python3 scripts/bitrix24_call.py imopenlines.session.mode.pinAll --json
# Unpin all
python3 scripts/bitrix24_call.py imopenlines.session.mode.unpinAll --json
```
Returns array of affected session IDs.
### Get session history
```bash
python3 scripts/bitrix24_call.py imopenlines.session.history.get \
--param 'CHAT_ID=1982' \
--param 'SESSION_ID=469' \
--json
```
Returns full conversation: messages, users, chat metadata. Both `CHAT_ID` and `SESSION_ID` are required.
Response includes: `chatId`, `sessionId`, `message` (dict of messages), `users` (participants), `chat` (chat metadata with `entityType: LINES`).
## Dialog Info
```bash
python3 scripts/bitrix24_call.py imopenlines.dialog.get \
--param 'CHAT_ID=2024' \
--json
```
Can query by any ONE of: `CHAT_ID`, `DIALOG_ID` (e.g., `chat29`), `SESSION_ID`, or `USER_CODE` (e.g., `livechat|1|1373|211`).
Returns: dialog name, color, avatar, owner, `entity_type`, `entity_id`, `manager_list`, `date_create`, `message_type` (`L` = lines).
## Messaging
### Send message from Open Line to user
```bash
python3 scripts/bitrix24_call.py imopenlines.network.message.add \
--param 'CODE=ab515f5d85a8b844d484f6ea75a2e494' \
--param 'USER_ID=2' \
--param 'MESSAGE=Hello from support' \
--json
```
- `CODE` (required) — Open Line code (hash string from connector settings)
- `USER_ID` (required) — recipient user ID
- `MESSAGE` (required) — text with formatting support
- `ATTACH` — attachment object (max 30 KB)
- `KEYBOARD` — interactive keyboard (max 30 KB)
- `URL_PREVIEW` — Y/N, default Y
### Send message in CRM entity chat
```bash
python3 scripts/bitrix24_call.py imopenlines.crm.message.add \
--param 'CRM_ENTITY_TYPE=deal' \
--param 'CRM_ENTITY=288' \
--param 'USER_ID=12' \
--param 'CHAT_ID=8773' \
--param 'MESSAGE=Follow-up message' \
--json
```
- `CRM_ENTITY_TYPE``lead`, `deal`, `company`, `contact`
- `CRM_ENTITY` — CRM entity ID
- `USER_ID` — user or bot to add to chat
- `CHAT_ID` — chat ID
- `MESSAGE` — text
## CRM Integration
### Create lead from dialog
```bash
python3 scripts/bitrix24_call.py imopenlines.crm.lead.create \
--param 'CHAT_ID=1988' \
--json
```
Creates a CRM lead based on the dialog. Returns `true` on success.
## Network (External Lines)
### Join external Open Line
```bash
python3 scripts/bitrix24_call.py imopenlines.network.join \
--param 'CODE=ab515f5d85a8b844d484f6ea75a2e494' \
--json
```
Connects an external Open Line to the portal. Returns bot ID on success.
## Bot Integration
### Send auto-message from bot
```bash
python3 scripts/bitrix24_call.py imopenlines.bot.session.message.send \
--param 'CHAT_ID=2' \
--param 'MESSAGE=Welcome to our support' \
--param 'NAME=WELCOME' \
--json
```
- `NAME` — message type: `WELCOME` (greeting) or `DEFAULT` (regular)
## API Revision
```bash
python3 scripts/bitrix24_call.py imopenlines.revision.get --json
```
Returns current API revision numbers for REST, web, and mobile clients.
## Method Reference
### Line config
| Method | Description |
|--------|-------------|
| `imopenlines.config.list.get` | List all lines (with filter/select/order) |
| `imopenlines.config.get` | Get line by ID (with operator queue) |
| `imopenlines.config.add` | Create a new line |
| `imopenlines.config.update` | Update line settings |
| `imopenlines.config.delete` | Delete a line |
| `imopenlines.config.path.get` | Get public page URL |
### Operators
| Method | Description |
|--------|-------------|
| `imopenlines.operator.answer` | Accept dialog |
| `imopenlines.operator.skip` | Skip dialog (back to queue) |
| `imopenlines.operator.transfer` | Transfer to operator or line |
| `imopenlines.operator.finish` | Close own dialog |
| `imopenlines.operator.another.finish` | Close another operator's dialog |
### Sessions
| Method | Description |
|--------|-------------|
| `imopenlines.session.start` | Start a session |
| `imopenlines.session.intercept` | Take over dialog from another operator |
| `imopenlines.session.mode.pin` | Pin/unpin dialog to current operator |
| `imopenlines.session.mode.pinAll` | Pin all dialogs to current operator |
| `imopenlines.session.mode.unpinAll` | Unpin all dialogs from current operator |
| `imopenlines.session.history.get` | Get session conversation history |
| `imopenlines.session.join` | Join a session |
| `imopenlines.session.open` | Get chat ID by USER_CODE |
| `imopenlines.message.session.start` | Start session with message transfer |
### Dialog & messaging
| Method | Description |
|--------|-------------|
| `imopenlines.dialog.get` | Get dialog info (by CHAT_ID, DIALOG_ID, SESSION_ID, or USER_CODE) |
| `imopenlines.network.message.add` | Send message from Open Line to user |
| `imopenlines.crm.message.add` | Send message in CRM entity chat |
| `imopenlines.crm.lead.create` | Create CRM lead from dialog |
### Network & bot
| Method | Description |
|--------|-------------|
| `imopenlines.network.join` | Connect external Open Line to portal |
| `imopenlines.bot.session.message.send` | Send auto-message from bot |
| `imopenlines.bot.session.operator` | Switch to free operator |
| `imopenlines.bot.session.transfer` | Switch to specific operator |
| `imopenlines.bot.session.finish` | End current session (bot) |
| `imopenlines.revision.get` | Get API revision info |
## Known Limitations
- Open Lines require the `imopenlines` scope — standard `im` scope is not enough
- `imopenlines.network.message.add` requires the line's CODE (hash), not the line ID
- Attachment and keyboard objects are limited to 30 KB each
- Session history (`imopenlines.session.history.get`) requires both CHAT_ID and SESSION_ID
- There is no method to list all active sessions across all lines — you work with individual chats
For the latest API updates, check the MCP documentation server (`https://mcp-dev.bitrix24.tech/mcp`) using `bitrix-search` with queries like `imopenlines session`, `imopenlines operator`.
## Good MCP Queries
- `imopenlines config`
- `imopenlines operator transfer`
- `imopenlines session history`
- `imopenlines dialog get`
- `imopenlines crm message`
- `imopenlines network message`
- `imopenlines bot session`

View File

@@ -0,0 +1,116 @@
# Products and Product Rows
Use this file for the product catalog, product items, and attaching products to CRM entities (deals, quotes, invoices).
Scope: `crm`, `catalog`
## Product Catalog
- `crm.product.list` — list products (supports `order`, `filter`, `select`)
- `crm.product.add` — add product to CRM catalog
- `crm.product.get` — get product by ID
- `crm.product.update` — update product
- `crm.product.delete` — delete product
- `crm.product.fields` — field schema
Key fields: `ID`, `NAME`, `PRICE`, `CURRENCY_ID`, `CATALOG_ID`, `SECTION_ID`, `ACTIVE`, `MEASURE`, `DESCRIPTION`.
## Product Sections
- `crm.productsection.list` — list product sections (categories)
- `crm.productsection.add` — add section
- `crm.productsection.get` — get section
- `crm.productsection.update` — update section
- `crm.productsection.delete` — delete section
## Product Rows on CRM Entities
Product rows attach catalog products to deals, quotes, invoices, and smart processes.
- `crm.item.productrow.list` — list product rows (filter by `ownerId`, `ownerType`)
- `crm.item.productrow.get` — get one product row by ID
- `crm.item.productrow.set` — set product rows on entity (replaces all existing rows!)
- `crm.item.productrow.delete` — delete a product row
- `crm.item.productrow.getAvailableForPayment` — get rows available for payment
Owner type short codes: `L` = lead, `D` = deal, `Q` = quote, `SI` = smart invoice, dynamic types use `T{entityTypeId}`.
### Product row fields
- `productId` — ID from catalog (optional, can use `productName` alone)
- `productName` — product name (auto-filled from catalog if `productId` given)
- `price` — price per unit including discounts and taxes
- `quantity` — quantity (default 1)
- `discountTypeId` — 1 = absolute, 2 = percentage
- `discountRate` — discount in %
- `discountSum` — discount in absolute value
- `taxRate` — tax rate in %
- `taxIncluded``Y`/`N`
- `measureCode` — unit of measure code
- `sort` — sort order
## Common Use Cases
### List all products
```bash
python3 scripts/bitrix24_call.py crm.product.list \
--param 'select[]=ID' \
--param 'select[]=NAME' \
--param 'select[]=PRICE' \
--param 'select[]=CURRENCY_ID' \
--param 'order[NAME]=ASC' \
--json
```
### Add a product to catalog
```bash
python3 scripts/bitrix24_call.py crm.product.add \
--param 'fields[NAME]=Widget Pro' \
--param 'fields[PRICE]=1500' \
--param 'fields[CURRENCY_ID]=RUB' \
--param 'fields[ACTIVE]=Y' \
--json
```
### Get product rows of a deal
```bash
python3 scripts/bitrix24_call.py crm.item.productrow.list \
--param 'filter[ownerId]=123' \
--param 'filter[ownerType]=D' \
--json
```
### Set product rows on a deal
This replaces all existing product rows:
```bash
python3 scripts/bitrix24_call.py crm.item.productrow.set \
--param 'ownerType=D' \
--param 'ownerId=123' \
--param 'productRows[0][productId]=456' \
--param 'productRows[0][price]=1500' \
--param 'productRows[0][quantity]=2' \
--param 'productRows[1][productName]=Custom Service' \
--param 'productRows[1][price]=5000' \
--param 'productRows[1][quantity]=1' \
--json
```
## Working Rules
- `crm.item.productrow.set` overwrites all existing rows — always include all desired rows.
- `crm.product.*` uses UPPER_CASE field names.
- `crm.item.productrow.*` uses camelCase field names.
- Filter prefix operators work on `crm.product.list`: `>=PRICE`, `%NAME`.
- `CATALOG_ID` filter is useful when multiple catalogs exist.
## Good MCP Queries
- `crm product list add fields`
- `crm item productrow set list`
- `crm productsection`
- `catalog product`

View File

@@ -0,0 +1,135 @@
# Projects and Workgroups
Use this file for workgroups, projects, scrum, group membership, and cross-entity queries (tasks in a project, files in a project, project chat).
## Core Methods
List and read:
- `socialnetwork.api.workgroup.list` — list groups with filter and select
- `socialnetwork.api.workgroup.get` — get one group by ID
- `sonet_group.get` — list groups accessible to current user
- `sonet_group.user.groups` — groups of current user
Create and manage:
- `sonet_group.create` — create group (owner = current user)
- `sonet_group.update` — update group
- `sonet_group.delete` — delete group
- `sonet_group.setowner` — change owner
Membership:
- `sonet_group.user.get` — list group members
- `sonet_group.user.add` — add member directly
- `sonet_group.user.invite` — invite member
- `sonet_group.user.delete` — remove member
- `sonet_group.user.update` — change member role
- `sonet_group.user.request` — request to join
- `sonet_group.feature.access` — check user permissions in group
Scrum (for scrum-type projects):
- `tasks.api.scrum.sprint.list` / `tasks.api.scrum.sprint.getFields`
- `tasks.api.scrum.backlog.get`
- `tasks.api.scrum.task.getFields`
## Querying Entities Within a Project
### Tasks in a project
Filter `tasks.task.list` by `GROUP_ID`:
```bash
python3 scripts/bitrix24_call.py tasks.task.list \
--param 'filter[GROUP_ID]=15' \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=STATUS' \
--param 'select[]=DEADLINE' \
--json
```
### Files in a project
Projects have their own disk storage. Find it via `disk.storage.getlist` and filter by `ENTITY_TYPE=group`:
```bash
python3 scripts/bitrix24_call.py disk.storage.getlist \
--param 'filter[ENTITY_TYPE]=group' \
--param 'filter[ENTITY_ID]=15' \
--json
```
Then browse with `disk.storage.getchildren` using the storage ID.
### Project chat
Group chats in Bitrix24 use dialog format `sg<GROUP_ID>`:
```bash
python3 scripts/bitrix24_call.py im.dialog.messages.get \
--param 'DIALOG_ID=sg15' \
--param 'LIMIT=20' \
--json
```
## Common Use Cases
### List all my projects
```bash
python3 scripts/bitrix24_call.py sonet_group.user.groups --json
```
### Get project details
```bash
python3 scripts/bitrix24_call.py socialnetwork.api.workgroup.get \
--param 'id=15' \
--json
```
### List project members
```bash
python3 scripts/bitrix24_call.py sonet_group.user.get \
--param 'ID=15' \
--json
```
### Create a project
```bash
python3 scripts/bitrix24_call.py sonet_group.create \
--param 'NAME=New Project' \
--param 'DESCRIPTION=Project description' \
--param 'VISIBLE=Y' \
--param 'OPENED=Y' \
--json
```
### Search groups by filter
```bash
python3 scripts/bitrix24_call.py socialnetwork.api.workgroup.list \
--param 'filter[%NAME]=marketing' \
--param 'select[]=ID' \
--param 'select[]=NAME' \
--param 'select[]=NUMBER_OF_MEMBERS' \
--json
```
## Working Rules
- Use `socialnetwork.api.workgroup.list` for filtered queries with `select[]`.
- Use `sonet_group.get` for a quick list of accessible groups.
- Task filtering by project: `tasks.task.list` with `filter[GROUP_ID]`.
- Project files live in a disk storage with `ENTITY_TYPE=group`.
- Project chat is addressable as `sg<GROUP_ID>` in `im.*` methods.
## Good MCP Queries
- `socialnetwork workgroup list get`
- `sonet_group user create`
- `tasks scrum sprint backlog`

View File

@@ -0,0 +1,107 @@
# Quotes and Invoices
Use this file for commercial proposals (quotes), new smart invoices, and legacy invoices.
Scope: `crm`
## Quotes
- `crm.quote.add` — create a quote
- `crm.quote.list` — list quotes (supports `order`, `filter`, `select`)
- `crm.quote.get` — get a quote by ID
- `crm.quote.update` — update a quote
- `crm.quote.delete` — delete a quote
- `crm.quote.fields` — field schema
- `crm.quote.productrows.get` — get product rows of a quote
- `crm.quote.productrows.set` — set product rows on a quote
Key fields: `TITLE`, `STATUS_ID`, `OPPORTUNITY`, `CURRENCY_ID`, `COMPANY_ID`, `CONTACT_ID`, `DEAL_ID`, `ASSIGNED_BY_ID`, `BEGINDATE`, `CLOSEDATE`, `COMMENTS`, `OPENED`.
Status values: `DRAFT`, `SENT`, `APPROVED`, `DECLINED`, etc.
## Smart Invoices (New)
New invoices use the universal `crm.item.*` API with `entityTypeId=31`.
- `crm.item.list` with `entityTypeId=31` — list invoices
- `crm.item.add` with `entityTypeId=31` — create invoice
- `crm.item.update` with `entityTypeId=31` — update invoice
- `crm.item.delete` with `entityTypeId=31` — delete invoice
- `crm.item.fields` with `entityTypeId=31` — field schema
## Legacy Invoices (Deprecated)
`crm.invoice.*` methods are deprecated. Use `crm.item.*` with `entityTypeId=31` for new invoices.
## Common Use Cases
### Create a quote
```bash
python3 scripts/bitrix24_call.py crm.quote.add \
--param 'fields[TITLE]=Quote for services' \
--param 'fields[STATUS_ID]=DRAFT' \
--param 'fields[CURRENCY_ID]=RUB' \
--param 'fields[OPPORTUNITY]=50000' \
--param 'fields[COMPANY_ID]=1' \
--param 'fields[ASSIGNED_BY_ID]=1' \
--json
```
### List quotes
```bash
python3 scripts/bitrix24_call.py crm.quote.list \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=STATUS_ID' \
--param 'select[]=OPPORTUNITY' \
--param 'order[ID]=DESC' \
--json
```
### Get quote product rows
```bash
python3 scripts/bitrix24_call.py crm.quote.productrows.get \
--param 'id=42' \
--json
```
### Create a new smart invoice
```bash
python3 scripts/bitrix24_call.py crm.item.add \
--param 'entityTypeId=31' \
--param 'fields[title]=Invoice for order' \
--param 'fields[currencyId]=RUB' \
--param 'fields[opportunity]=15000' \
--param 'fields[companyId]=1' \
--json
```
### List smart invoices
```bash
python3 scripts/bitrix24_call.py crm.item.list \
--param 'entityTypeId=31' \
--param 'select[]=id' \
--param 'select[]=title' \
--param 'select[]=stageId' \
--param 'select[]=opportunity' \
--json
```
## Working Rules
- `crm.quote.*` uses UPPER_CASE field names.
- `crm.item.*` with `entityTypeId=31` uses camelCase field names.
- For new invoices, prefer `crm.item.*` over deprecated `crm.invoice.*`.
- Quote product rows: use `crm.quote.productrows.set` (not `crm.item.productrow.set`).
- Invoice product rows: use `crm.item.productrow.set` with `ownerType=SI`.
## Good MCP Queries
- `crm quote add list fields productrows`
- `crm item invoice entityTypeId 31`
- `crm invoice add list`

View File

@@ -0,0 +1,165 @@
# Landing Pages and Sites
Use this file for creating and managing Bitrix24 sites and landing pages, adding blocks, editing content, and publishing.
Scope: `landing`
## Sites
- `landing.site.getList` — list sites (params: `select`, `filter`, `order`)
- `landing.site.add` — create a site (fields: `TITLE`, `CODE`, `DOMAIN_ID`)
- `landing.site.update` — update a site
- `landing.site.delete` — delete a site
- `landing.site.publication` — publish site and all its pages
- `landing.site.unpublic` — unpublish site and all its pages
- `landing.site.getPublicUrl` — get public URL of a site
- `landing.site.fullExport` — export site structure
- `landing.site.getFolders` — get site folders
- `landing.site.getRights` — get current user's rights to a site
## Pages
- `landing.landing.getList` — list pages (params: `select`, `filter`, `order`; filter by `SITE_ID`)
- `landing.landing.add` — add page (fields: `TITLE`, `CODE`, `SITE_ID`)
- `landing.landing.update` — update page
- `landing.landing.delete` — delete page
- `landing.landing.copy` — copy page
- `landing.landing.move` — move page to another site
- `landing.landing.publication` — publish a page
- `landing.landing.unpublic` — unpublish a page
- `landing.landing.getpublicurl` — get public URL of a page
Extra flags in `landing.landing.getList`: `get_preview=1`, `get_urls=1`, `check_area=1`.
## Blocks
Blocks are visual sections added to pages.
- `landing.block.getlist` — list blocks on a page
- `landing.block.getrepository` — list available block templates by section
- `landing.block.getContentFromRepository` — get block HTML before placing
- `landing.landing.addblock` — add block to page (fields: `CODE`, `AFTER_ID`, `ACTIVE`, `CONTENT`)
- `landing.block.updatenodes` — edit block content by node selectors
- `landing.block.updatecontent` — replace entire block HTML
- `landing.block.getcontent` — get block content (HTML, CSS, JS)
- `landing.block.getmanifest` — get block manifest (describes editable nodes)
Repository sections: `cover`, `about`, `text_image`, `columns`, `tiles`, `forms`, `team`, `gallery`, `menu`, `footer`, `social`, etc.
### Node types in `updatenodes`
- Text: `'.selector': 'New text with <b>html</b>'`
- Image: `'.selector': {src: '/path/image.png', alt: 'Alt text'}`
- Link: `'.selector': {text: 'Link text', href: 'https://...', target: '_blank'}`
- Embed: `'.selector': {src: '//youtube.com/embed/...', source: 'https://youtube.com/watch?v=...'}`
## Common Use Cases
### List all sites
```bash
python3 scripts/bitrix24_call.py landing.site.getList \
--param 'params[select][]=ID' \
--param 'params[select][]=TITLE' \
--param 'params[select][]=DOMAIN.DOMAIN' \
--json
```
### Create a site
```bash
python3 scripts/bitrix24_call.py landing.site.add \
--param 'fields[TITLE]=My Site' \
--param 'fields[CODE]=mysite' \
--json
```
### Add a page to a site
```bash
python3 scripts/bitrix24_call.py landing.landing.add \
--param 'fields[TITLE]=About Us' \
--param 'fields[CODE]=about' \
--param 'fields[SITE_ID]=292' \
--json
```
### List available block templates
```bash
python3 scripts/bitrix24_call.py landing.block.getrepository \
--param 'section=cover' \
--json
```
### Add a block to a page
```bash
python3 scripts/bitrix24_call.py landing.landing.addblock \
--param 'lid=351' \
--param 'fields[CODE]=04.1.one_col_fix_with_title' \
--json
```
### Edit block text content
```bash
python3 scripts/bitrix24_call.py landing.block.updatenodes \
--param 'lid=351' \
--param 'block=6058' \
--param 'data[.landing-block-node-title]=Welcome to our site' \
--param 'data[.landing-block-node-text]=We provide great services.' \
--json
```
### Replace entire block HTML
```bash
python3 scripts/bitrix24_call.py landing.block.updatecontent \
--param 'lid=351' \
--param 'block=6058' \
--param 'content=<section class="landing-block"><h1>Hello World</h1></section>' \
--json
```
### Publish a site
```bash
python3 scripts/bitrix24_call.py landing.site.publication \
--param 'id=292' \
--json
```
### Get site public URL
```bash
python3 scripts/bitrix24_call.py landing.site.getPublicUrl \
--param 'id[]=292' \
--json
```
## Building a Site from Scratch
1. Create site: `landing.site.add`
2. Add pages: `landing.landing.add` with `SITE_ID`
3. Browse block templates: `landing.block.getrepository` by section
4. Add blocks to pages: `landing.landing.addblock` with block `CODE`
5. Edit block content: `landing.block.updatenodes` or `landing.block.updatecontent`
6. Publish: `landing.site.publication`
7. Get URL: `landing.site.getPublicUrl`
## Working Rules
- Landing methods use nested `params` for `select`/`filter`/`order` (not top-level).
- Block `CODE` comes from `landing.block.getrepository`.
- `landing.block.updatenodes` uses CSS selectors to target editable nodes.
- Get the block manifest first (`landing.block.getmanifest`) to see which nodes are editable.
- `landing.block.updatecontent` replaces the entire block HTML — use with care.
- Filter uses `%` for LIKE: `filter[TITLE]=%keyword%`.
## Good MCP Queries
- `landing site add getList publication`
- `landing landing add getList page`
- `landing block addblock getrepository updatenodes`
- `landing block updatecontent getcontent`

View File

@@ -0,0 +1,135 @@
# Smart Processes and Funnels
Use this file for smart processes (custom CRM types), funnels/categories, stages, and stage history.
Scope: `crm`
## Entity Type IDs
Standard types:
- `1` — lead
- `2` — deal
- `3` — contact
- `4` — company
- `5` — old invoice (deprecated)
- `7` — quote
- `31` — new invoice (smart invoice)
- `128+` — custom smart processes
Discover custom types with `crm.type.list`.
## Smart Process Types
- `crm.type.list` — list all smart process types (filter by `title`, `entityTypeId`, `isCategoriesEnabled`, etc.)
- `crm.type.get` — get one type by `id`
- `crm.type.add` — create a new smart process type
- `crm.type.update` — update a smart process type
- `crm.type.delete` — delete a smart process type
## Items (Universal API)
`crm.item.*` works for deals, leads, contacts, companies, quotes, invoices, and all custom smart processes. The `entityTypeId` parameter is always required.
- `crm.item.list` — list items (requires `entityTypeId`, supports `select`, `filter`, `order`)
- `crm.item.get` — get one item by `id` and `entityTypeId`
- `crm.item.add` — create item (requires `entityTypeId` and `fields`)
- `crm.item.update` — update item
- `crm.item.delete` — delete item
- `crm.item.fields` — get field schema for an entity type
Filter operators are prefixes on the key: `>=dateCreate`, `!stageId`, `%title`.
## Funnels (Categories)
- `crm.category.list` — list funnels for a type (requires `entityTypeId`)
- `crm.category.get` — get one funnel
- `crm.category.add` — create funnel
- `crm.category.update` — update funnel
- `crm.category.delete` — delete funnel
- `crm.category.fields` — field schema
## Stages and Statuses
- `crm.status.list` — list statuses/stages (filter by `ENTITY_ID`)
- `crm.stagehistory.list` — stage change history (requires `entityTypeId`)
Stage history `TYPE_ID` values:
- `1` — create
- `2` — intermediate stage change
- `3` — final stage
- `5` — funnel change
`STAGE_SEMANTIC_ID`: `P` = in progress, `S` = success, `F` = fail.
## Common Use Cases
### List all smart process types
```bash
python3 scripts/bitrix24_call.py crm.type.list --json
```
### List items of a smart process
```bash
python3 scripts/bitrix24_call.py crm.item.list \
--param 'entityTypeId=128' \
--param 'select[]=id' \
--param 'select[]=title' \
--param 'select[]=stageId' \
--param 'select[]=assignedById' \
--json
```
### Create an item in a smart process
```bash
python3 scripts/bitrix24_call.py crm.item.add \
--param 'entityTypeId=128' \
--param 'fields[title]=New item' \
--param 'fields[assignedById]=1' \
--json
```
### Get field schema for a type
```bash
python3 scripts/bitrix24_call.py crm.item.fields \
--param 'entityTypeId=128' \
--json
```
### List funnels (categories) for deals
```bash
python3 scripts/bitrix24_call.py crm.category.list \
--param 'entityTypeId=2' \
--json
```
### Get stage history
```bash
python3 scripts/bitrix24_call.py crm.stagehistory.list \
--param 'entityTypeId=2' \
--param 'filter[>=CREATED_TIME]=2026-01-01T00:00:00' \
--json
```
## Working Rules
- `entityTypeId` is mandatory for all `crm.item.*` and `crm.category.*` methods.
- Use `crm.type.list` to discover `entityTypeId` values for custom processes.
- Fields use camelCase in `crm.item.*` (e.g. `stageId`, `assignedById`, `categoryId`).
- For deal-specific methods (`crm.deal.*`), fields use UPPER_CASE (`STAGE_ID`, `CATEGORY_ID`).
- Always call `crm.item.fields` before writing custom field values.
## Good MCP Queries
- `crm type list add smart process`
- `crm item list add fields`
- `crm category list funnels`
- `crm stagehistory`
- `crm status list`

View File

@@ -0,0 +1,158 @@
# Tasks
Use this file for task CRUD, delegation, checklists, comments, planner data, and filtering.
## Core Methods
- `tasks.task.list` — list tasks with filters, sorting, and pagination
- `tasks.task.get` — get one task by ID
- `tasks.task.add` — create task
- `tasks.task.update` — update task
- `tasks.task.complete` — mark task as completed
- `tasks.task.renew` — reopen task
- `tasks.task.delegate` — delegate task
- `tasks.task.delete` — delete task
- `tasks.task.favorite.add` / `tasks.task.favorite.remove`
Checklist:
- `task.checklistitem.add` / `task.checklistitem.getlist`
- `task.checklistitem.complete` / `task.checklistitem.renew`
- `task.checklistitem.delete`
Comments:
- `task.commentitem.add` / `task.commentitem.getlist`
Planner:
- `task.planner.getlist` — returns task IDs from current user's "Plan for the day"
Deprecated: `task.item.*` methods — do not use.
## Critical: Filter Syntax
`tasks.task.list` uses prefix operators on filter keys:
- `>=DEADLINE` — deadline on or after date
- `<=DEADLINE` — deadline on or before date
- `!STATUS` — status not equal to value
- `RESPONSIBLE_ID` — assigned user
- `CREATED_BY` — creator
- `GROUP_ID` — project group
Dates in filters use `YYYY-MM-DD` format.
**Wrong:** `filter[DEADLINE]=2026-03-10` — this does not filter by exact date.
**Right:** `filter[>=DEADLINE]=2026-03-10` + `filter[<=DEADLINE]=2026-03-10` for tasks with deadline on that day.
## Statuses
- `1` — new
- `2` — waiting (pending)
- `3` — in progress
- `4` — supposedly completed (awaiting approval)
- `5` — completed
- `6` — deferred
To exclude deferred tasks: `filter[!STATUS]=6`
## Common Use Cases
### Overdue tasks (for proactive warnings)
Tasks where deadline has passed but task is not completed or deferred:
```bash
python3 scripts/bitrix24_call.py tasks.task.list \
--param 'filter[RESPONSIBLE_ID]=1' \
--param 'filter[<DEADLINE]=2026-03-08' \
--param 'filter[<REAL_STATUS]=5' \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=DEADLINE' \
--param 'select[]=STATUS' \
--param 'order[DEADLINE]=asc' \
--json
```
`<DEADLINE` = deadline before today. `<REAL_STATUS` = status less than 5 (excludes completed=5 and deferred=6).
Use this in morning briefing and task lists to flag overdue items with "⚠️".
### Show active tasks for current user
First get user ID, then list active tasks:
```bash
python3 scripts/bitrix24_call.py user.current --json
python3 scripts/bitrix24_call.py tasks.task.list \
--param 'filter[RESPONSIBLE_ID]=1' \
--param 'filter[!STATUS]=5' \
--param 'filter[!STATUS]=6' \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=STATUS' \
--param 'select[]=DEADLINE' \
--param 'order[DEADLINE]=asc' \
--json
```
Note: to exclude both statuses 5 and 6, use `REAL_STATUS` with range filter or pass `filter[<REAL_STATUS]=5`.
### Tasks with deadline on a specific date
```bash
python3 scripts/bitrix24_call.py tasks.task.list \
--param 'filter[>=DEADLINE]=2026-03-10' \
--param 'filter[<=DEADLINE]=2026-03-10' \
--param 'select[]=ID' \
--param 'select[]=TITLE' \
--param 'select[]=DEADLINE' \
--param 'select[]=STATUS' \
--json
```
### Create a task
```bash
python3 scripts/bitrix24_call.py tasks.task.add \
--param 'fields[TITLE]=Task title' \
--param 'fields[RESPONSIBLE_ID]=1' \
--param 'fields[DEADLINE]=2026-03-15' \
--param 'fields[PRIORITY]=2' \
--json
```
### Add checklist item
```bash
python3 scripts/bitrix24_call.py task.checklistitem.add \
--param 'TASKID=456' \
--param 'FIELDS[TITLE]=Subtask text' \
--json
```
### Add comment
```bash
python3 scripts/bitrix24_call.py task.commentitem.add \
--param 'TASKID=456' \
--param 'FIELDS[POST_MESSAGE]=Comment text' \
--json
```
## Working Rules
- Always use `select[]` to pick only the fields you need.
- Use `order[DEADLINE]=asc` to sort by deadline.
- Pagination: page size is 50, use `start=0`, `start=50`, etc.
- Get user ID from `user.current` before filtering by `RESPONSIBLE_ID`.
- For read-only requests, execute immediately.
## Good MCP Queries
- `tasks task list filter`
- `task checklistitem`
- `task commentitem`
- `task planner`

View File

@@ -0,0 +1,143 @@
# Time Tracking and Work Reports
Use this file for work day management, time tracking on tasks, absence reports, and work schedules.
Scope: `timeman`
## Work Day Management
- `timeman.status` — get current work day status (OPENED/CLOSED/PAUSED/EXPIRED)
- `timeman.open` — start or resume work day
- `timeman.pause` — pause work day
- `timeman.close` — end work day
- `timeman.settings` — get work time settings for a user
- `timeman.schedule.get` — get work schedule by ID
Status values from `timeman.status`:
- `OPENED` — work day is active
- `CLOSED` — work day is finished
- `PAUSED` — work day is paused
- `EXPIRED` — opened before today and never closed
## Time Control and Absence Reports
- `timeman.timecontrol.reports.get` — get absence report for a user/month
- `timeman.timecontrol.reports.users.get` — list users in department with access level
- `timeman.timecontrol.report.add` — submit absence explanation
- `timeman.timecontrol.settings.get` / `.set` — time control module settings
- `timeman.timecontrol.reports.settings.get` — report UI settings
## Task Time Tracking
- `task.elapseditem.add` — add time spent on a task
- `task.elapseditem.getlist` — list time entries for a task
- `task.elapseditem.get` — get a single time entry
- `task.elapseditem.update` — update time entry
- `task.elapseditem.delete` — delete time entry
## Office Network
- `timeman.networkrange.get` — get office network ranges
- `timeman.networkrange.set` — set office network ranges
- `timeman.networkrange.check` — check if IP is in office network
## Common Use Cases
### Check work day status
```bash
python3 scripts/bitrix24_call.py timeman.status --json
```
### Check work day status for another user
```bash
python3 scripts/bitrix24_call.py timeman.status \
--param 'USER_ID=42' \
--json
```
### Start work day
```bash
python3 scripts/bitrix24_call.py timeman.open --json
```
### End work day
```bash
python3 scripts/bitrix24_call.py timeman.close --json
```
### Get absence report for a user
```bash
python3 scripts/bitrix24_call.py timeman.timecontrol.reports.get \
--param 'USER_ID=42' \
--param 'MONTH=3' \
--param 'YEAR=2026' \
--json
```
### List department users for time reports
```bash
python3 scripts/bitrix24_call.py timeman.timecontrol.reports.users.get \
--param 'DEPARTMENT_ID=5' \
--json
```
### Get time spent on a task
```bash
python3 scripts/bitrix24_call.py task.elapseditem.getlist \
--param 'TASKID=456' \
--json
```
### Log time on a task
```bash
python3 scripts/bitrix24_call.py task.elapseditem.add \
--param 'TASKID=456' \
--param 'FIELDS[SECONDS]=3600' \
--param 'FIELDS[COMMENT_TEXT]=Development work' \
--json
```
### Get user work schedule
```bash
python3 scripts/bitrix24_call.py timeman.settings \
--param 'USER_ID=42' \
--json
```
## Building Department Reports
To build a time report for a department:
1. Get department employees: `im.department.employees.get`
2. For each employee, get work day status: `timeman.status` with `USER_ID`
3. For detailed reports: `timeman.timecontrol.reports.get` with `USER_ID`, `MONTH`, `YEAR`
4. For task time: `task.elapseditem.getlist` per task
## Working Rules
- `timeman.status` returns current user by default — pass `USER_ID` for other users.
- `timeman.timecontrol.reports.get` requires `USER_ID`, `MONTH`, and `YEAR` (all mandatory).
- Access to other users' reports depends on role (manager/admin).
- `task.elapseditem.*` uses `TASKID` (not `TASK_ID`).
- Time entries use `SECONDS` field, not hours.
## Note: No Email API
Bitrix24 has `mailservice.*` methods for configuring SMTP/IMAP mail services, but there is **no REST API for reading or sending individual emails** from Bitrix24 mailboxes.
## Good MCP Queries
- `timeman status open close`
- `timeman timecontrol reports`
- `task elapseditem time spent`
- `timeman schedule settings`

View File

@@ -0,0 +1,121 @@
# Troubleshooting
Use this file when webhook calls fail or the agent cannot reach the portal.
## Default Behavior
Do the first diagnosis yourself before asking the user anything.
Preferred order:
1. Run `scripts/check_webhook.py --json`
2. Inspect the result: format, DNS, HTTP status
3. Report concrete findings and one next fix
4. Only ask the user for a webhook if no saved config exists
## Typical Failure: No Webhook Configured
If `check_webhook.py` reports `"source": "missing"`:
- Ask the user for a webhook URL
- Save and verify in one step:
```bash
python3 scripts/bitrix24_call.py user.current --url "<webhook>" --json
```
If the user already pasted the webhook earlier in the conversation, save it immediately and retry.
## Typical Failure: DNS Resolution Failed
Usually means:
- typo in the portal domain
- local network issue
Tell the user the portal address could not be reached. Name the host that failed.
## Typical Failure: Bad Format
Expected format:
```text
https://your-portal.bitrix24.ru/rest/<user_id>/<webhook>/
```
Common mistakes:
- copied portal URL instead of webhook URL
- extra quotes or spaces
- wrong user ID segment
## Typical Failure: HTTP 401 or Auth Errors
Usually indicates:
- revoked webhook
- wrong secret
- expired OAuth token
Ask the user to verify or regenerate the webhook in Bitrix24.
## Typical Failure: `ACCESS_DENIED` or `insufficient_scope`
Missing permissions. Tell the user exactly which scope family is likely missing:
- CRM
- Tasks
- Calendar
- Disk
- IM
- `imbot`
Do not just say "permissions issue" without naming the likely scope.
## User-Facing Style
Prefer:
- what you checked
- what failed
- what is already confirmed working
- one next action
- plain business language ("connection to Bitrix24", "access to calendar")
- doing the next safe step yourself before asking the user
Avoid:
- long lists of shell commands for the user
- asking for confirmation before a simple retry
- exposing webhook URLs or secrets
- talking about curl, MCP, JSON, DNS, or config mechanics unless explicitly asked
- multiple-choice menus
## Response Templates
Bad:
- "What you need to do now: 1. create env 2. source env 3. run curl 4. or try direct URL..."
Better for missing webhook:
- "Сейчас доступ к Битрикс24 не подключен. Пришлите вебхук, и я сразу настрою и проверю подключение."
Better when DNS failed:
- "Не удаётся связаться с Битрикс24. Похоже, адрес портала указан неверно."
Better when auth failed:
- "Связь с Битрикс24 есть, но доступ не подтверждён. Скорее всего, вебхук нужно обновить."
## Autonomous Retry Rule
For safe read-only requests:
- Execute immediately
- If it fails, run `check_webhook.py --json`
- If fixable, retry once automatically
- Only then report the blocker
Do not ask "Should I try again?" — just do it.

View File

@@ -0,0 +1,125 @@
# Users, Departments, and Org Structure
Use this file for user lookup, department hierarchy, subordinates, managers, and org-structure reports.
## Users
- `user.current` — current webhook user (always start here to get own ID)
- `user.get` — get users by filter (supports `UF_DEPARTMENT`, `ACTIVE`, etc.)
- `user.search` — fast fuzzy search by name, position, department
- `profile` — basic info about current user (no scope required)
## Departments
- `department.get` — list departments (supports `PARENT`, `UF_HEAD`, `NAME` filters)
- `department.add` / `department.update` / `department.delete`
- `department.fields` — field schema
Key fields in `department.get`:
- `ID` — department ID
- `NAME` — department name
- `PARENT` — parent department ID (use to build tree)
- `UF_HEAD` — user ID of department head
- `SORT` — sort order
## Org Structure Helpers (Messenger API)
- `im.department.employees.get` — employees of given department(s)
- `im.department.managers.get` — managers/heads of given department(s)
- `im.department.colleagues.list` — colleagues of current user (for managers: returns subordinates)
- `im.department.get` — department data by ID
- `im.search.user.list` — search users by name/position
- `im.search.department.list` — search departments by name
- `im.user.get` — get user data by ID
`BX24.selectUsers` is frontend-only, not usable from REST.
## Common Use Cases
### Get current user identity
```bash
python3 scripts/bitrix24_call.py user.current --json
```
### Build department tree
Get all departments, use `PARENT` field to reconstruct hierarchy:
```bash
python3 scripts/bitrix24_call.py department.get --json
```
### Get subdepartments of a specific department
```bash
python3 scripts/bitrix24_call.py department.get \
--param 'PARENT=1' \
--json
```
### Get department head
```bash
python3 scripts/bitrix24_call.py im.department.managers.get \
--param 'ID[]=5' \
--param 'USER_DATA=Y' \
--json
```
### Get all employees of a department
```bash
python3 scripts/bitrix24_call.py im.department.employees.get \
--param 'ID[]=5' \
--json
```
### Get subordinates (for a manager)
`im.department.colleagues.list` returns subordinates when called by a manager:
```bash
python3 scripts/bitrix24_call.py im.department.colleagues.list --json
```
### Get users by department
```bash
python3 scripts/bitrix24_call.py user.get \
--param 'filter[UF_DEPARTMENT]=5' \
--param 'filter[ACTIVE]=true' \
--json
```
### Search users by name
```bash
python3 scripts/bitrix24_call.py user.search \
--param 'FILTER[NAME]=Ivan' \
--json
```
## Building Reports by Department
To build a report by department with subordinates:
1. Get all departments: `department.get`
2. For each department, get employees: `im.department.employees.get` or `user.get` with `filter[UF_DEPARTMENT]`
3. For each department, get head: `im.department.managers.get`
4. Cross-reference with task/timeman data as needed
## Working Rules
- Always start with `user.current` to know the webhook user's ID.
- Use `department.get` with `PARENT` filter to navigate the tree.
- `UF_HEAD` in department data gives the head's user ID directly.
- Pagination: page size 50, use `START=0`, `START=50`, etc.
## Good MCP Queries
- `user current get search`
- `department get fields`
- `im department employees managers colleagues`
- `im search user department`

View File

@@ -0,0 +1,69 @@
#!/bin/bash
# Auto-update bitrix24 skill on the target machine.
#
# Checks ClawHub for new versions, installs if available, and restarts gateway.
# Designed for scheduled execution (e.g., every hour via cron or OpenClaw scheduled task).
#
# Usage:
# ssh slon-mac "export PATH=\$HOME/.nvm/versions/node/*/bin:/opt/homebrew/bin:/usr/local/bin:\$PATH && bash -s" < scripts/auto_update.sh
#
# Or run directly on slon-mac:
# bash /path/to/auto_update.sh
set -euo pipefail
export PATH="$HOME/.nvm/versions/node/*/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
SKILL="bitrix24"
LOG_PREFIX="[bitrix24-auto-update]"
log() { echo "$LOG_PREFIX $(date '+%H:%M:%S') $1"; }
# Get currently installed version
INSTALLED=$(npx clawhub list 2>/dev/null | grep "$SKILL" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")
if [ -z "$INSTALLED" ]; then
log "ERROR: $SKILL not installed"
exit 1
fi
log "Installed: v$INSTALLED"
# Get latest available version from registry
LATEST=$(npx clawhub inspect "$SKILL" 2>/dev/null | grep -oE 'latest:\s*[0-9]+\.[0-9]+\.[0-9]+' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "")
if [ -z "$LATEST" ]; then
log "Could not check latest version (network or rate limit)"
exit 0
fi
log "Latest: v$LATEST"
# Compare
if [ "$INSTALLED" = "$LATEST" ]; then
log "Up to date"
exit 0
fi
log "Update available: v$INSTALLED → v$LATEST"
# Install
log "Installing v$LATEST..."
if npx clawhub install "$SKILL" --version "$LATEST" --force 2>&1; then
log "Installed v$LATEST"
else
log "ERROR: Install failed (rate limit?). Will retry next run."
exit 0
fi
# Restart gateway
log "Restarting gateway..."
if openclaw gateway restart 2>&1; then
log "Gateway restarted"
else
log "WARNING: Gateway restart failed"
fi
log "UPDATE COMPLETE: $SKILL v$INSTALLED → v$LATEST"
# Output summary for the user notification
echo ""
echo "---"
echo "Скилл Bitrix24 обновлён: v$INSTALLED → v$LATEST"
echo "Подробности: https://rsvbitrix.github.io/bitrix24-skill/#changelog"

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Execute multiple Bitrix24 REST methods in one HTTP request using batch API."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from urllib import error, parse, request
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from bitrix24_config import load_url, normalize_url, validate_url # noqa: E402
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Batch-call Bitrix24 REST methods.")
parser.add_argument(
"--cmd",
action="append",
required=True,
help="Command in name=method?params form, e.g. 'tasks=tasks.task.list?filter[STATUS]=2'. Repeat for each method.",
)
parser.add_argument("--halt", type=int, default=0, help="Stop on first error (1) or run all (0, default)")
parser.add_argument("--url", help="Webhook URL override")
parser.add_argument("--config-file", help="Config file path override")
parser.add_argument("--timeout", type=float, default=30.0, help="HTTP timeout in seconds")
parser.add_argument("--json", action="store_true", help="Pretty-print JSON response")
return parser.parse_args()
def parse_commands(raw_cmds: list[str]) -> dict[str, str]:
"""Parse 'name=method?params' into {name: 'method?params'}."""
commands: dict[str, str] = {}
for i, item in enumerate(raw_cmds):
if "=" not in item:
raise ValueError(f"Invalid --cmd '{item}'. Use name=method or name=method?params")
name, method_with_params = item.split("=", 1)
if not name:
name = f"cmd{i}"
commands[name] = method_with_params
return commands
def main() -> int:
args = parse_args()
raw_url, source = load_url(cli_url=args.url, config_file=args.config_file)
if not raw_url:
print(json.dumps({"ok": False, "error": "No Bitrix24 webhook configured", "source": source}, indent=2))
return 1
normalized_url = validate_url(raw_url)
try:
commands = parse_commands(args.cmd)
except ValueError as exc:
print(str(exc), file=sys.stderr)
return 1
url = normalize_url(normalized_url) + "batch.json"
params: list[tuple[str, str]] = [("halt", str(args.halt))]
for name, method_call in commands.items():
params.append((f"cmd[{name}]", method_call))
data = parse.urlencode(params).encode("utf-8")
req = request.Request(url, data=data, headers={"Accept": "application/json"})
try:
with request.urlopen(req, timeout=args.timeout) as response:
payload = response.read().decode("utf-8", errors="replace")
status = response.getcode()
except error.HTTPError as exc:
payload = exc.read().decode("utf-8", errors="replace")
status = exc.code
except Exception as exc:
print(json.dumps({"ok": False, "error": str(exc), "source": source}, indent=2))
return 1
try:
body = json.loads(payload)
except Exception:
body = {"raw": payload}
result = {"ok": status < 400, "status": status, "source": source, "commands": list(commands.keys()), "body": body}
if args.json:
print(json.dumps(result, ensure_ascii=True, indent=2))
else:
print(json.dumps(result, ensure_ascii=True))
return 0 if status < 400 else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,273 @@
#!/usr/bin/env python3
"""Call Bitrix24 REST methods using saved config or explicit URL.
Features:
--param key=value Standard parameter passing (repeat for multiple)
--params-file FILE Pass parameters from a JSON file (safer for complex data)
--dry-run Validate request without executing
--iterate Auto-paginate list methods (collect all pages)
--max-items N Limit total items when iterating
--confirm-write Required flag for write operations (add, update, set)
--confirm-destructive Required flag for destructive operations (delete, remove)
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from urllib import error, parse, request
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from bitrix24_config import load_url, normalize_url, persist_url_to_config, validate_url, cache_user_data # noqa: E402
# Operation classification patterns
WRITE_SUFFIXES = re.compile(
r"(?:^|\.)(add|update|set|register|bind|import|complete|start|stop|move|clear|confirm|attach|send|mute|pin)$",
re.IGNORECASE,
)
DESTRUCTIVE_SUFFIXES = re.compile(
r"(?:^|\.)(delete|remove|recyclebin|unregister|unbind)$",
re.IGNORECASE,
)
MAX_PAGES = 200 # Safety limit for auto-pagination
def classify_operation(method: str) -> str:
"""Classify method as 'read', 'write', or 'destructive'."""
if DESTRUCTIVE_SUFFIXES.search(method):
return "destructive"
if WRITE_SUFFIXES.search(method):
return "write"
return "read"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Call a Bitrix24 REST method.")
parser.add_argument("method", help="REST method, e.g. user.current or calendar.event.get")
parser.add_argument("--url", help="Webhook URL (saved to config automatically)")
parser.add_argument("--config-file", help="Config file path override")
parser.add_argument(
"--param",
action="append",
default=[],
help="Request parameter in key=value form; repeat as needed",
)
parser.add_argument(
"--params-file",
help="Path to a JSON file with method parameters (alternative to --param)",
)
parser.add_argument("--timeout", type=float, default=20.0, help="HTTP timeout in seconds")
parser.add_argument("--json", action="store_true", help="Pretty-print JSON response")
parser.add_argument("--dry-run", action="store_true", help="Show what would be called without executing")
parser.add_argument("--iterate", action="store_true", help="Auto-paginate list methods (collect all pages)")
parser.add_argument("--max-items", type=int, default=None, help="Max items to collect when iterating")
parser.add_argument("--confirm-write", action="store_true", help="Confirm write operation (add/update/set)")
parser.add_argument("--confirm-destructive", action="store_true", help="Confirm destructive operation (delete/remove)")
return parser.parse_args()
def parse_params(raw_params: list[str]) -> list[tuple[str, str]]:
out: list[tuple[str, str]] = []
for item in raw_params:
if "=" not in item:
raise ValueError(f"Invalid --param '{item}'. Use key=value.")
key, value = item.split("=", 1)
out.append((key, value))
return out
def do_request(url: str, data: bytes, timeout: float, source: str) -> dict:
"""Execute HTTP request and return structured result."""
req = request.Request(url, data=data, headers={"Accept": "application/json"})
try:
with request.urlopen(req, timeout=timeout) as response:
payload = response.read().decode("utf-8", errors="replace")
status = response.getcode()
except error.HTTPError as exc:
payload = exc.read().decode("utf-8", errors="replace")
status = exc.code
except Exception as exc:
return {"ok": False, "error": str(exc), "source": source}
try:
body = json.loads(payload)
except Exception:
body = {"raw": payload}
return {"ok": status < 400, "status": status, "source": source, "body": body}
def main() -> int:
args = parse_args()
raw_url, source = load_url(cli_url=args.url, config_file=args.config_file)
if not raw_url:
print(json.dumps({"ok": False, "error": "No Bitrix24 webhook configured", "source": source}, ensure_ascii=True, indent=2))
return 1
normalized_url = validate_url(raw_url)
if not source.startswith("config:"):
persist_url_to_config(normalized_url, args.config_file)
method = args.method[:-5] if args.method.endswith(".json") else args.method
# --- Resolve parameters ---
# --params-file takes priority (safer for complex JSON)
if args.params_file:
try:
params_json = json.loads(Path(args.params_file).read_text(encoding="utf-8"))
except FileNotFoundError:
print(json.dumps({"ok": False, "error": f"Params file not found: {args.params_file}"}, indent=2))
return 1
except json.JSONDecodeError as e:
print(json.dumps({"ok": False, "error": f"Invalid JSON in {args.params_file}: {e}"}, indent=2))
return 1
if not isinstance(params_json, dict):
print(json.dumps({"ok": False, "error": "params-file must contain a JSON object"}), indent=2)
return 1
# Convert JSON dict to URL-encoded pairs (flat)
params = _flatten_json(params_json)
else:
try:
params = parse_params(args.param)
except ValueError as exc:
print(str(exc), file=sys.stderr)
return 1
# --- Operation classification & safety checks ---
op_type = classify_operation(method)
if args.dry_run:
dry = {
"dry_run": True,
"method": method,
"operation": op_type,
"params_count": len(params),
"source": source,
}
print(json.dumps(dry, ensure_ascii=True, indent=2))
return 0
if op_type == "write" and not args.confirm_write:
print(json.dumps({
"ok": False,
"error": f"Write operation '{method}' requires --confirm-write flag",
"operation": op_type,
}, ensure_ascii=True, indent=2))
return 2
if op_type == "destructive" and not args.confirm_destructive:
print(json.dumps({
"ok": False,
"error": f"Destructive operation '{method}' requires --confirm-destructive flag",
"operation": op_type,
}, ensure_ascii=True, indent=2))
return 2
base_url = normalize_url(normalized_url)
# --- Auto-pagination ---
if args.iterate and method.endswith(".list"):
all_items: list = []
start = 0
total = None
for page in range(1, MAX_PAGES + 1):
page_params = params + [("start", str(start))]
url = base_url + f"{method}.json"
data = parse.urlencode(page_params).encode("utf-8")
result = do_request(url, data, args.timeout, source)
if not result.get("ok"):
print(json.dumps(result, ensure_ascii=True, indent=2 if args.json else None))
return 1
body = result.get("body", {})
page_result = body.get("result", [])
if isinstance(page_result, dict):
# Some methods return {tasks: [...]} instead of [...]
for v in page_result.values():
if isinstance(v, list):
page_result = v
break
else:
page_result = []
all_items.extend(page_result)
if total is None:
total = body.get("total", len(all_items))
if args.max_items and len(all_items) >= args.max_items:
all_items = all_items[:args.max_items]
break
next_start = body.get("next")
if next_start is None:
break
start = next_start
result = {
"ok": True,
"status": 200,
"source": source,
"body": {"result": all_items, "total": total, "fetched": len(all_items)},
}
print(json.dumps(result, ensure_ascii=True, indent=2 if args.json else None))
return 0
# --- Single request ---
url = base_url + f"{method}.json"
data = parse.urlencode(params).encode("utf-8")
result = do_request(url, data, args.timeout, source)
# Auto-cache user_id and timezone after successful user.current call
status = result.get("status", 0)
body = result.get("body", {})
if method == "user.current" and status and status < 400 and isinstance(body, dict):
user_result = body.get("result", {})
uid = user_result.get("ID")
tz = user_result.get("TIME_ZONE", "")
if uid:
try:
cache_user_data(int(uid), tz, args.config_file)
except Exception:
pass
if args.json:
print(json.dumps(result, ensure_ascii=True, indent=2))
else:
print(json.dumps(result, ensure_ascii=True))
return 0 if result.get("ok") else 1
def _flatten_json(obj: dict, prefix: str = "") -> list[tuple[str, str]]:
"""Flatten nested JSON dict into URL-encoded key=value pairs.
{"fields": {"TITLE": "X"}} → [("fields[TITLE]", "X")]
{"select": ["ID", "TITLE"]} → [("select[]", "ID"), ("select[]", "TITLE")]
"""
pairs: list[tuple[str, str]] = []
for key, value in obj.items():
full_key = f"{prefix}[{key}]" if prefix else key
if isinstance(value, dict):
pairs.extend(_flatten_json(value, full_key))
elif isinstance(value, list):
for item in value:
if isinstance(item, dict):
pairs.extend(_flatten_json(item, f"{full_key}[]"))
else:
pairs.append((f"{full_key}[]", str(item)))
else:
pairs.append((full_key, str(value)))
return pairs
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Shared helpers for Bitrix24 skill scripts."""
from __future__ import annotations
import json
import re
from pathlib import Path
WEBHOOK_RE = re.compile(r"^https://(?P<host>[^/]+)/rest/(?P<user_id>\d+)/(?P<secret>[^/]+)/?$")
DEFAULT_CONFIG_PATH = Path.home() / ".config" / "bitrix24-skill" / "config.json"
def normalize_url(value: str) -> str:
return value.strip().strip('"').strip("'").rstrip("/") + "/"
def validate_url(value: str) -> str:
normalized = normalize_url(value)
if not WEBHOOK_RE.match(normalized):
raise ValueError("Webhook format is invalid. Expected https://<host>/rest/<user_id>/<secret>/")
return normalized
def mask_url(value: str) -> str:
match = WEBHOOK_RE.match(value)
if not match:
return value
secret = match.group("secret")
if len(secret) <= 4:
masked = "*" * len(secret)
else:
masked = f"{secret[:2]}***{secret[-2:]}"
return f"https://{match.group('host')}/rest/{match.group('user_id')}/{masked}/"
def load_config(path: Path) -> dict:
if not path.is_file():
return {}
try:
data = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return data if isinstance(data, dict) else {}
def save_config(path: Path, data: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=True, indent=2) + "\n", encoding="utf-8")
def persist_url_to_config(url: str, config_file: str | None = None) -> Path:
config_path = Path(config_file).expanduser() if config_file else DEFAULT_CONFIG_PATH
config = load_config(config_path)
config["webhook_url"] = validate_url(url)
save_config(config_path, config)
return config_path
def load_url(
*,
cli_url: str | None,
config_file: str | None = None,
) -> tuple[str | None, str]:
if cli_url:
return cli_url, "arg:url"
config_path = Path(config_file).expanduser() if config_file else DEFAULT_CONFIG_PATH
config = load_config(config_path)
config_url = config.get("webhook_url")
if isinstance(config_url, str) and config_url.strip():
return config_url, f"config:{config_path}"
return None, "missing"
def get_cached_user(config_file: str | None = None) -> dict | None:
"""Return cached user data (user_id, timezone) or None."""
config_path = Path(config_file).expanduser() if config_file else DEFAULT_CONFIG_PATH
config = load_config(config_path)
user_id = config.get("user_id")
if user_id is not None:
return {"user_id": user_id, "timezone": config.get("timezone", "")}
return None
def cache_user_data(user_id: int, timezone: str = "", config_file: str | None = None) -> None:
"""Save user_id and timezone to config for reuse."""
config_path = Path(config_file).expanduser() if config_file else DEFAULT_CONFIG_PATH
config = load_config(config_path)
config["user_id"] = user_id
if timezone:
config["timezone"] = timezone
save_config(config_path, config)

View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Convert CHANGELOG.md to docs/changelog.json for the landing page.
Usage: python3 scripts/changelog_to_json.py <current_version>
Parses CHANGELOG.md format:
## 0.15.4 — 2026-03-11
### Added
- **Feature name**: description
Outputs docs/changelog.json:
{"version": "0.15.4", "entries": [{"version": "0.15.4", "date": "2026-03-11", "items": [...]}]}
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
CHANGELOG = ROOT / "CHANGELOG.md"
OUTPUT = ROOT / "docs" / "changelog.json"
VERSION_RE = re.compile(r"^## (\d+\.\d+\.\d+)\s*[—–-]\s*(\d{4}-\d{2}-\d{2})")
SECTION_RE = re.compile(r"^### (.+)")
ITEM_RE = re.compile(r"^- \*\*(.+?)\*\*:\s*(.+)")
ITEM_PLAIN_RE = re.compile(r"^- (.+)")
def parse_changelog() -> list[dict]:
if not CHANGELOG.exists():
return []
entries = []
current: dict | None = None
section = ""
for line in CHANGELOG.read_text(encoding="utf-8").splitlines():
line = line.rstrip()
vm = VERSION_RE.match(line)
if vm:
if current:
entries.append(current)
current = {"version": vm.group(1), "date": vm.group(2), "items": []}
section = ""
continue
sm = SECTION_RE.match(line)
if sm:
section = sm.group(1)
continue
if current is None:
continue
im = ITEM_RE.match(line)
if im:
current["items"].append({
"section": section,
"title": im.group(1),
"description": im.group(2),
})
continue
ip = ITEM_PLAIN_RE.match(line)
if ip:
current["items"].append({
"section": section,
"title": "",
"description": ip.group(1),
})
if current:
entries.append(current)
return entries
def main() -> None:
version = sys.argv[1] if len(sys.argv) > 1 else ""
entries = parse_changelog()
data = {
"version": version or (entries[0]["version"] if entries else ""),
"entries": entries[:10], # Last 10 releases
}
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
OUTPUT.write_text(
json.dumps(data, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(f"Generated {OUTPUT} ({len(entries)} releases, showing {len(data['entries'])})")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""Check Bitrix24 webhook availability from saved config or explicit URL."""
from __future__ import annotations
import argparse
import json
import socket
import sys
from pathlib import Path
from typing import Any
from urllib import error, request
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from bitrix24_config import load_url, mask_url, normalize_url, validate_url # noqa: E402
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Check a Bitrix24 webhook URL.")
parser.add_argument("--url", help="Webhook URL to check")
parser.add_argument("--config-file", help="Config file path override")
parser.add_argument("--skip-http", action="store_true", help="Skip user.current probe")
parser.add_argument("--json", action="store_true", help="Print JSON output")
parser.add_argument("--timeout", type=float, default=10.0, help="HTTP timeout in seconds")
return parser.parse_args()
def resolve_dns(host: str) -> tuple[bool, list[str], str | None]:
try:
infos = socket.getaddrinfo(host, 443, type=socket.SOCK_STREAM)
except OSError as exc:
return False, [], str(exc)
ips = sorted({info[4][0] for info in infos if info[4]})
return True, ips, None
def probe_user_current(url: str, timeout: float) -> tuple[bool, int | None, dict[str, Any] | None, str | None]:
target = url + "user.current.json"
req = request.Request(target, headers={"Accept": "application/json"})
try:
with request.urlopen(req, timeout=timeout) as response:
status = response.getcode()
payload = response.read().decode("utf-8", errors="replace")
except error.HTTPError as exc:
try:
payload = exc.read().decode("utf-8", errors="replace")
except Exception:
payload = ""
return False, exc.code, safe_json(payload), f"HTTP {exc.code}"
except Exception as exc:
return False, None, None, str(exc)
data = safe_json(payload)
if isinstance(data, dict) and data.get("error"):
return False, status, data, str(data.get("error_description") or data["error"])
return True, status, data, None
def safe_json(payload: str) -> dict[str, Any] | None:
if not payload:
return None
try:
value = json.loads(payload)
except Exception:
return None
return value if isinstance(value, dict) else {"raw": value}
def build_result(args: argparse.Namespace) -> dict[str, Any]:
raw_url, source = load_url(cli_url=args.url, config_file=args.config_file)
result: dict[str, Any] = {
"source": source,
"url_found": raw_url is not None,
"format_ok": False,
"dns_ok": False,
"http_ok": None if args.skip_http else False,
}
if not raw_url:
result["error"] = "No Bitrix24 webhook found in config"
return result
normalized = normalize_url(raw_url)
result["masked_url"] = mask_url(normalized)
try:
validate_url(normalized)
except ValueError:
result["error"] = "Webhook format is invalid"
return result
parts = normalized.split("/")
host = parts[2]
user_id = parts[4]
result["format_ok"] = True
result["host"] = host
result["user_id"] = user_id
dns_ok, ips, dns_error = resolve_dns(host)
result["dns_ok"] = dns_ok
result["dns_ips"] = ips
if dns_error:
result["dns_error"] = dns_error
return result
if args.skip_http:
return result
http_ok, status, payload, http_error = probe_user_current(normalized, args.timeout)
result["http_ok"] = http_ok
result["http_status"] = status
if payload is not None:
result["http_payload"] = payload
if http_error:
result["http_error"] = http_error
return result
def print_plain(result: dict[str, Any]) -> None:
for key in [
"source",
"url_found",
"format_ok",
"host",
"user_id",
"dns_ok",
"dns_ips",
"dns_error",
"http_ok",
"http_status",
"http_error",
"error",
]:
if key in result:
print(f"{key}: {result[key]}")
if "http_payload" in result:
print("http_payload:")
print(json.dumps(result["http_payload"], ensure_ascii=True, indent=2))
def main() -> int:
args = parse_args()
result = build_result(args)
if args.json:
print(json.dumps(result, ensure_ascii=True, indent=2))
else:
print_plain(result)
if not result.get("url_found"):
return 1
if not result.get("format_ok"):
return 1
if not result.get("dns_ok"):
return 1
if result.get("http_ok") is False:
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Publish skill to ClawHub with automatic version/changelog sync.
#
# Usage: ./scripts/publish.sh 0.15.5
#
# What it does:
# 1. Updates version in docs/index.html footer
# 2. Generates docs/changelog.json from CHANGELOG.md
# 3. Commits version bump
# 4. Pushes to origin/main
# 5. Publishes to ClawHub
set -euo pipefail
VERSION="${1:?Usage: $0 <version>}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$ROOT_DIR"
echo "=== Publishing bitrix24@${VERSION} ==="
# 1. Update version in footer
echo "→ Updating docs/index.html footer..."
sed -i '' "s/Bitrix24 Skill v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*/Bitrix24 Skill v${VERSION}/" docs/index.html
# 2. Generate changelog.json from CHANGELOG.md
echo "→ Generating docs/changelog.json..."
python3 "$SCRIPT_DIR/changelog_to_json.py" "$VERSION"
# 3. Commit
echo "→ Committing version bump..."
git add docs/index.html docs/changelog.json CHANGELOG.md
git commit -m "Release v${VERSION}" --allow-empty || true
# 4. Push
echo "→ Pushing to origin/main..."
git push origin main
# 5. Publish to ClawHub
echo "→ Publishing to ClawHub..."
npx clawhub publish . --version "$VERSION"
echo ""
echo "=== Done: bitrix24@${VERSION} published ==="
echo "Wait ~90s for security scan, then install:"
echo " ssh slon-mac \"export PATH=\\\$HOME/.nvm/versions/node/*/bin:/opt/homebrew/bin:/usr/local/bin:\\\$PATH && npx clawhub install bitrix24 --version ${VERSION} --force\""

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Save a Bitrix24 webhook into stable config."""
from __future__ import annotations
import argparse
import subprocess
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from bitrix24_config import DEFAULT_CONFIG_PATH, load_config, save_config, validate_url # noqa: E402
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Save a Bitrix24 webhook into stable config.")
parser.add_argument("--url", required=True, help="Bitrix24 webhook URL")
parser.add_argument("--config-file", default=str(DEFAULT_CONFIG_PATH), help="Target config file path")
parser.add_argument("--force", action="store_true", help="Overwrite existing saved webhook")
parser.add_argument("--check", action="store_true", help="Run check_webhook.py after saving")
return parser.parse_args()
def run_check(config_path: Path) -> int:
script = Path(__file__).with_name("check_webhook.py")
cmd = [sys.executable, str(script), "--config-file", str(config_path), "--json"]
result = subprocess.run(cmd, check=False)
return result.returncode
def main() -> int:
args = parse_args()
try:
normalized = validate_url(args.url)
except ValueError as exc:
print(str(exc), file=sys.stderr)
return 1
config_path = Path(args.config_file).expanduser()
config = load_config(config_path)
found = isinstance(config.get("webhook_url"), str) and bool(config.get("webhook_url"))
if found and not args.force:
changed = False
else:
config["webhook_url"] = normalized
save_config(config_path, config)
changed = True
print(f"saved_to: {config_path}")
if found and not args.force:
print("note: saved webhook already existed and was kept unchanged; use --force to replace it")
elif found and args.force:
print("note: existing saved webhook was replaced")
elif changed:
print("note: webhook was saved")
if args.check:
return run_check(config_path)
return 0
if __name__ == "__main__":
sys.exit(main())

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,730 @@
# SendForSign API — Full Reference
Base URL: `https://api.sendforsign.com/api`
Auth header: `X-Sendforsign-Key: {API_KEY}`
Content-Type: `application/json` (unless uploading files)
All POST endpoints use a `data` envelope with an `action` field.
---
## CLIENTS — POST /api/client
### Create client
```json
{
"data": {
"action": "create",
"client": {
"fullname": "John Doe",
"email": "john@example.com",
"organization": "Acme Corp",
"customKey": "optional-your-own-id",
"users": [
{ "fullname": "Jane Doe", "email": "jane@example.com", "customKey": "optional" }
]
}
}
}
```
Response 201: `{ clientKey, createTime, users: [{ userKey }] }`
### List clients
```json
{ "data": { "action": "list" } }
```
Response 200: array of `{ clientKey, fullname, email, organization, customKey, createTime, changeTime }`
### Read client
```json
{
"data": {
"action": "read",
"client": { "clientKey": "..." }
}
}
```
Can also identify by: `fullname`, `email`, `organization`, or `customKey`.
### Update client
```json
{
"data": {
"action": "update",
"client": {
"clientKey": "...",
"fullname": "New Name",
"email": "new@email.com",
"organization": "New Org",
"customKey": "new-custom-key"
}
}
}
```
Response 201: updated client with `changeTime`.
---
## USERS — POST /api/user
### Create user
```json
{
"data": {
"action": "create",
"clientKey": "...",
"user": { "fullname": "Jane", "email": "jane@example.com", "customKey": "optional" }
}
}
```
Response 201: `{ userKey, createTime }`
### List users
```json
{ "data": { "action": "list", "clientKey": "..." } }
```
Response 200: array of users with `userKey, fullname, email, customKey, createTime, changeTime`
### Read user
```json
{
"data": {
"action": "read",
"clientKey": "...",
"user": { "userKey": "..." }
}
}
```
Can also identify by: `fullname`, `email`, `customKey`.
### Update user
```json
{
"data": {
"action": "update",
"clientKey": "...",
"user": { "userKey": "...", "fullname": "...", "email": "...", "customKey": "..." }
}
}
```
---
## CONTRACTS — POST /api/contract
### Create from HTML
```json
{
"data": {
"action": "create",
"clientKey": "...",
"userKey": "... (optional)",
"contract": {
"name": "My Contract",
"value": "<p>Contract content in HTML</p>"
}
}
}
```
Response 201: `{ contractKey, createTime }`
### Create from template
```json
{
"data": {
"action": "create",
"clientKey": "...",
"contract": { "templateKey": "...", "name": "My Contract" }
}
}
```
### Create from template + fill placeholders
```json
{
"data": {
"action": "create",
"clientKey": "...",
"contract": { "templateKey": "...", "name": "My Contract" },
"placeholders": [
{ "placeholderKey": "...", "value": "filled value" },
{ "name": "placeholder_name", "value": "filled value" }
]
}
}
```
### Create and send in one step
```json
{
"data": {
"action": "create",
"clientKey": "...",
"contract": { "name": "My Contract", "value": "<p>HTML content</p>" },
"recipients": [
{
"email": "signer@example.com",
"action": "sign",
"fullname": "John Signer",
"position": 1,
"customMessage": "Please sign this document"
}
]
}
}
```
Recipient `action` values: `"view"` | `"sign"` | `"approve"` | `"lock"`
### List contracts
```json
{ "data": { "action": "list", "clientKey": "..." } }
```
Response 200: array of `{ contractKey, name, status, createTime, changeTime }`
### Read contract
```json
{
"data": {
"action": "read",
"clientKey": "...",
"contract": { "contractKey": "..." }
}
}
```
Can also identify by `name`. Response includes `value` (HTML content) and `status`.
### Update contract
```json
{
"data": {
"action": "update",
"clientKey": "...",
"contract": { "contractKey": "...", "name": "New Name", "value": "<p>New HTML</p>" }
}
}
```
### Archive contract
```json
{
"data": {
"action": "archive",
"clientKey": "...",
"contract": { "contractKey": "..." }
}
}
```
### Get contract timeline / audit trail
```
GET /api/contract_event?clientKey=CLIENT_KEY&contractKey=CONTRACT_KEY
```
Response 200: array of events with status changes, timestamps, recipient info.
---
## TEMPLATES — POST /api/template
### Create template
```json
{
"data": {
"action": "create",
"clientKey": "...",
"template": {
"name": "My Template",
"value": "<p>Hello {{recipient_name}}, this agreement...</p>"
},
"placeholders": [
{ "name": "recipient_name", "value": "Default Value" }
]
}
}
```
Response 201: `{ templateKey, createTime }`
### List templates
```json
{ "data": { "action": "list", "clientKey": "..." } }
```
### Read template
```json
{
"data": {
"action": "read",
"clientKey": "...",
"template": { "templateKey": "..." }
}
}
```
### Update template
```json
{
"data": {
"action": "update",
"clientKey": "...",
"template": { "templateKey": "...", "name": "New Name", "value": "<p>New HTML</p>" }
}
}
```
### Delete template
```json
{
"data": {
"action": "delete",
"clientKey": "...",
"template": { "templateKey": "..." }
}
}
```
### Convert contract to template
```json
{
"data": {
"action": "convert",
"clientKey": "...",
"template": { "name": "Template Name", "contractKey": "..." }
}
}
```
Note: Content and placeholders are copied; placeholder values are cleared.
---
## PLACEHOLDERS — POST /api/placeholder
### Create placeholder
```json
{
"data": {
"action": "create",
"clientKey": "...",
"contractKey": "...",
"placeholder": { "name": "client_name", "value": "John Doe" }
}
}
```
Response 201: `{ placeholderKey, id, createTime, changeTime, type, position }`
### List placeholders
```json
{
"data": {
"action": "list",
"clientKey": "...",
"contractKey": "..."
}
}
```
Use `templateKey` instead of `contractKey` for template placeholders.
Response: array of `{ id, name, value, placeholderKey, position, createTime, changeTime }`
### Update placeholder (basic)
```json
{
"data": {
"action": "update",
"clientKey": "...",
"contractKey": "...",
"placeholder": { "placeholderKey": "...", "name": "new_name", "value": "new value" }
}
}
```
### Place basic placeholder on PDF ("pdfbasic")
CRITICAL: All values inside `insertion` must be **strings**. `id` starts from `"1"`, not `"0"`. `action` and `clientKey` are required inside each insertion item. Violations cause silent failure (201 response but field not placed).
```json
{
"data": {
"action": "update",
"clientKey": "...",
"contractKey": "...",
"placeholders": [
{
"placeholderKey": "...",
"insertion": [
{
"action": "update",
"clientKey": "...",
"id": "1",
"pageId": "0",
"width": "100",
"height": "100",
"positionX": "1",
"positionY": "1"
}
]
}
]
}
}
```
### Place special placeholder on PDF ("pdfspecial")
Special types: `1` = date signed, `2` = fullname, `3` = email, `4` = signature
The `placeholderKey` for special placeholders comes from the placeholder list response (fetched with `X-Sendforsign-Component: true`). Special placeholders are auto-created when a recipient is added. Their keys follow the pattern `{recipientKey}_{specialType}`.
Same rules as basic: all `insertion` values must be **strings**, `id` starts from `"1"`, and `action`+`clientKey` are required inside insertion.
```json
{
"data": {
"action": "update",
"clientKey": "...",
"contractKey": "...",
"placeholders": [
{
"placeholderKey": "e8c68d6e-27b8-4388-b90d-47f7b3853c4c_4",
"isSpecial": true,
"specialType": 4,
"insertion": [
{
"action": "update",
"clientKey": "...",
"id": "1",
"pageId": "0",
"positionX": "56",
"positionY": "79",
"width": "100",
"height": "50"
}
]
}
]
}
}
```
### Update placeholder — Table
```json
{
"data": {
"action": "update",
"clientKey": "...",
"contractKey": "...",
"placeholder": {
"placeholderKey": "...",
"table": {
"columns": ["Item", "Qty", "Price"],
"rows": [
["Widget A", 10, 9.99],
["Widget B", 5, 19.99]
]
}
}
}
}
```
Note: `placeholderKey` and `table` are nested inside `placeholder` (singular), not at the top `data` level.
### Delete placeholder
```json
{
"data": {
"action": "delete",
"clientKey": "...",
"contractKey": "...",
"placeholder": { "placeholderKey": "..." }
}
}
```
---
## RECIPIENTS — POST /api/recipient
### Create recipient
```json
{
"data": {
"action": "create",
"clientKey": "...",
"contractKey": "...",
"userKey": "... (optional)",
"recipients": [
{
"action": "sign",
"fullname": "John Signer",
"email": "signer@example.com",
"position": 1,
"customMessage": "Please review and sign"
}
]
}
}
```
Note: `recipients` is an ARRAY — even for a single recipient, wrap it in `[]`.
Response 200: `{ result: true, recipientKey }`
### List recipients
```json
{
"data": {
"action": "list",
"clientKey": "...",
"contractKey": "... (optional)"
}
}
```
Response: array of `{ id, recipientKey, email, fullname, customMessage, position, action, createTime, changeTime }`
### Update recipient
```json
{
"data": {
"action": "update",
"clientKey": "...",
"contractKey": "...",
"recipient": {
"recipientKey": "...",
"action": "approve",
"fullname": "New Name",
"email": "new@email.com",
"position": 2,
"customMessage": "Updated message"
}
}
}
```
### Delete recipient
```json
{
"data": {
"action": "delete",
"clientKey": "...",
"contractKey": "...",
"recipient": { "recipientKey": "..." }
}
}
```
### Send contract to recipients
```json
{
"data": {
"action": "send",
"clientKey": "...",
"contractKey": "...",
"recipients": [
{
"email": "signer@example.com",
"action": "sign",
"fullname": "John Signer",
"position": 1,
"customMessage": "Please sign"
},
{
"email": "viewer@example.com",
"action": "view",
"position": 2
}
]
}
}
```
To update existing recipient while sending, include `recipientKey` in the recipient object.
---
## DOCUMENTS
### Download PDF
```
GET /api/download_pdf?clientKey=CLIENT_KEY&contractKey=CONTRACT_KEY
Header: X-Sendforsign-Key: API_KEY
```
Response: binary PDF (ArrayBuffer / file stream). Save with `--output file.pdf` in curl.
### Download Word (DOCX)
```
GET /api/download_docx?clientKey=CLIENT_KEY&contractKey=CONTRACT_KEY
```
Save with `--output file.docx`.
### Upload PDF
First create contract with `contractType: "pdf"`, then:
```
POST /api/upload_pdf?clientKey=CLIENT_KEY&contractKey=CONTRACT_KEY
Content-Type: multipart/form-data
Field name: "pdf", MIME type: application/pdf
```
```bash
curl -X POST "https://api.sendforsign.com/api/upload_pdf?clientKey=...&contractKey=..." \
-H "X-Sendforsign-Key: $API_KEY" \
-F "pdf=@document.pdf;type=application/pdf"
```
---
## WEBHOOKS — POST /api/webhook
Event types: `Contract.created`, `Contract.sent`, `Contract.seen`, `Contract.approved`, `Contract.signed`, `Contract.fully_signed`
Webhook payloads include: `clientKey`, `contractKey`, `createTime`, `status`, recipient info (email, fullname, recipientKey), contract name.
### Create webhook
```json
{
"data": {
"action": "create",
"clientKey": "...",
"webhooks": [
{
"url": "https://yourapp.com/webhook",
"created": true,
"seen": true,
"sent": true,
"approved": true,
"signed": true,
"fullySigned": true
}
]
}
}
```
Response 201: `{ webhookKey, url, secret (whsec_...), createTime }`
### List webhooks
```json
{ "data": { "action": "list", "clientKey": "..." } }
```
### Read webhook
```json
{
"data": {
"action": "read",
"clientKey": "...",
"webhooks": [{ "webhookKey": "..." }]
}
}
```
### Update webhook
```json
{
"data": {
"action": "update",
"clientKey": "...",
"webhooks": [
{
"webhookKey": "...",
"url": "https://new-url.com/webhook",
"signed": false,
"fullySigned": true
}
]
}
}
```
### Delete webhook
```json
{
"data": {
"action": "delete",
"clientKey": "...",
"webhooks": [{ "webhookKey": "..." }]
}
}
```
---
## SETTINGS — POST /api/settings
### Custom branding
```json
{
"data": {
"type": "brandings",
"clientKey": "...",
"branding": {
"emailSenderName": "My Company",
"emailExplainer": "Custom email footer text",
"emailLogo": "https://example.com/logo.png",
"buttonColor": "#FF5733",
"emailDomain": "mail.mycompany.com",
"recipientLinkDomain": "sign.mycompany.com"
}
}
}
```
Note: `emailDomain` and `recipientLinkDomain` require Business plan.
### Email notifications
```json
{
"data": {
"type": "email_notifications",
"clientKey": "...",
"notification": {
"approved": true,
"signed": true
}
}
}
```
---
## TOKENS — POST /api/token
Tokens are short-lived (1800 seconds / 30 min). Use for OAuth 2.0 compliant flows.
### Generate platform token
```json
{
"apiKey": "...",
"apiSecret": "..."
}
```
No auth header needed for this endpoint.
### Generate client token
```json
{
"apiKey": "...",
"apiSecret": "...",
"clientKey": "..."
}
```
### Revoke token
```json
{
"action": "revoke",
"token": "..."
}
```
Returns 401 on expired tokens — handle with token refresh logic.
---
## AI API (Beta) — POST https://aiapi.sendforsign.com/webhook/aiapi
```bash
curl -X POST https://aiapi.sendforsign.com/webhook/aiapi \
-H "X-Sendforsign-Key: $API_KEY" \
-H "clientKey: $CLIENT_KEY" \
-H "secretKey: $SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Create a non-disclosure agreement between TechCorp and John Doe"}'
```
Capabilities:
1. Generate contracts in free format with legal best practices
2. Create contracts from existing templates
3. Populate template placeholders with provided data
4. List available templates
5. View placeholder fields for templates
Response: AI-generated text + contract details including unique identifier and 30-minute preview URL.

View File

@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""
Render PDF pages to PNG images AND extract exact text positions for SendForSign.
Usage:
python render_pdf.py <pdf_path> [output_dir]
Output: JSON with per-page image paths + text elements with pre-calculated API coordinates.
Coordinate system:
SendForSign API uses a 1000px-wide coordinate system for ALL PDFs.
Text positions come from pdftohtml -xml (PDF points, page width ~595pt for A4).
They are scaled to 1000px space using: api_coord = round(pdf_pt * 1000 / page_width_pts)
Images are rendered at exactly 1000px wide for visual reference.
Pixel coordinate in the image = API coordinate directly.
Workflow:
1. Read image_path to visually identify which fields need placeholders.
2. Search text_elements for the exact text string to get precise api_x/api_y/api_w/api_h.
3. Use those api_* values directly in the placeholder placement API call.
4. For non-text areas (images, empty boxes), estimate from the 1000px image visually.
"""
import subprocess
import json
import sys
import re
import struct
import tempfile
import xml.etree.ElementTree as ET
from pathlib import Path
def get_png_dimensions(png_path: str) -> tuple[int, int]:
"""Read width and height from PNG header (bytes 16-24)."""
try:
with open(png_path, "rb") as f:
f.seek(16)
w = struct.unpack(">I", f.read(4))[0]
h = struct.unpack(">I", f.read(4))[0]
return w, h
except Exception:
return 0, 0
def extract_text_positions(pdf_path: str, output_dir: Path) -> dict:
"""
Run pdftohtml -xml to get per-page text elements with exact PDF-point coordinates.
Returns dict: {page_number (1-indexed): {"width_pts": W, "height_pts": H, "texts": [...]}}
Each text: {"text": str, "left": int, "top": int, "width": int, "height": int}
"""
xml_base = str(output_dir / "pdftext")
result = subprocess.run(
["pdftohtml", "-xml", "-noframes", "-zoom", "1", str(pdf_path), xml_base],
capture_output=True, text=True
)
xml_path = Path(xml_base + ".xml")
if not xml_path.exists():
return {}
try:
tree = ET.parse(str(xml_path))
root = tree.getroot()
except ET.ParseError:
return {}
pages = {}
for page_el in root.findall("page"):
page_num = int(page_el.get("number", 1))
page_w = float(page_el.get("width", 595))
page_h = float(page_el.get("height", 841))
scale = 1000.0 / page_w
texts = []
for text_el in page_el.findall("text"):
raw = "".join(text_el.itertext()).strip()
if not raw:
continue
left = int(text_el.get("left", 0))
top = int(text_el.get("top", 0))
w = int(text_el.get("width", 0))
h = int(text_el.get("height", 0))
texts.append({
"text": raw,
"pdf_left": left, "pdf_top": top, "pdf_width": w, "pdf_height": h,
"api_x": round(left * scale),
"api_y": round(top * scale),
"api_w": round(w * scale),
"api_h": round(h * scale),
})
pages[page_num] = {
"width_pts": page_w,
"height_pts": page_h,
"scale_to_api": round(scale, 6),
"texts": texts,
}
return pages
def render_pdf(pdf_path: str, output_dir: str = None) -> dict:
pdf_path = Path(pdf_path).resolve()
if not pdf_path.exists():
return {"error": f"File not found: {pdf_path}"}
if output_dir is None:
output_dir = Path(tempfile.mkdtemp(prefix="sfs_pdf_"))
else:
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Render images at exactly 1000px wide
prefix = str(output_dir / "page")
render_result = subprocess.run(
["pdftoppm", "-scale-to-x", "1000", "-scale-to-y", "-1", "-png",
str(pdf_path), prefix],
capture_output=True, text=True
)
if render_result.returncode != 0:
return {"error": f"pdftoppm failed: {render_result.stderr}"}
image_files = sorted(output_dir.glob("page-*.png"))
if not image_files:
image_files = sorted(output_dir.glob("page.png"))
# Extract exact text positions from PDF
text_data = extract_text_positions(str(pdf_path), output_dir)
pages = []
for i, img_file in enumerate(image_files):
w_px, h_px = get_png_dimensions(str(img_file))
page_num = i + 1 # pdftohtml uses 1-indexed pages
page_texts = text_data.get(page_num, {})
scale_to_api = round(1000.0 / w_px, 6) if w_px > 0 else 1.0
pages.append({
"page_id": i,
"image_path": str(img_file),
"width_px": w_px,
"height_px": h_px,
"scale_to_api": scale_to_api,
"text_elements": page_texts.get("texts", []),
"coordinate_hint": (
"Image is 1000px wide — pixel = API coord for visual estimates. "
"For text fields: use api_x/api_y/api_w/api_h from text_elements directly."
),
})
return {
"pdf_path": str(pdf_path),
"page_count": len(pages),
"pages": pages,
"instructions": (
"To place a placeholder over existing text: "
"1) Search text_elements for the text string. "
"2) Use its api_x/api_y/api_w/api_h directly as positionX/positionY/width/height. "
"3) Add 2-3px padding if needed (increase width/height slightly). "
"For non-text areas: read image_path visually and estimate pixel coords (= API coords at 1000px width). "
"Use page_id as pageId in the API (0-indexed)."
)
}
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python render_pdf.py <pdf_path> [output_dir]", file=sys.stderr)
sys.exit(1)
pdf = sys.argv[1]
out = sys.argv[2] if len(sys.argv) > 2 else None
result = render_pdf(pdf, out)
print(json.dumps(result, indent=2, ensure_ascii=False))