- Главная
- Документация
- Документация API
Документация API
Ключи и области, заявки, комментарии, выгрузка, почта, чаты, звонки, ошибки и примеры.
Версия API: v1. Базовый адрес: https://crm.linkodium.com.
API нужен, чтобы заявки и звонки попадали в CRM автоматически, а внешние системы работали с заявками, почтой и чатами сотрудников:
- заявки с форм сайта, из квизов, чат-ботов и рекламных кабинетов (раздел 8);
- чтение, правка и выгрузка заявок, лента событий с записями и расшифровками звонков, комментарии с файлами (синхронизация с 1С, учётной системой, своим ботом, разделы 9-14). Это делает ключ сотрудника с его правами (раздел 1);
- почта и чаты сотрудника: чтение переписки, отправка писем и ответы клиентам в Telegram, MAX, Авито и виджете сайта от его имени (разделы 15 и 16);
- звонки из любой АТС (Novofon, Zadarma, Mango, Asterisk и других) вместе с записями разговоров (разделы 17-19);
- всё остальное, что сотрудник делает в кабинете: импорт заявок, расшифровка звонков, правка и удаление сообщений, управление ящиками почты (разделы 21-24), настройки компании, сотрудники, ключи, интеграции и профиль (отдельная страница);
- ИИ-ассистенты Claude, ChatGPT и Cursor по протоколу MCP (раздел 26).
Содержание
- Ключ API
- Аутентификация
- Формат запросов и ответов
- Ошибки
- Лимиты
- Идемпотентность
- Справочники: GET /api/v1/me, /managers, /statuses, /services
- Заявки: POST /api/v1/leads
- Список заявок: GET /api/v1/leads
- Заявка: GET и PATCH /api/v1/leads/{номер}
- Статус, архив, объединение
- Лента событий: GET /api/v1/leads/{номер}/events
- Комментарии и файлы
- Выгрузка: GET /api/v1/leads/export.xlsx
- Почта: /api/v1/mail
- Чаты: /api/v1/chats
- Телефония: POST /api/v1/calls
- Запись звонка файлом: POST /api/v1/calls/recording
- Пример прослойки для Novofon и Zadarma
- Переход со старых адресов
- Импорт через API
- Расшифровка звонков
- Чаты: правка, удаление, стикеры, голосовые
- Почта: ящики, участники, удаление
- Компания, сотрудники, интеграции
- ИИ-ассистенты (MCP)
- Изменения API
1. Ключ API
Ключ выпускает администратор компании для любого сотрудника: Настройки компании -> API-ключи -> Выпустить ключ. Сотрудник может выпустить ключ и сам себе: Профиль -> API-ключи, если администратор не запретил это в «Правах доступа» (право «Выпускать себе API-ключи в профиле», по умолчанию разрешено). При выпуске ключу дают название (например, «Форма на сайте»), по нему ключ потом легко найти в списке.
- Ключ выглядит так:
crm_и 40 символов0-9a-f, напримерcrm_3f9a.... - Полное значение показывается один раз, сразу после выпуска. Скопируйте его и сохраните в настройках своего сервера. CRM хранит только отпечаток ключа и показать его снова не может. В списке ключей видны только первые символы. Если ключ потерян, выпустите новый и отзовите старый.
- Ключ привязан к компании: всё, что приходит с ним, попадает в эту компанию. Выбрать другую компанию полем запроса нельзя.
- Ключ привязан к сотруднику и действует с его правами. Он видит только те заявки, которые видит сотрудник (при видимости «только свои» только его заявки), и не делает того, что сотруднику запрещено в «Правах доступа». События в ленте заявки подписаны сотрудником с пометкой «через API» (кроме событий звонков из раздела 17 и события «создана» у заявки, созданной звонком: они, как и прежде, пишутся без автора).
- Права сотрудника действуют и на телефонию (
calls:write): ключ сотрудника с видимостью «только свои» не показывает номера чужих заявок и не принимает записи к их звонкам (раздел 17). Для телефонии всей компании выпустите ключ администратору или сотруднику с видимостью «все заявки»; прежний ключ компании (ниже) работает как раньше. - При выпуске выбираются области ключа: какие адреса API он вызывает. Области не дают больше прав, чем есть у сотрудника:
leads:read,leads:create,leads:write,leads:export,calls:write, а также почта и чаты:mail:read(ящики, переписка, вложения, «прочитано»),mail:send(отправка писем, привязка переписки к заявке),chats:read(чаты, сообщения, файлы, «прочитано»),chats:write(ответы клиентам, привязка чата к заявке). Почта по ключу сотрудника: только ящики, где он участник, и только по ключу, который сотрудник выпустил себе сам (mail:readиmail:sendтолько себе); чаты: по его режиму доступа к чатам. - Ключ перестаёт действовать сам, если сотрудника исключили из компании или отключили его учётную запись (ответ
401). - Ключи, выпущенные до появления ключей сотрудников, остаются ключами компании: они по-прежнему создают заявки (раздел 8) и передают звонки (разделы 17, 18), а на прочие адреса отвечают
403 SCOPE_REQUIRED. Для чтения и правки заявок выпустите ключ сотрудника. - Заведите отдельный ключ на каждую интеграцию (форма сайта, телефония, чат-бот, синхронизация). Тогда при утечке одного ключа достаточно отозвать только его. Для формы сайта достаточно области
leads:create, для телефонииcalls:write. - Отозвать ключ можно там же, в списке ключей; сотрудник отзывает свои ключи в профиле. Отзыв действует сразу.
- Кнопка «Выйти на всех устройствах» в профиле ключи не отзывает: ключ принадлежит интеграции, а не устройству. Исключение сотрудника из компании, смена его роли и его выход из компании отзывают ключи, которые он выпустил и которые привязаны к нему, только с отметкой «Отозвать API-ключи» в карточке сотрудника и у кнопки «Покинуть компанию». Но ключ, привязанный к самому сотруднику, после его исключения или выхода из компании не действует и без отметки (ответ
401); без отзыва он снова заработает, если сотрудника пригласят обратно. - У компании может быть до 100 действующих ключей, у сотрудника в компании до 10.
| Область | Что даёт |
|---|---|
leads:read | список и карточка заявок, лента событий, файлы, справочники |
leads:create | создание заявок (POST /api/v1/leads) |
leads:write | правка, статус, архив, объединение, комментарии; включает leads:create |
leads:export | выгрузка заявок в XLSX |
calls:write | звонки и записи разговоров (/api/v1/calls, /api/v1/calls/recording) |
mail:read | почта сотрудника: ящики, переписка, тела писем, вложения, отметка «прочитано» (раздел 15); только себе |
mail:send | отправка писем и привязка переписки к заявке (раздел 15); только себе |
chats:read | чаты, которые видит сотрудник: список, сообщения, файлы, отметка «прочитано» (раздел 16) |
chats:write | ответы клиентам в чатах и привязка чата к заявке (раздел 16) |
leads:import | импорт заявок из файла XLSX или CSV (раздел 21) |
calls:transcribe | запрос расшифровки записей звонков (раздел 22) |
chats:edit | изменение своих сообщений и удаление их у клиента (раздел 23); только себе |
chats:admin | администратор: удаление сообщений и переписок из CRM, блокировка посетителей виджета (раздел 23); только себе |
mail:remove | удаление писем и переписки: из CRM и в «Корзину» почтового ящика (раздел 24); только себе |
mail:manage | ящики: подключение, настройки, участники, загрузка ранних писем, выход и отключение (раздел 24); только себе |
company:read | администратор: просмотр настроек компании, сотрудников, приглашений, прав, ключей и интеграций (API компании) |
company:write | администратор: настройки компании, права доступа, услуги и быстрые ссылки; включает company:read; только себе |
team:write | администратор: приглашения, роли, личные права, менеджер по умолчанию, исключение сотрудников; включает company:read; только себе |
integrations:write | администратор: подключение и настройка Telegram, MAX и виджета; включает company:read; только себе |
keys:write | выпуск и отзыв API-ключей, не шире областей этого ключа; только себе |
company:transfer | владелец: передача владения компанией; только себе |
profile:read | профиль: имя, компании, приглашения, уведомления, свои ключи; только себе |
profile:write | профиль: имя и фото, уведомления, отвязка Telegram, компании и приглашения, уход из компании, завершение сессий; включает profile:read; только себе |
support:write | обращения в поддержку: отправка, переписка, ответы и файлы; только себе |
visitors:read | IP-адрес и браузер заявки (в том числе строка IP в тексте повторного обращения, раздел 12), карточка посетителя чата на сайте |
tracking:read | администратор: просмотр трекинга: сайты, телефонные номера, боты, Метрика, ДАРА без ключей, конверсии, касания и визиты (API компании) |
tracking:write | администратор: настройка трекинга: сайты, номера, Метрика, ДАРА, цели конверсий; включает tracking:read; только себе |
Новые области по выбору: области из второй части таблицы (с leads:import) появились 09.10.2026 и выдаются только по выбору при выпуске ключа. Прежние ключи их не получают и работают ровно как раньше.
Только себе: почту, правку сообщений чатов, области управления компанией и профилем (с пометкой «только себе») сотрудник выпускает только себе, в профиле, а администратор себе на вкладке «API-ключи». На ключ другого сотрудника они не выпускаются: на вкладке «API-ключи» ошибка «Область «Почта: Чтение» сотрудник выпускает только себе в профиле», через API 403 PERMISSION_DENIED с permission: "keys.self_only". У ключа, выпущенного за другого, они не действуют и при вызове (403 SCOPE_REQUIRED; GET /api/v1/me их в key.scopes не показывает, прочие области такого ключа работают). Ключ с keys:write не выпускает ключ с областями шире своих.
С 14.10.2026 «только себе» и почтовые области mail:read и mail:send: ключ, который администратор выпустил за сотрудника, его почту не читает и писем от его имени не отправляет, в том числе ключи, выпущенные так раньше. Для почты сотрудник выпускает ключ себе сам в профиле.
Где ключ хранить нельзя: в коде страницы сайта, в JavaScript, в мобильном приложении, в публичном репозитории, в адресе запроса (?key=...). Адрес запроса попадает в журналы серверов и историю браузера, поэтому ключ из адреса CRM не читает вовсе.
2. Аутентификация
Ключ передаётся в заголовке запроса. Подходит любой из двух вариантов:
X-API-Key: crm_0123456789abcdef0123456789abcdef01234567
Authorization: Bearer crm_0123456789abcdef0123456789abcdef01234567
Нет ключа, ключ неверный или отозван, компания заблокирована, сотрудника ключа нет в компании или его учётная запись отключена: ответ 401 UNAUTHORIZED. Без ключа или с неверным ключом (в ответе ещё заголовок WWW-Authenticate: Bearer) текст такой: «Нужен действующий API-ключ в заголовке X-API-Key (или Authorization: Bearer). Ключ выпускает администратор в настройках компании (раздел API-ключи) или сотрудник сам себе в профиле (раздел API-ключи).». У ключа нет нужной области: 403 SCOPE_REQUIRED.
Проверить ключ: GET /api/v1/me (раздел 7) отвечает, чей это ключ, какие у него области и права сотрудника.
curl https://crm.linkodium.com/api/v1/me -H "X-API-Key: $CRM_API_KEY"
3. Формат запросов и ответов
- Только HTTPS. Методы:
GET(чтение),POST(создание и действия),PATCH(правка заявки, настроек компании и каналов, профиля),PUT(замена списка: услуги, быстрые ссылки, настройки заявок из чатов),DELETE(удаление комментария). Адрес без нужного метода отвечает405с заголовкомAllow. - Необратимые действия (удаление писем и переписки, отключение ящика или канала, исключение сотрудника, передача владения и другие, отмечены в таблицах) требуют в теле
"confirm": true, иначе400 CONFIRM_REQUIRED. - Тело запроса: JSON-объект (
Content-Type: application/json, кодировка UTF-8) или обычная форма. УPOSTформа бываетapplication/x-www-form-urlencodedилиmultipart/form-data(файлы: запись звонка, комментарий, письмо, ответ в чат, импорт, фото). УPATCH,PUTиDELETEтолько JSON илиapplication/x-www-form-urlencoded: телоmultipart/form-dataу них не разбирается, поля теряются, и запрос получает400. - Время в ответах: ISO-8601 в UTC с
Z(2026-10-03T09:15:00Z). Время в параметрах: ISO-8601 со смещением илиZ(2026-10-03T12:15:00+03:00). - Сотрудники, события, файлы, чаты и переписка обозначаются
uuid, заявки номером в компании. - Списки постраничные: параметр
limit(до 100, по умолчанию 50), в ответеitems,has_moreиnext_cursor; следующая страница с параметромcursor=<next_cursor>и теми же остальными параметрами. - Тело, которое начинается с
{или[, разбирается как JSON при любомContent-Type. Если это не корректный JSON-объект (ошибка в JSON или массив вместо объекта), ответ400 VALIDATION_ERROR. - Ответ JSON в UTF-8, в том числе отказы, которые сервер выдаёт до разбора запроса: слишком частые запросы (
429), слишком большое тело (413), технические работы (503). Обработчик запросов не ответил вовремя (например, перезапуск во время обновления CRM или запрос дольше предела сервера): ответ503 SERVICE_UNAVAILABLEв том же JSON с заголовкомRetry-After: 5. Сами502и504CRM не отдаёт: их может вернуть только промежуточный прокси между вами и CRM, и тогда без JSON. Проверяйте код ответа до разбора тела и повторяйте502и504так же, как503. - Адреса пишутся строчными буквами, как в таблицах разделов. Путь, который начинается не со строчной латинской буквы (
/api/v1/Leads,/api/v1/1), и сам/api/v1без пути до API не доходят: сервер отвечает обычной HTML-страницей404, а не JSON. Прочий неизвестный адрес под/api/v1/отвечает JSON404 NOT_FOUND.
Успешный ответ:
{
"success": true,
"data": { "lead_number": 128, "is_duplicate": false, "message": "Заявка создана" }
}
Ответ с ошибкой:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Ошибка валидации данных",
"details": ["Некорректный формат email"]
}
}
lead_number: номер заявки внутри вашей компании, тот же, что менеджеры видят в CRM (#128).- Неизвестные поля запроса молча игнорируются, кроме адресов компании, сотрудников, приглашений, прав, услуг и быстрых ссылок, ключей компании и интеграций (
/company, в том числе/company/leaveи/company/transfer-ownership,/members,/invites,/permissions,/services/items,/sidebar-links,/api-keys,/integrations, API компании): там неизвестное поле тела даёт400 VALIDATION_ERROR(вerror.detailsстрока «<поле>: неизвестное поле» и список допустимых полей), и ничего не сохраняется. Поляclient_request_idиconfirmдопустимы везде. - Ответы не кэшируются (
Cache-Control: no-store). - В каждом ответе есть заголовок
X-Request-ID. Если пишете в поддержку о конкретном запросе, приложите его значение.
4. Ошибки
| HTTP | error.code | Что случилось | Что делать |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Данные не прошли проверку. Список причин в error.details | Исправить запрос. Повторять без изменений бесполезно |
| 400 | CONFIRM_REQUIRED | Необратимое действие без "confirm": true в теле (раздел 3) или присоединение к уже подключённому ящику почты без confirm (раздел 24, сервер проверки пароля в error.join_confirm) | Убедиться, что действие нужно, и передать confirm |
| 401 | UNAUTHORIZED | Нет ключа, ключ неверный или отозван, компания заблокирована, сотрудника ключа нет в компании или его учётная запись отключена | Проверить заголовок и ключ |
| 403 | SCOPE_REQUIRED | У ключа нет нужной области (error.scope) или это ключ компании на адресе для ключа сотрудника | Выпустить ключ сотрудника с этой областью |
| 403 | PERMISSION_DENIED | Права сотрудника запрещают действие (error.permission, например leads.edit) | Попросить администратора изменить права сотрудника |
| 403 | KEY_NOT_ALLOWED | Ключ сотрудника на старом адресе (раздел 20) | Перейти на адреса /api/v1 |
| 403 | QUOTA_EXCEEDED | Хранилище компании заполнено: файл комментария, письма, ответа в чат или голосовое либо запись звонка не приняты (разделы 13, 15, 16, 18, 23) | Освободить место или обратиться в поддержку |
| 404 | NOT_FOUND | Адреса нет (JSON только для пути со строчной буквы, раздел 3); заявки, комментария или файла нет, они в другой компании или сотрудник ключа их не видит | Проверить адрес и номер. Повтор бесполезен |
| 404 | CALL_NOT_FOUND | Звонка с таким call_id в CRM нет: пришло событие без номера клиента, а начала звонка ещё не было (раздел 17), или загружается файл к незаведённому звонку либо к звонку в заявке, которую сотрудник ключа не видит (раздел 18) | Если начало звонка может прийти позже, повторить через минуту. Для записи исходящего звонка, который не сохранялся (раздел 17), это штатный ответ |
| 405 | METHOD_NOT_ALLOWED | Метод не поддерживается адресом (список в заголовке Allow) | Использовать метод из Allow |
| 409 | LEAD_MERGED | Заявка объединена с другой, номер главной в error.merged_into | Работать с главной заявкой |
| 409 | VERSION_CONFLICT | Заявка изменилась после того, как вы получили её версию (раздел 10), текущая в error.current_version | Получить заявку заново и повторить правку |
| 409 | CONFLICT | Заявку в эту же секунду изменил другой запрос (например, статус) | Получить заявку заново и решить, нужно ли действие |
| 409 | IDEMPOTENCY_IN_PROGRESS | Запрос с этим ключом идемпотентности ещё выполняется (раздел 6) | Повторить через Retry-After секунд |
| 409 | MAIL_ACCOUNT_INACTIVE | Ящик не может отправлять письма: отключён или не принят пароль SMTP (раздел 15) | Исправить подключение ящика в CRM |
| 409 | SEND_QUOTA | С адреса ящика в этой компании уже отправлено 100 писем за час (error.retry_at, раздел 15) | Повторить после retry_at (Retry-After) |
| 409 | SERVICE_PAUSED | Почта временно приостановлена | Повторить позже, см. Retry-After |
| 409 | ATTACHMENT_UNAVAILABLE | Вложение письма не скачать с сервера почты (error.state: too_big, gone, no_space, error) | error: повторить с ?retry=1 (раздел 15); иначе открыть письмо в почтовой программе |
| 409 | DUPLICATE_FOUND | Клиент из письма уже есть в активной заявке (раздел 24). error.reason: confirm_duplicate (номер заявки в error.lead) или duplicate_hidden (заявка, которую сотрудник ключа не видит: другого сотрудника или без ответственного) | confirm_duplicate: повторить с confirm_duplicate: true или привязать письмо к найденной заявке. duplicate_hidden: повтор не поможет, привязать переписку может ответственный той заявки или администратор |
| 409 | FEATURE_DISABLED | Возможность выключена в настройках компании или не настроена (расшифровка звонков, раздел 22) | Включить в настройках компании |
| 409 | CHAT_UNAVAILABLE | Ответить в чат нельзя (error.reason: blocked клиент заблокировал бота, channel_disabled канал отключён, token_invalid), раздел 16 | Повтор бесполезен до исправления канала |
| 413 | PAYLOAD_TOO_LARGE | Тело запроса или файл больше допустимого, в том числе все файлы одного запроса вместе (раздел 5) | Уменьшить файл (например, mp3 вместо wav) или разделить файлы на несколько запросов |
| 422 | IDEMPOTENCY_MISMATCH | Ключ идемпотентности уже использован для другого запроса | Для нового запроса взять новый ключ |
| 422 | TOO_MANY_ROWS | Выгрузка больше допустимого числа строк (раздел 14) | Сузить фильтры |
| 428 | PRECONDITION_REQUIRED | Правка заявки без версии (раздел 10) | Передать If-Match или version |
| 429 | RATE_LIMITED | Превышен лимит запросов или одновременных загрузок | Повторить позже, см. заголовок Retry-After |
| 429 | QUEUE_FULL | Очередь расшифровки заполнена (раздел 22) или, у старого адреса /api/call.record, очередь скачивания записей звонков (раздел 20; в /api/v1/calls та же причина приходит полем recording_error, раздел 17) | Повторить через Retry-After секунд |
| 500 | INTERNAL_ERROR | Сбой на стороне CRM | Повторить с паузой |
| 503 | MAINTENANCE | Идут технические работы | Повторить через несколько минут, см. Retry-After |
| 503 | SERVICE_UNAVAILABLE | Сервис временно недоступен (в том числе обработчик запросов не ответил вовремя, раздел 3) или возможность ждёт обновления базы данных (текст «… станет доступна после обновления базы данных») | Повторить через минуту; если есть заголовок Retry-After, не раньше. Ответ «после обновления базы данных» повтором в ближайшие минуты не исправляется |
Рекомендация по повторам: при 429, 500, 502, 503, 504 и обрыве связи повторяйте запрос с растущей паузой (например, 10 секунд, 1 минута, 5 минут), а если в ответе есть заголовок Retry-After, не раньше указанного в нём числа секунд. Повтор безопасен: /api/v1/calls и загрузка записи идемпотентны, а создание заявки и прочие действия повторяйте с тем же заголовком Idempotency-Key (раздел 6). Для заявки ключ идемпотентности обязателен, чтобы повтор был безопасен: повторное обращение (раздел 8) распознаётся только у активной заявки с email или телефоном от 10 цифр. Заявка, созданная сразу в этапе «Успешно», «Провал» или «Спам», заявка только с именем, с телефоном короче 10 цифр без email или с dedup: false при повторе без ключа идемпотентности создастся второй раз (и для этапов кроме «Спама» менеджеры получат второе уведомление). 404 CALL_NOT_FOUND повторяйте, только если начало звонка ещё может прийти. Ошибки 400, 401, 403, 404, 405, 413, 422 повтором не исправляются.
Отказ принять запись разговора по ссылке ошибкой всего запроса не считается: звонок и заявка сохраняются, а причина отказа приходит в ответе полем recording_error (раздел 17).
5. Лимиты
| Что | Лимит |
|---|---|
| Запросов на один ключ | 3000 за 5 минут |
Изменений (все методы, кроме GET: POST, PATCH, PUT, DELETE) на один ключ сотрудника | 1500 за 5 минут (в общие 3000 тоже входят). Звонки и записи (/api/v1/calls, /api/v1/calls/recording) сюда не входят, у них только общий лимит |
| Выгрузок XLSX | 30 за 10 минут на сотрудника |
Отправок писем (POST /api/v1/mail/send) на один ключ | 100 за 5 минут; ещё квота ящика: 100 писем в час с адреса ящика (считаются письма этой компании), до 10 получателей в письме |
Ответов в чаты (POST /api/v1/chats/{uuid}/messages) на один ключ | 300 за 5 минут; ещё 30 ответов в минуту одному клиенту и 90 в минуту от сотрудника (Telegram, MAX и Авито) |
| Запросов с одного IP-адреса | около 100 запросов в секунду с одного IP-адреса, пачка до 1000 разом: мягкое ограничение от флуда, живая интеграция в него не упирается |
| Одновременных загрузок файлов (запись звонка, комментарий, письмо и ответ в чат с файлами, а также чтение сообщений чата) с одного IP-адреса | 8 |
| Размер тела запроса (JSON, форма) | 3 МБ |
| Файл записи звонка, файл комментария | 100 МБ |
| Файлы одного запроса вместе (комментарий, ответ в чат, письмо, голосовое) | всё тело запроса до 110 МБ, больше: 413 PAYLOAD_TOO_LARGE |
| Файлы письма | до 10, вместе до 25 МБ |
| Файл импорта заявок (раздел 21) | XLSX или CSV до 20 МБ, до 10 000 строк |
| Файлы ответа в чат | до 10, каждый до предела канала (Telegram и MAX 50 МБ, виджет 25 МБ, Авито 24 МБ и только картинки), вместе до 110 МБ |
| Строк на странице списка | 100 |
| Запись звонка по ссылке | до 200 МБ |
| Записей по ссылке, ждущих скачивания | 20 на одну заявку, 200 на компанию, 400 на все компании одного владельца |
| Действующих ключей | 100 у компании, 10 у сотрудника в компании |
Превышение лимита запросов или одновременных загрузок: 429 RATE_LIMITED с заголовком Retry-After.
Очередь записей по ссылке. Пока у компании меньше 20 записей ждут скачивания, её новые записи принимаются всегда, как бы ни была загружена очередь остальных компаний (предел в 20 записей на одну заявку действует и тогда). Если очередь заполнена (своя, общая для компаний владельца или общая для сервиса), звонок всё равно сохраняется, а запись получает отказ QUEUE_FULL в поле recording_error (раздел 17). Живая телефония держит в очереди единицы записей и в этот потолок не упирается.
6. Идемпотентность
Создание заявки, статус, архив, возврат из архива, объединение, комментарий, письмо, ответ в чат и привязка переписки или чата к заявке можно безопасно повторять: передайте заголовок Idempotency-Key (или поле client_request_id в теле), 1-255 видимых символов ASCII, уникальных для каждого нового действия (например, номер заказа в вашей системе или UUID).
- Повтор того же запроса с тем же ключом в течение суток не выполняет действие второй раз: CRM отдаёт сохранённый ответ (тот же код и тело) с заголовком
Idempotent-Replayed: true. - Тот же ключ с другим запросом (другой адрес, тело или файлы):
422 IDEMPOTENCY_MISMATCH. - Первый запрос ещё выполняется:
409 IDEMPOTENCY_IN_PROGRESSсRetry-After. - Сохраняются только успешные ответы. Если запрос получил ошибку (
400,403,409и другие), исправьте его и повторите с тем же ключом. - Правка заявки (
PATCH) защищена версией (раздел 10), звонки распознаются поcall_id, а удаление комментария безопасно повторять и так: повтор тем же ключом в течение суток отвечает200{deleted: true, already: true}(раздел 13). - Новые адреса, которые создают сущность или отправляют сообщение (спам, импорт, расшифровка, стикер и голосовое, правка и повтор сообщения, участник ящика, заявка из письма, приглашение, виджет, компания, обращение в поддержку), принимают ключ идемпотентности так же. Выпуск API-ключа его не принимает: сырой ключ не хранится, повтор выпустит второй ключ.
- Письмо и ответ в чат с ключом идемпотентности не уйдут дважды и после суток: повтор отвечает
200сduplicate: trueи тем же письмом или сообщением, файлы второй раз не сохраняются.
7. Справочники: GET /api/v1/me, /managers, /statuses, /services
GET /api/v1/me отвечает любому действующему ключу: чей ключ, его области, права сотрудника и лимиты.
{
"success": true,
"data": {
"key": { "uuid": "5f0e...", "name": "Синхронизация", "prefix": "crm_3f9a01b2", "scopes": ["leads:read", "leads:write"], "personal": true, "created_at": "2026-10-03T09:00:00Z" },
"company": { "uuid": "b150...", "name": "Linkodium", "timezone": "Europe/Moscow" },
"user": { "uuid": "1b4e...", "name": "Анна", "email": "anna@example.com", "role": "manager" },
"permissions": { "leads.visibility": "own", "leads.create": true, "leads.edit": true, "leads.status": true, "leads.archive": true, "leads.reassign": false, "leads.merge": false, "leads.comment": true, "leads.export": true, "leads.import": false, "api.keys": true },
"limits": { "requests_per_5_minutes": 3000, "writes_per_5_minutes": 1500, "page_size_max": 100 }
}
}
У ключа компании user и permissions равны null, personal: false.
Справочники (ключ сотрудника с областью leads:read, leads:create или leads:write), ответ { "items": [...] }:
| Адрес | Элемент |
|---|---|
GET /api/v1/managers | работающий сотрудник: uuid, name, role (owner, admin, manager), is_default (менеджер по умолчанию) |
GET /api/v1/statuses | этап заявки: id (1, 2, 5, 6, 7), name, color, requires_reason (у «Провала» нужна причина) |
GET /api/v1/services | услуга из справочника компании: name |
8. Заявки: POST /api/v1/leads
Создаёт заявку в компании ключа. Ответственного можно указать полем manager (uuid или email сотрудника). Без него ответственным становится менеджер по умолчанию компании (Настройки компании -> Сотрудники), а если он не назначен, владелец компании. У ключа сотрудника ответственный зависит ещё и от того, какие заявки видит сотрудник (ниже). Сотрудники получают уведомление в Telegram по своим настройкам.
Повтор создания передавайте с заголовком Idempotency-Key (раздел 6), например с номером заказа или UUID формы: так потерянный ответ можно смело повторить, и вторая заявка не появится.
Нужна область leads:create (или leads:write). Ключ сотрудника создаёт заявку с его правами и понимает дополнительные поля (ниже); ключ компании работает как описано здесь.
Поля
Обязательно хотя бы одно из полей name, phone, email.
| Поле | Описание |
|---|---|
name | Имя клиента |
phone | Телефон в любом формате, не меньше 7 цифр. Номер короче: ошибка 400 |
email | Email. Адрес с кириллицей в домене или до «@» (info@сайт.рф, иван@почта.рф) принимается и хранится как прислан. Адрес в неверном формате: 400 VALIDATION_ERROR, заявка не создаётся |
company | Компания клиента |
website | Сайт клиента. Некорректный адрес не сохраняется, но заявку не отменяет |
message | Текст обращения |
client_needs | Что нужно клиенту (например, название формы или квиза) |
source | Источник: название сайта, рекламной кампании, «Звонок» и т. п. |
service | Услуга, любой текст до 255 символов |
status | Этап заявки: номер (1, 2, 5, 6, 7) или название («Новая заявка», «В работе», «Успешно», «Провал», «Спам»; регистр букв не важен). По умолчанию «Новая заявка». Другое значение: ошибка 400 |
landing_page | Страница первого захода посетителя |
form_page | Страница, с которой отправлена форма |
referrer | Откуда пришёл посетитель |
ip_address | IP-адрес посетителя |
user_agent | Браузер посетителя |
utm_source, utm_medium, utm_campaign, utm_term, utm_content | Метки UTM |
ym_client_id | ClientID Яндекс Метрики (латиница, цифры, ., _, -, до 64 символов; иное значение, в том числе длиннее 64 символов, не сохраняется) |
tracking_token | Метка визита из скрытого поля lkt_token, которое скрипт сквозной аналитики добавляет в формы сайта: ровно 16 символов A-Z, a-z, 0-9, _, -. По ней CRM связывает заявку с визитом посетителя (Сквозная аналитика). Значение в другом формате молча пропускается, заявка создаётся как обычно. Так же поле принимает /api/lead.add |
lkt_hp | Скрытое поле-ловушка, которое общий код сайта добавляет в формы: передайте его значение как есть. Заполненная ловушка учитывается антиспамом (Антиспам), в заявке поле не сохраняется. Так же поле принимает /api/lead.add |
spam_check_wait | true (также 1, "1", "true", "on"): ответ ждёт решение антиспама CRM (не дольше 4 секунд) и несёт ключ spam_check (ниже, с 12.11.2026). В заявке не сохраняется |
manager | Ответственный: uuid сотрудника или его email (регистр не важен). Список uuid отдаёт GET /api/v1/managers, но только ключу сотрудника: ключ компании получает там 403 SCOPE_REQUIRED, поэтому с ключом компании указывайте email сотрудника. Без поля, null или пустая строка (пробелы не в счёт): менеджер по умолчанию, а если он не назначен, владелец компании. Сотрудник не из этой компании, неизвестный или отключённый: 400 VALIDATION_ERROR («manager: сотрудник не найден в компании»). У ключа сотрудника свои правила, ниже |
Заявка не теряется из-за длины: слишком длинные значения обрезаются по ширине поля (phone 50 символов, landing_page, form_page, referrer 500, ip_address 45, message, client_needs и user_agent около 65 000 байт, остальные строки 255 символов). Исключение ym_client_id: значение длиннее 64 символов не обрезается, а не сохраняется вовсе (поле выше). Байты не в UTF-8 заменяются знаком замены (U+FFFD).
ip_address, user_agent, referrer передавайте с данными посетителя сайта. CRM не подставляет вместо них адрес вашего сервера.
Заявка со статусом «Спам» сохраняется, но уведомление о ней не отправляется. Так удобно передавать заявки, которые отметил антиспам формы: если он ошибся, заявку видно в CRM под фильтром «Спам».
Повторное обращение
Если в компании уже есть активная заявка (этап «Новая заявка» или «В работе», не в архиве), созданная не раньше 30 суток назад, с тем же телефоном (российские номера и номера без «+» сравниваются по последним 10 цифрам, номер с кодом другой страны целиком; номер короче 10 цифр не сверяется, подробнее ниже) или тем же email, новая заявка не создаётся. В существующую добавляется событие «Повторное обращение» с полями source, name, company, phone, email, service, message, client_needs, form_page, ip_address, метками UTM (utm_source, utm_medium, utm_campaign, utm_term, utm_content), ym_client_id, landing_page и referrer, ответ 200 с is_duplicate: true и номером существующей заявки. Уведомление при этом не отправляется.
С 10.11.2026 активная заявка старше 30 суток (по дате создания) повторного обращения не принимает: создаётся новая заявка, как у нового клиента. То же правило у ключа сотрудника и у входящих звонков (раздел 17).
Остальные поля повторного обращения (website, user_agent, status) не сохраняются. Поля самой существующей заявки не меняются: метки и ym_client_id повторного обращения лежат в его событии в ленте заявки, а не в карточке.
Телефон, в котором меньше 10 цифр (например, городской номер без кода города), для поиска повторного обращения не используется: по короткому хвосту он совпал бы с номером другого клиента. Такая заявка сверяется только по email.
Номер с кодом другой страны (записан с «+», код страны не 7) сравнивается целиком, вместе с кодом страны: +84912345678 не совпадает с российским +74912345678, хотя последние 10 цифр у них одни. Российский номер в любой записи (8 912 ..., +7 912 ..., 912 ...) и номер без «+» сравниваются по последним 10 цифрам. Номер с +8 и следующей цифрой 3 или 9 (+8 916 ...) CRM считает российским, набранным с опечаткой.
Ответы
Новая заявка, 201 Created:
{ "success": true, "data": { "lead_number": 128, "is_duplicate": false, "message": "Заявка создана" } }
Повторное обращение, 200 OK:
{ "success": true, "data": { "lead_number": 97, "is_duplicate": true, "message": "Повторное обращение добавлено к существующей заявке" } }
Решение антиспама в ответе
С 12.11.2026. Форма сайта, которая сама отправляет письмо владельцу сайта (Contact Form 7, веб-формы 1С-Битрикс), может передать заявку в CRM до письма и не отправлять письмо со спамом. Для этого в теле поле spam_check_wait: true. CRM записывает заявку как обычно, ждёт решение антиспама (Антиспам; обычно 1-2 секунды, не дольше 4) и добавляет в data ответа ключ spam_check:
| Поле | Описание |
|---|---|
state | done проверена, pending решения ещё нет (4 секунды прошли), unchecked сервис проверки не ответил, skipped не проверялась (причина видна в карточке) |
verdict | ok, suspect, spam или null |
live | true: боевой режим антиспама (спам уходит в «Спам» сам), false: только считать |
action | reject: боевой режим и спам, заявка уже на этапе «Спам», письмо формы не отправляйте; accept: решение есть, письмо отправляйте; null: решения нет за 4 секунды, отправляйте письмо (заявку CRM проверит позже) |
reasons | Коды причин, как в spam_check заявки |
spam_check: null: заявка не проверялась (антиспам выключен, ключ с отметкой «Не проверять заявки на спам», нет полей формы сайта у ключа сотрудника) или это повторное обращение. Письмо отправляйте. Правило для формы одно: не отправлять письмо только при action: "reject". Ошибка связи, таймаут (ставьте не меньше 9 секунд) или ответ 5xx: письмо отправляйте, а заявку повторите тем же телом и тем же Idempotency-Key. Без поля spam_check_wait ответ прежний (ключа spam_check нет), запрос не ждёт. Так работают плагин «Linkodium CRM для Contact Form 7» с версии 5.1.0 (справка) и модуль «Linkodium CRM» для 1С-Битрикс с версии 2.1.0 (справка).
{ "success": true, "data": { "lead_number": 129, "is_duplicate": false, "message": "Заявка создана",
"spam_check": { "state": "done", "verdict": "spam", "live": true, "action": "reject", "reasons": ["no_script", "links_in_text"] } } }
Ключ сотрудника
Ключ сотрудника создаёт заявку так же, как сотрудник кнопкой «Новая заявка», и понимает те же поля, что выше, плюс:
| Поле | Описание |
|---|---|
title | Название заявки. Пусто: из имени и компании клиента |
manager | uuid ответственного (GET /api/v1/managers) или его email (регистр не важен). Как оно действует, зависит от того, какие заявки видит сотрудник ключа, список ниже |
status | Как выше; любой этап, кроме «Новая заявка», требует права менять статус |
dedup | false: создать заявку, даже если есть активная с тем же телефоном или email |
Ответственный по полю «Какие заявки видит сотрудник» в «Правах доступа» сотрудника ключа (с 14.10.2026):
- «Только свои (где сотрудник ответственный)»: ответственным всегда становится сам сотрудник ключа. Поле
managerне учитывается и ошибки не даёт. - «Все заявки компании», поле
managerне передано,null, пустая строка или одни пробелы: менеджер по умолчанию компании, а если он не назначен, владелец компании. Право «Менять ответственного» для этого не нужно. - «Все заявки компании», в
managerсам сотрудник ключа: он. - «Все заявки компании», в
managerдругой сотрудник: нужно право «Менять ответственного», как в CRM. С правом заявку получает указанный сотрудник, без права ответ403 PERMISSION_DENIED(error.permission: "leads.reassign"), заявка не создаётся. - Сотрудник из
managerне из этой компании, неизвестный или отключённый:400 VALIDATION_ERROR(«manager: сотрудник не найден в компании»).
Администратор компании видит все заявки и имеет все права.
- Нужно право сотрудника «Создавать заявки», иначе
403 PERMISSION_DENIED. В ленте заявки событие «создана» (source: api, раздел 12) подписано сотрудником с пометкой «через API», в карточке «Заявка создана через API». - В ответе ещё
lead(заявка целиком, как в разделе 10) иvisible.leadприходит только ключу с областьюleads:read, и у новой заявки, и у повторного обращения: ключ формы сайта с однойleads:createполучаетlead: null(номер,matched_byиvisibleте же). Заявка, созданная интеграцией по ключу сотрудника, ему всегда видна (visible: true).visible: false(иlead: null) бывает только у заявки от ИИ-ассистента (ниже) при видимости «только свои» и чужом ответственном. - Повторное обращение к заявке, которую сотрудник не видит: ответ
200сlead_number: null,is_duplicate: true,hidden: true. Обращение добавлено к той заявке, её номер не раскрывается. - Уведомление «Новая заявка» получает и сам сотрудник ключа, по своим галочкам в профиле: «Новые заявки, где я ответственный» или «Все новые заявки компании» (только если заявка ему видна). Заявки со статусом «Спам» уведомлений не дают. Исключение: заявка, созданная ИИ-ассистентом по просьбе сотрудника (раздел 26), как кнопка «Новая заявка» в кабинете, ему самому уведомления не присылает.
- Антиспам (с 10.11.2026): заявка с непустым
tracking_token,form_pageилиlanding_pageпохожа на форму сайта и проверяется на спам так же, как заявка по ключу компании (Антиспам): при боевом режиме уведомление приходит после проверки, явный спам уходит в «Спам». Без этих полей и у заявки от ИИ-ассистента проверки нет. Отметка ключа «Не проверять заявки на спам» (skip_antispam) снимает проверку и с ключа сотрудника. - Для формы сайта выпускайте ключ сотруднику с видимостью «Все заявки компании» (тогда заявки получает менеджер по умолчанию или тот, кого форма укажет в
manager, с правом «Менять ответственного») или тому, кто должен вести эти заявки сам (видимость «Только свои»: каждая заявка с ключа его). - Заявку, созданную ИИ-ассистентом по просьбе сотрудника (инструмент
create_lead, раздел 26), ассистент создаёт как сотрудник кнопкой «Новая заявка» в CRM: без права «Менять ответственного» ответственный сам сотрудник, выбор другого сотрудника даёт403 PERMISSION_DENIED; тамmanagerтолькоuuid: пустая строка не принимается, ассистент получает ошибку проверки аргументовVALIDATION_ERROR(«manager: ожидается uuid») при любых правах.
curl -X POST https://crm.linkodium.com/api/v1/leads \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10452" \
-d '{"title": "Заказ 10452", "name": "Анна", "phone": "+79991234567",
"manager": "1b4e28ba-2fa1-41d2-883f-0016d3cca427", "status": 2}'
Пример: curl
curl -X POST https://crm.linkodium.com/api/v1/leads \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: form-7f3c9a52-1d4e-4b8a-9c61-0e2f5a7b3d10" \
-d '{
"name": "Анна",
"phone": "+7 999 123-45-67",
"email": "anna@example.com",
"message": "Нужна консультация",
"source": "Сайт example.com",
"form_page": "https://example.com/contacts/",
"utm_source": "yandex",
"utm_campaign": "brand"
}'
Пример: PHP
<?php
// Ключ храните в настройках сервера, не в коде страницы.
$apiKey = getenv('CRM_API_KEY');
$lead = [
'name' => $_POST['name'] ?? '',
'phone' => $_POST['phone'] ?? '',
'email' => $_POST['email'] ?? '',
'message' => $_POST['message'] ?? '',
'source' => 'Сайт example.com',
'form_page' => $_SERVER['HTTP_REFERER'] ?? '',
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
];
// Один ключ идемпотентности на одну отправку формы: повтор с ним же
// (после таймаута, из очереди) вторую заявку не создаст. Храните его
// вместе с заявкой, если повторяете позже.
$idem = bin2hex(random_bytes(16));
$ch = curl_init('https://crm.linkodium.com/api/v1/leads');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($lead, JSON_UNESCAPED_UNICODE),
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-API-Key: ' . $apiKey,
'Idempotency-Key: ' . $idem],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$resp = json_decode((string)$body, true);
// 201 - новая заявка, 200 - повторное обращение: оба случая означают «заявка у менеджера».
if (in_array($code, [200, 201], true) && !empty($resp['success'])) {
$number = $resp['data']['lead_number'];
} else {
// Заявку не потеряйте: сохраните её у себя вместе с $idem или отправьте
// письмом и повторите позже с тем же Idempotency-Key.
error_log('CRM: HTTP ' . $code . ' ' . $body);
}
9. Список заявок: GET /api/v1/leads
Ключ сотрудника, область leads:read. Те же фильтры, что в списке заявок CRM, и видимость сотрудника: при «только свои» в списке только его заявки.
| Параметр | Описание |
|---|---|
status | Этапы через запятую: номера или названия (1,2 или Новая заявка,В работе). Без параметра: все, кроме «Спама» |
archived | 1: архив вместо рабочего списка. В архиве фильтр status не действует: приходят архивные заявки любого этапа, включая «Спам» |
manager | uuid ответственного |
date_from, date_to | Дата создания ГГГГ-ММ-ДД в часовом поясе компании (как в фильтре CRM), обе границы включительно |
search | Поиск по названию, имени, компании, телефону, email, сайту, источнику, услуге, сообщению. Телефон находится в любом формате записи: если в строке поиска только цифры, пробелы, скобки, дефисы, точки и «+», а цифр не меньше 4, сравниваются цифры номера (у 11-значного номера, начинающегося с 7 или 8, последние 10 цифр), так что 89991234567, +7 999 123-45-67 и 9991234567 находят одну заявку. Ещё поиск идёт по полю client_needs («Что хочет клиент?») и по телефону и email заявок, объединённых с этой (раздел 11): главная заявка находится и по контактам поглощённой |
updated_since | Заявки, изменённые начиная с момента (ISO-8601), для синхронизации |
sort | number (по умолчанию), created_at, updated_at |
order | desc (по умолчанию), asc. С updated_since по умолчанию updated_at и asc |
limit, cursor | Размер страницы (до 100) и курсор следующей страницы (раздел 3) |
with_total | 1: в ответе ещё total, число заявок по тем же фильтрам |
Ответ: { "items": [заявка, ...], "next_cursor": "...", "has_more": true }. Заявка описана в разделе 10.
curl -G https://crm.linkodium.com/api/v1/leads \
-H "X-API-Key: $CRM_API_KEY" \
--data-urlencode "status=1,2" \
--data-urlencode "updated_since=2026-10-01T00:00:00Z" \
--data-urlencode "limit=100"
Синхронизация: запомните updated_at последней полученной заявки и в следующий раз передайте его в updated_since. Заявка, изменённая во время обхода страниц, придёт в следующем обходе. С updated_since список не отдаёт правки последних 30 секунд: время правки ставится в начале записи, а видна правка после её завершения, и без такого окна отметка могла уйти вперёд правки, которая ещё записывается. Такие заявки придут в следующем опросе (заявка, которую правят без пауз, придёт, когда правки стихнут на 30 секунд), поэтому updated_at последней полученной заявки безопасная отметка, если запись заявки завершается быстрее 30 секунд (обычный случай; правка, застрявшая в ожидании блокировки дольше, может прийти позже со старым updated_at). Первую полную выгрузку тоже делайте с updated_since, например 2000-01-01T00:00:00Z: тогда и её последняя заявка годится как отметка. Без updated_since список отдаёт и самые свежие правки.
updated_since работает внутри остальных фильтров. Заявка, которая после изменения под них больше не подходит, в выдачу не попадёт: с status=1,2 не придёт заявка, переведённая в «Успешно», без archived=1 не придёт заявка, ушедшая в архив, без «Спама» в status не придёт заявка, отмеченная спамом (без параметра status «Спам» тоже не приходит). Для полной копии обходите с одним и тем же updated_since два списка: рабочий со всеми этапами (status=1,2,5,6,7) и архив (archived=1). Заявка, поглощённая объединением, не приходит ни в один список, в том числе в архив: по её номеру GET /api/v1/leads/{номер} отвечает 409 LEAD_MERGED с номером главной в error.merged_into, а в ленте главной есть событие merged (раздел 12).
Сводка: GET /api/v1/leads/stats
Область leads:read. Те же числа, что на дашборде CRM, по заявкам, которые видит сотрудник: timezone, total, today, month, conversion (won, lost, rate: карточка «Конверсия закрытых», доля «Успешно» среди закрытых заявок без архива), by_status (status, count), recent (5 последних). С параметрами date_from и date_to (ГГГГ-ММ-ДД в поясе компании, обе вместе, не больше 366 дней) ещё period: созданные за период created, by_status и by_source (до 10 источников).
Канбан: GET /api/v1/leads/board
Область leads:read. Заявки колонками по этапам, как вид «Канбан» в CRM: те же фильтры и видимость, что у списка (archived, manager, date_from, date_to, search). status: колонки (номера или названия через запятую), без параметра все пять, включая «Спам». limit: заявок в каждой колонке, до 100, по умолчанию 50. cursor: next_cursor колонки, только вместе с status этой одной колонки.
Ответ: { "columns": [ { "status": {"id", "name", "color"}, "total", "items": [заявка, ...], "next_cursor", "has_more" } ] }. В колонке заявки по номеру, новые первыми; устаревшие этапы 3 и 4 лежат в колонке «В работе». Перенос в другую колонку: POST /api/v1/leads/{номер}/status (раздел 11).
Источники обращений: GET /api/v1/tracking/report
Область leads:read. Отчёт «Источники обращений» с дашборда (Сквозная аналитика) по заявкам, которые видит сотрудник. Параметры date_from и date_to обязательны: ГГГГ-ММ-ДД в поясе компании, обе границы включительно, период не длиннее 366 дней, иначе 400 VALIDATION_ERROR. Объединённые (поглощённые) заявки не считаются.
Ответ: { "report": { "date_from", "date_to", "rows", "totals", "by_channel", "by_method" } }. Строка rows: site ({uuid, host} или null), channel, method (null: у заявки нет визита), precision, leads (заявки без спама), spam, won («Успешно»), conversion (колонка «Конверсия обращений»: доля «Успешно» от leads в процентах с одним знаком, 0 при leads = 0), won_amount (сумма выигранных сделок или null). totals: те же числа по всем заявкам периода; by_channel и by_method: сводки по каналу и по способу.
channel:ad(реклама),email(рассылки),social(соцсети),search(поиск),utm(метки UTM),referral(переходы с сайтов),direct(прямые заходы),maps(карты),other(прочее),unknown(нет данных). Входящий звонок на номер с отметкой «Рекламный» получаетadи без клика на сайте; канал, заданный у номера в «Трекинге», заменяет у входящего звонка толькоunknownиdirect(правила каналов).method:form,call,telegram,max,widget,api.precision:exact(точно),probable(вероятно: за 10 минут до звонка один клик по номеру или несколько кликов одного посетителя; звонок на рекламный номер без клика: за 20 минут до звонка на сайте был ровно один посетитель из рекламы),none(нет данных).
curl -G https://crm.linkodium.com/api/v1/tracking/report \
-H "X-API-Key: $CRM_API_KEY" \
--data-urlencode "date_from=2026-11-01" \
--data-urlencode "date_to=2026-11-30"
10. Заявка: GET и PATCH /api/v1/leads/{номер}
GET /api/v1/leads/128 (область leads:read): { "lead": {...} } и заголовок ETag с версией заявки.
{
"number": 128,
"title": "Анна, ООО Ромашка",
"display_title": "Анна, ООО Ромашка",
"status": { "id": 2, "name": "В работе" },
"loss_reason": null,
"amount": 50000.0,
"manager": { "uuid": "1b4e28ba-2fa1-41d2-883f-0016d3cca427", "name": "Анна" },
"name": "Анна", "company": "ООО Ромашка", "phone": "+79991234567", "email": "anna@example.com",
"website": null, "service": "Сайт", "source": "Сайт example.com", "client_needs": null, "message": "Нужна консультация",
"utm": { "source": "yandex", "medium": null, "campaign": "brand", "term": null, "content": null },
"landing_page": null, "form_page": "https://example.com/contacts/", "referrer": null, "ym_client_id": null,
"is_archived": false,
"created_at": "2026-10-03T09:00:00Z",
"updated_at": "2026-10-03T09:15:42.123456Z",
"version": "2026-10-03T09:15:42.123456Z",
"url": "https://crm.linkodium.com/c/b15092d3-.../leads/128/"
}
Заявки нет, она в другой компании или сотрудник её не видит: 404 NOT_FOUND. Заявка объединена с другой: 409 LEAD_MERGED с номером главной в error.merged_into.
IP-адрес и браузер, с которых пришла заявка (ip_address, user_agent), есть в заявке только у ключа с областью visitors:read; у прочих ключей этих полей нет вовсе, а из текста повторных обращений в ленте убрана строка с IP (раздел 12).
amount: «Сумма сделки» числом JSON (рубли с копейками) или null, если сумма не указана. Ключ появился 07.11.2026, после обновления базы данных; до обновления его в заявке нет.
pinned_comment (с 08.11.2026, только в GET одной заявки, не в списке): закреплённый комментарий карточки или null: uuid, text, has_files, author (uuid, name), created_at, pinned_at, pinned_by. Ключ есть после обновления базы данных.
spam_check (с 10.11.2026, только в GET одной заявки, не в списке): проверка заявки на спам или null, если заявка не проверялась: state (pending: проверяется, done, unchecked: сервис проверки не ответил, skipped: не проверялась), verdict (ok, suspect, spam или null), score (0..100), reasons (коды причин, например no_interaction, links_in_text, honeypot), checked (full или partial: на странице формы не было общего кода), live (true: боевой режим, false: только считать), human_label (отметка сотрудника spam или ham). IP-адреса и браузера в объекте нет. Ключ есть после обновления базы данных (Антиспам).
История клиента GET /api/v1/leads/{номер}/history (область leads:read), как блок «История клиента» карточки: другие заявки того же клиента в компании (тот же телефон или email по правилу повторных обращений, прошлая заявка клиента чата; архивные и закрытые тоже, поглощённые объединением нет), свежие первыми. Параметр limit от 1 до 50 (умолчание 20). Ответ: count, has_more, items (number, status, amount, created_at, url). Заявки, которые сотрудник ключа не видит, не попадают ни в items, ни в count.
PATCH /api/v1/leads/128 (область leads:write) меняет поля заявки и ответственного:
- поля
title,source,service,client_needs,name,company,phone,email,website,message(nullили пустая строка очищает поле) иmanager(uuidили email сотрудника, регистр email не важен;null, пустая строка или одни пробелы снимают ответственного); поле без ключа не меняется; amount: сумма сделки числом (50000,12500.5), строкой ("12 500,50") илиnull(очистить), от 0 до 9999999999.99, два знака после запятой. Другое значение:400 VALIDATION_ERROR(«amount: Сумма сделки: число от 0 до 9 999 999 999,99»). Правка суммы требует того же права «Править поля заявки». Сумма уходит ценой конверсии «Сделка» в Метрику и ДАРА (Сквозная аналитика);- версия обязательна: заголовок
If-Matchсо значениемETagизGETили полеversion. Без версии:428 PRECONDITION_REQUIRED. Если заявку уже изменили (менеджер в CRM, другая интеграция):409 VERSION_CONFLICT, ничего не сохраняется, текущая версия вerror.current_version; - права сотрудника: поля требуют права «Править поля заявки», ответственный права «Менять ответственного», иначе
403 PERMISSION_DENIED; - ответ:
changed(изменённые поля),manager_changed,lead(заявка после правки, новыйETag). Если сотрудник видит только свои заявки и передал заявку другому,leadравенnull,visible: false.
curl -X PATCH https://crm.linkodium.com/api/v1/leads/128 \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-H 'If-Match: "2026-10-03T09:15:42.123456Z"' \
-d '{"company": "ООО Ромашка", "manager": "1b4e28ba-2fa1-41d2-883f-0016d3cca427"}'
Визит заявки: GET /api/v1/leads/{номер}/visit
Область leads:read. Данные сквозной аналитики заявки, как раздел «Маркетинг» в карточке (Сквозная аналитика): { "visit": {...} } или { "visit": null }, если визита нет (заявка создана вручную, импортом, из почты или до подключения трекинга). Заявки нет или сотрудник её не видит: 404 NOT_FOUND; заявка объединена с другой: 409 LEAD_MERGED с номером главной в error.merged_into.
{
"visit": {
"method": "telegram",
"precision": "exact",
"channel": "ad",
"site": { "uuid": "5d0c3a2e-8f41-4b6a-9c3d-2e1f0a9b8c7d", "host": "example.com", "title": "Example" },
"line": null,
"from_ad": true,
"utm": { "source": "yandex", "medium": "cpc", "campaign": "brand", "term": null, "content": null },
"referrer": "https://yandex.ru/",
"landing_page": "https://example.com/?utm_source=yandex&utm_medium=cpc&utm_campaign=brand",
"page_url": "https://example.com/contacts/",
"first_touch": { "source": null, "medium": null, "campaign": null, "referrer": "https://www.google.com/",
"landing_page": "https://example.com/", "seen_at": "2026-11-01T08:00:00Z" },
"ym_client_id": "1696427893123456789",
"dara_visitor": "0b7e6f8a-3c2d-4e5f-9a1b-2c3d4e5f6a7b",
"clicked_at": "2026-11-07T09:02:00Z",
"note": null,
"created_at": "2026-11-07T09:02:05Z",
"conversions": [ { "goal": "qualified", "target": "metrika", "status": "sent", "skip_reason": null, "sent_at": "2026-11-07T09:40:31Z" } ]
}
}
method,precision,channel: значения как в отчёте.line({uuid, number, display}) у звонков: номер, на который звонил клиент.note: пояснение точности, например «Вероятно, по клику на номер в 14:02».conversions: что отправлено в Метрику и ДАРА (goal:lead,call_target,qualified,deal;target:metrika,dara;status:pending,processing,sent,skipped,dead;skip_reasonу пропущенных).ip_addressиuser_agentвизита есть только у ключа с областьюvisitors:read, как у заявки.- Время ISO-8601 в UTC с
Z.
11. Статус, архив, объединение
Область leads:write, права сотрудника те же, что у кнопок карточки заявки.
| Адрес | Тело | Ответ |
|---|---|---|
POST /api/v1/leads/{номер}/status | status (номер или название этапа), loss_reason (обязательна для «Провала»); необязательные amount (сумма сделки, как в PATCH) и comment (комментарий в ленту), одной транзакцией со статусом | changed, lead; с amount ещё amount_changed, с comment ещё comment |
POST /api/v1/leads/{номер}/archive | нет | changed (false, если уже в архиве), lead |
POST /api/v1/leads/{номер}/unarchive | нет | changed, lead |
POST /api/v1/leads/{номер}/merge | absorbed (номер заявки, которая будет поглощена), dry_run (true: только предпросмотр) | already, main_number, absorbed_number, moved, lead; с dry_run: true другая форма, см. ниже |
POST /api/v1/leads/{номер}/spam | нет | changed, lead (кнопка «Спам», право менять статус) |
GET /api/v1/leads/{номер}/merge-candidates | параметр q (поиск, до 100 символов) | suggested (похожие заявки с причинами reasons), found (найденные по q); право объединять заявки |
Объединение необратимо: события, звонки, чаты и переписка поглощённой заявки переходят в заявку из адреса, пустые поля дополняются, поглощённая уходит в архив. Повтор по уже объединённой паре отвечает успехом с already: true.
Предпросмотр (dry_run: true) ничего не меняет и отвечает { "dry_run": true, "preview": {...} }, тем же, что показывает окно объединения в CRM: preview.already (пара уже объединена), preview.main и preview.absorbed (кратко о заявках: number, title, name, phone, email, source, status (name, color, bg), archived, manager (имя), created_at, url), preview.moved (счётчики того, что перейдёт: comments, calls, other_events, files, recordings, chats, chat_messages, mail_threads) и preview.moved_lines (то же строками), preview.fill (пустые поля главной, которые дополнятся: label, value; строки «IP» и «Браузер» только у ключа с областью visitors:read, как поля ip_address и user_agent заявки), preview.diff (поля, где значения различаются и останется значение главной: label, main, merged), preview.manager (имя ответственного после объединения). Полей main_number и absorbed_number у предпросмотра нет: номера в preview.main.number и preview.absorbed.number.
curl -X POST https://crm.linkodium.com/api/v1/leads/128/status \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": 6, "loss_reason": "Дорого"}'
12. Лента событий: GET /api/v1/leads/{номер}/events
Область leads:read. События заявки, свежие первыми (order=asc: старые первыми), постранично.
| Параметр | Описание |
|---|---|
types | Типы через запятую: comment, status_change, field_change, created, assigned, duplicate_request, call, chat, merged, action |
since | События, созданные начиная с момента (ISO-8601): отбор по времени создания created_at |
updated_since | События, созданные или изменённые начиная с момента (ISO-8601), для синхронизации (ниже). Сочетается с since, types и cursor; порядок и курсор прежние, по created_at |
order, limit, cursor | Порядок, страница, курсор |
Событие: uuid, type, created_at, updated_at (ISO-8601 в UTC с Z; пока событие не менялось, равно created_at), author (uuid и name сотрудника или null у событий системы; у автора с фото ещё avatar_url), via (api, import или mcp у действий не из интерфейса, antispam у перевода в «Спам» антиспамом), data по типу:
| type | data |
|---|---|
comment | text, attachments (файлы: uuid, name, size, type, url) |
status_change | from, to (id, name) |
field_change | field, old, new |
created | source (web: заявка создана в CRM вручную или ИИ-ассистентом (у ассистента via: mcp); api: заявка пришла через API: ключом сотрудника (via: api, автор события сотрудник ключа) или прежним ключом компании, в том числе старым адресом /api/lead.add, который пишет и присланный ему creation_source (web или call; у таких событий via: null и author: null); call, mail, import, telegram, max, widget, avito), previous_lead_number (прошлая заявка клиента или null; null и тогда, когда сотрудник ключа эту заявку не видит) |
assigned | from, to (uuid, name или null) |
duplicate_request | matched_by, text (сводка обращения; у ключа без visitors:read строка «**IP:** <адрес>» из неё убрана, остальной текст тот же) |
call | call_id, direction (in, out), phone, line, status, status_label, duration, started_at (ISO-8601 в UTC с Z; звонки, записанные до 2026-09-09 по часам телефонии без зоны, приводятся к UTC тем же правилом, что и при приёме; значение, которое не удалось разобрать: null), recordings (файлы записи с расшифровкой transcript: state (pending в очереди, processing идёт, done готова, dead не удалась), text, language и duration у готовой, error_code (bad_audio, too_long, file_missing, attempts) и error у неудавшейся, updated_at; запись, которую не ставили на расшифровку: transcript: null) |
chat | action (bound, moved, deleted), note; у moved ещё moved_to_lead_number (заявка, куда перенесён чат; null, если сотрудник ключа её не видит), у deleted ещё messages_deleted |
merged | absorbed_number, details |
action | action (код действия ИИ-ассистента: chat_reply, chat_sticker, chat_dismiss, chat_bind, mail_send, mail_bind, mail_unbind, transcribe), text; пишется только для действий через MCP (раздел 26) |
Синхронизация ленты. Событие звонка дополняется на месте: статус, длительность, записи и расшифровка приходят позже, а created_at не меняется. События, перенесённые объединением из поглощённой заявки (раздел 11), сохраняют свой created_at, но считаются изменёнными. Поэтому since такие изменения пропускает. Запоминайте наибольший updated_at из полученных событий, в следующем опросе передавайте updated_since, равный ему минус 60 секунд, и сверяйте события по uuid: пришедшее снова событие заменяет прежнее. Удалённый комментарий из ленты пропадает и с updated_since больше не приходит.
Первая страница (без cursor) кратко перечисляет чаты (chats: uuid, name, channel, unread) и переписку почты (mail_threads: uuid, subject, message_count, last_message_at, last_direction, unread, account_email) заявки, доступные сотруднику так же, как в карточке. Там же прошлые заявки клиента из почты: past_leads (number, url, до 5) и past_leads_total (до 100), то есть другие заявки с тем же email или телефоном (от 10 цифр), которые видит сотрудник и к которым привязана переписка из его ящиков (кроме переписки, привязанной и к этой заявке). Прошлая заявка без такой переписки (с формы сайта, из звонка) сюда не попадает: ищите её в списке по телефону или email (search, раздел 9). mail_threads_total: сколько всего переписки заявки в ящиках сотрудника (в mail_threads первые 20). У ключа, выпущенного администратором за сотрудника, mail_threads и past_leads пустые, mail_threads_total и past_leads_total равны 0. У ключа с mail:read ещё unbound_mail_count: письма с адресом клиента в ящиках сотрудника, не привязанные к заявке. Фото автора: GET /api/v1/users/{uuid}/avatar (область leads:read).
curl -G https://crm.linkodium.com/api/v1/leads/128/events \
-H "X-API-Key: $CRM_API_KEY" \
--data-urlencode "types=call,comment"
13. Комментарии и файлы
POST /api/v1/leads/{номер}/comments (область leads:write, право «Писать комментарии и прикреплять файлы»): поле text и файлы. Файлы передаются формой multipart/form-data в полях files[] (или file): до 10 файлов, каждый до 100 МБ, но весь запрос вместе с текстом не больше 110 МБ (больше: 413 PAYLOAD_TOO_LARGE; крупные файлы отправляйте отдельными комментариями). Место файлы занимают в хранилище компании (403 QUOTA_EXCEEDED, если его не хватает). Ответ 201 с comment (событие, как в разделе 12). Ответственный за заявку получает уведомление, как от комментария в CRM.
curl -X POST https://crm.linkodium.com/api/v1/leads/128/comments \
-H "X-API-Key: $CRM_API_KEY" \
-H "Idempotency-Key: comment-77" \
-F "text=Счёт и договор во вложении" \
-F "files[]=@/tmp/invoice.pdf" \
-F "files[]=@/tmp/contract.pdf"
Только текст можно отправить и JSON: {"text": "Перезвонить в пятницу"}.
Закреп. POST /api/v1/leads/{номер}/comments/{uuid}/pin закрепляет комментарий над лентой заявки, DELETE того же адреса открепляет (область leads:write, право «Писать комментарии и прикреплять файлы»). У заявки один закреп: новый заменяет прежний. Ответ: changed, pinned, pinned_comment. Закреп и снятие видны в ленте событием field_change с полем pinned_comment; удалённый комментарий снимается с закрепа сам. До обновления базы данных: 503 SERVICE_UNAVAILABLE.
DELETE /api/v1/comments/{uuid} (область leads:write): удаляет свой комментарий (администратор компании: любой) вместе с файлами. Ответ { "deleted": true, "already": false }. Повтор тем же ключом API после удаления (в течение суток) отвечает { "deleted": true, "already": true }, а не 404. Чужой комментарий: 403, не комментарий: 400.
GET /api/v1/attachments/{uuid} (область leads:read): файл из ленты (комментария или записи звонка) заявки, которую видит сотрудник. Отдаётся файлом (Content-Disposition: attachment), заголовок Range поддерживается.
curl -o call.mp3 https://crm.linkodium.com/api/v1/attachments/5d0c7a4e-8f3b-4f5e-9a51-2b7f0c1d9e20 \
-H "X-API-Key: $CRM_API_KEY"
14. Выгрузка: GET /api/v1/leads/export.xlsx
Область leads:export и право сотрудника «Выгружать заявки в XLSX». Тот же файл, что кнопка «Скачать XLSX» в CRM: те же фильтры, что у списка (раздел 9, кроме updated_since, limit, cursor; с archived=1 фильтр status так же не действует), видимость сотрудника, даты в часовом поясе компании. Сортировка: sort (number, created_at, title, name, phone, status) и order.
- Больше 100 000 строк:
422 TOO_MANY_ROWSс числом строк вerror.rows. Сузьте фильтр (даты, этапы) и выгрузите частями. - Не больше 30 выгрузок за 10 минут на сотрудника, сверх этого
429.
curl -G -o leads.xlsx https://crm.linkodium.com/api/v1/leads/export.xlsx \
-H "X-API-Key: $CRM_API_KEY" \
--data-urlencode "date_from=2026-09-01" \
--data-urlencode "date_to=2026-09-30"
15. Почта: /api/v1/mail
Ключ сотрудника с областью mail:read (чтение) или mail:send (отправка, привязка к заявке); обе области «только себе» (раздел 1): ключ, выпущенный администратором за сотрудника, на этих адресах отвечает 403 SCOPE_REQUIRED. Видны только ящики, где сотрудник участник: чужой ящик, переписка, письмо и вложение отвечают 404 NOT_FOUND, как несуществующие (администратор компании чужие ящики через эти адреса не видит). Заявки в переписке показываются по видимости сотрудника. У сотрудника нет ни одного ящика: списки ящиков и переписки приходят пустыми (200, пустой items), адреса конкретного ящика, переписки и письма отвечают 404. 404 NOT_FOUND на всех адресах раздела бывает, только если подключение новых ящиков компании закрыто и у сотрудника нет ни одного ящика или почта в CRM ещё не включена.
| Адрес | Область | Что делает |
|---|---|---|
GET /api/v1/mail/accounts | mail:read | Ящики сотрудника: uuid, email, display_name, provider, provider_title, status, status_text, can_send, last_ok_at, unread, members_count, quota (отправлено за час и получателей за сутки с этого адреса в этой компании), disable_warning, error_text, smtp_error_text, never_worked, sent_folder, sync (since, initial_done, backfill_running, can_backfill), me (can_manage, notify, signature; те же can_manage и signature и в корне объекта); servers (login, imap, smtp, smtp_separate_password) только управляющему ящиком и только с областью mail:manage |
GET /api/v1/mail/threads | mail:read | Список переписки (последнее письмо, новые первыми) |
GET /api/v1/mail/threads/{uuid} | mail:read | Переписка: заявки, письма страницей |
GET /api/v1/mail/messages/{uuid}/body | mail:read | Тело письма: текст и очищенный HTML |
GET /api/v1/mail/parts/{uuid} | mail:read | Вложение файлом; не скачано с сервера почты: заказ скачивания |
POST /api/v1/mail/threads/{uuid}/read | mail:read | Отметить прочитанным (или одно письмо непрочитанным) |
POST /api/v1/mail/send | mail:send | Новое письмо или ответ, с файлами |
POST /api/v1/mail/threads/{uuid}/lead | mail:send | Привязать переписку к заявке |
Данные раздела, журнал изменений, ящик целиком, подключение и настройки ящиков, участники, удаление писем, отвязка и заявка из письма: раздел 24.
Список переписки
GET /api/v1/mail/threads: фильтры как в разделе «Почта» CRM.
| Параметр | Описание |
|---|---|
account | uuid ящика (без него все ящики сотрудника); чужой ящик: 404 |
view | inbox (по умолчанию, во «Входящих»), sent (есть отправленные), all. С lead или contact всегда all: переданный view не учитывается |
unread | true: только с непрочитанными |
q | Поиск по теме, адресу и имени собеседника, началу письма (до 100 символов) |
lead | Номер заявки: переписка, когда-либо привязанная к ней (заявка не видна сотруднику: 404) |
contact | Адрес собеседника: его переписка без заявки |
limit, cursor | Страница и курсор (раздел 3) |
Элемент: uuid, account (uuid, email), subject, snippet, contact (name, email), last_message_at, last_direction (in, out), message_count, unread_count, has_attachments, send_failed, in_inbox, in_sent, lead (number, status, is_open, manager, url или null).
curl -G https://crm.linkodium.com/api/v1/mail/threads \
-H "X-API-Key: $CRM_API_KEY" \
--data-urlencode "unread=true" \
--data-urlencode "limit=50"
Переписка, тело письма, вложения
GET /api/v1/mail/threads/{uuid}: thread, leads (все привязки, текущая с current: true), lead_prefill (поля заявки по умолчанию для «Создать заявку»: title, name, email, phone, message), messages (последние limit писем по возрастанию времени), next_cursor и has_more для более ранних писем. Переписка, объединённая с другой, отдаётся объединённой (redirected_from = запрошенный uuid). Письмо: uuid, direction, from, to, cc, bcc (только у исходящих), reply_to, subject, date, seen, snippet, text (до 20 000 символов, text_truncated), auto (автоматическое письмо: рассылка или автоответ), bounce (отчёт о недоставке), spoof_warning (имя отправителя похоже на другой адрес), auth_fail (почтовый сервис не подтвердил отправителя: DMARC или SPF не прошли), body (state, has_html, url), attachments (uuid, name, mime, size, state, url), send (state: queued, sending, sent, failed, и error у писем из CRM), author (сотрудник, отправивший письмо из CRM).
GET /api/v1/mail/messages/{uuid}/body: text (текст письма до 256 КБ: CRM хранит не больше, более длинный текст обрезан), html (HTML письма, очищенный от скриптов, тот же, что CRM показывает в окне письма; картинки с чужих серверов в нём не загружены, их число в remote_images), html_too_big, state. HTML больше 2 МБ CRM не сохраняет: такое письмо приходит с state: too_big, html: null, html_too_big: true и только текстом. html_too_big: true бывает и у сохранённого HTML больше 5 МБ. Не хватило места в хранилище компании (state: no_space): html: null, html_too_big: false, текст первые 16 КБ. Встроенные картинки письма (логотип, подпись) в html ссылаются на /api/v1/mail/parts/{uuid} (путь без домена): загружайте их тем же ключом, как вложение. Тело ещё не скачано с сервера почты (state: queued): CRM скачивает его первым, повторите запрос через Retry-After секунд.
GET /api/v1/mail/parts/{uuid}: скачанное вложение отдаётся файлом (Content-Disposition: attachment, Range поддерживается). Вложение ещё на сервере почты: CRM ставит его в очередь скачивания и отвечает 202 с part (state: queued) и Retry-After; повторите запрос. Скачать нельзя: 409 ATTACHMENT_UNAVAILABLE с error.state. error.state: error значит, что CRM исчерпала попытки скачать вложение (таймауты, сбои сервера почты): запрос с ?retry=1 ставит скачивание заново, как кнопка «Повторить» у вложения в CRM, и отвечает 202; без retry такое вложение остаётся 409. Значение retry: 1, true, 0 или false, иное 400 VALIDATION_ERROR.
curl -o invoice.pdf https://crm.linkodium.com/api/v1/mail/parts/0f2c1c64-3a9e-4b1d-8c55-6e1f0a2b7d31 \
-H "X-API-Key: $CRM_API_KEY"
Отправка: POST /api/v1/mail/send
Область mail:send. Письмо ставится в очередь и уходит с сервера почты ящика в течение минуты, как письмо из CRM; копия ложится в «Отправленные» ящика.
| Поле | Описание |
|---|---|
account | uuid ящика отправителя (обязателен у нового письма; у ответа по умолчанию ящик переписки) |
to, cc, bcc | Получатели: строка ("Анна <anna@example.com>, boss@example.com"), список строк или объектов {"name", "email"}. Вместе до 10 адресов. В строке имя, в котором есть запятая или адрес почты, пишите в кавычках: "\"Петров, Иван\" <ivan@example.com>"; имя без кавычек с адресом внутри даёт 400 VALIDATION_ERROR, причина отказа в error.message |
subject, text | Тема (до 500 символов) и текст письма (до 100 000 символов) |
mode | new (по умолчанию), reply, reply_all |
reply_to | uuid письма, на которое ответ (или thread: ответ на последнее письмо переписки) |
lead | Номер заявки (только новое письмо): переписка привязывается к ней |
files[] или file | Файлы (multipart/form-data), до 10, вместе до 25 МБ, место в хранилище компании (403 QUOTA_EXCEEDED, если его не хватает) |
client_request_id | Ключ идемпотентности (или заголовок Idempotency-Key, раздел 6) |
У ответа без to и cc получатели как у кнопки «Ответить» («Ответить всем» при reply_all), без subject тема Re: и тема письма. Ответ 201: message (письмо с send.state: queued), thread, duplicate: false, warning (предупреждение о суточном пределе почтового сервиса или null). Повтор с тем же ключом идемпотентности в течение суток: сохранённый первый ответ (201, duplicate: false) с заголовком Idempotent-Replayed: true (раздел 6); позже суток: 200, duplicate: true, то же письмо. В обоих случаях второе письмо не уходит. Ящик не может отправлять: 409 MAIL_ACCOUNT_INACTIVE; квота ящика (100 писем в час с адреса, считаются письма этой компании): 409 SEND_QUOTA; почта приостановлена: 409 SERVICE_PAUSED; заявка не найдена или не видна сотруднику: 404.
curl -X POST https://crm.linkodium.com/api/v1/mail/send \
-H "X-API-Key: $CRM_API_KEY" \
-H "Idempotency-Key: offer-2026-10-03-128" \
-F "account=7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-F "to[]=Анна <anna@example.com>" \
-F "subject=Коммерческое предложение" \
-F "text=Добрый день! Предложение во вложении." \
-F "lead=128" \
-F "files[]=@/tmp/offer.pdf"
Ответ в переписку JSON-ом:
curl -X POST https://crm.linkodium.com/api/v1/mail/send \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"thread": "3b241101-e2bb-4255-8caf-4136c566a962", "text": "Спасибо, счёт оплатим сегодня.", "client_request_id": "reply-5521"}'
Прочитано и привязка к заявке
POST /api/v1/mail/threads/{uuid}/read: без тела все входящие письма переписки становятся прочитанными; {"message": "<uuid письма>", "seen": false} отмечает одно письмо непрочитанным. Отметка общая для всех участников ящика и почтовой программы (флаг на сервере почты). Ответ: thread, touched (сколько писем изменено).
POST /api/v1/mail/threads/{uuid}/lead с {"lead": 128}: переписка относится к заявке, новые письма тоже. Заявка не найдена или не видна сотруднику: 404; номер объединённой заявки ведёт на главную. Ответ: thread, lead.
16. Чаты: /api/v1/chats
Ключ сотрудника с областью chats:read (чтение, «прочитано», файлы) или chats:write (ответы, привязка к заявке). Видны чаты по режиму доступа сотрудника («Права доступа», блок «Чаты»): «все чаты», «все чаты, отвечает ответственный», «только свои и без ответственного» (администраторы видят все). Невидимый чат отвечает 404. У компании нет каналов: 404 на всех адресах.
| Адрес | Область | Что делает |
|---|---|---|
GET /api/v1/chats | chats:read | Список чатов (последняя активность, новые первыми) |
GET /api/v1/chats/{uuid}/messages | chats:read | Сообщения чата |
GET /api/v1/chats/files/{uuid} | chats:read | Файл сообщения |
POST /api/v1/chats/{uuid}/read | chats:read | Отметить прочитанным |
POST /api/v1/chats/{uuid}/messages | chats:write | Ответить клиенту, с файлами |
POST /api/v1/chats/{uuid}/lead | chats:write | Привязать чат к заявке |
Шапка переписки, журнал изменений, «Отметить все прочитанными», «Ответ не нужен», стикеры, голосовые, правка и удаление сообщений, телефон в заявку, карточка посетителя и блокировка: раздел 23.
GET /api/v1/chats: filter (all по умолчанию, mine: ответственный за заявку чата сотрудник, awaiting: ждут ответа), q (поиск как в CRM: #номер заявки, телефон, имя или @username; по #номеру и по телефону заявки находятся только чаты, чья заявка видна сотруднику (при видимости «только свои» только его заявки), свой телефон клиента в чате ищется всегда), lead (номер заявки: переписки её клиента), limit, cursor. Чат: uuid, channel (uuid, type: telegram, max, widget, с 11.11.2026 avito, title, status), name, username, phone (телефон клиента; если своего телефона у клиента в чате нет, телефон его заявки, но только если заявка видна сотруднику, иначе null), last_message, last_activity_at, unread, awaiting_reply, blocked, lead (number, status, is_open, manager или null), can_reply, reply_block, reply_block_text, initials, avatar_url (/api/v1/chats/{uuid}/avatar или null), item (объявление чата Авито: title, price строкой или null, url объявления на avito.ru или null; у прочих чатов и пока Авито не прислал объявление null); у канала ещё disabled.
curl -G https://crm.linkodium.com/api/v1/chats \
-H "X-API-Key: $CRM_API_KEY" \
--data-urlencode "filter=awaiting"
GET /api/v1/chats/{uuid}/messages: без параметров последние limit (до 100, по умолчанию 50) по возрастанию; before=<uuid сообщения>: более ранние, after=<uuid>: более поздние. Ответ: chat, items, has_more_before, has_more_after. Сообщение: uuid, direction (in, out, system), kind (text, photo, document, voice и другие), text, created_at, lead_number, author, auto (автоответ: greeting, offhours или contact_request; иначе null), external (avito_app: исходящее сообщение продавец написал в приложении Авито, а не в CRM, author тогда null; иначе null), status (received, queued, sent, failed и другие), error, service (у служебного сообщения code и text, иначе null), reply_to (uuid, direction, kind, text, missing), forward (пересланное: from, at), album (общий код у фото одного альбома), files (uuid, kind, name, mime, size, width, height, duration, status, url = /api/v1/chats/files/{uuid}), sticker (uuid, emoji, format), location (lat, lon, live, live_until), venue (lat, lon, title, address), contact_card (name, phone, is_self), edited, deleted, history (прежние тексты изменённого сообщения: text, at, by), phones (телефоны в тексте), unsupported, а также что сотрудник может сделать с сообщением: can_edit, can_delete_at_client, can_retry, can_delete (удаление из CRM, только администратору).
curl -G https://crm.linkodium.com/api/v1/chats/9b2f5a70-1c3d-4e8f-a6b7-0c1d2e3f4a5b/messages \
-H "X-API-Key: $CRM_API_KEY" \
--data-urlencode "limit=100"
Ответ клиенту: POST /api/v1/chats/{uuid}/messages
Поля: text (до 10 000 символов), reply_to (uuid сообщения этого чата: цитата), файлы files[] или file (multipart/form-data, до 10, каждый до предела канала, весь запрос вместе не больше 110 МБ, иначе 413 PAYLOAD_TOO_LARGE; место в хранилище компании: 403 QUOTA_EXCEEDED, если его не хватает), client_request_id или Idempotency-Key. Сообщение уходит клиенту от бота компании без подписи сотрудника, как ответ из CRM; ответ 201 с message (status: queued, у виджета сайта сразу sent). Первый ответ клиенту без ответственного делает сотрудника ответственным за заявку (в ленте с пометкой «через API»). Права ответа как в CRM: в режиме «отвечает ответственный» чужому клиенту 403 PERMISSION_DENIED (error.permission: chats.mode); клиент заблокировал бота или канал отключён: 409 CHAT_UNAVAILABLE. В ответе ещё chat (uuid, lead: номер текущей заявки клиента или null). Стикеры и голосовые: раздел 23.
curl -X POST https://crm.linkodium.com/api/v1/chats/9b2f5a70-1c3d-4e8f-a6b7-0c1d2e3f4a5b/messages \
-H "X-API-Key: $CRM_API_KEY" \
-H "Idempotency-Key: answer-81234" \
-F "text=Добрый день! Договор во вложении." \
-F "files[]=@/tmp/contract.pdf"
Прочитано и привязка к заявке
POST /api/v1/chats/{uuid}/read: {"upto": "<uuid сообщения>"} или без тела (до последнего сообщения). Отметка у каждого сотрудника своя. Ответ: unread (непрочитанные этой переписки) и badge (непрочитанные переписки сотрудника, как значок раздела).
POST /api/v1/chats/{uuid}/lead с {"lead": 128}: чат переходит к заявке (видимой сотруднику; номер объединённой ведёт на главную), прошлые сообщения остаются при прежней заявке, в ленты обеих заявок пишутся события. Ответ: chat (null, если после смены заявки сотрудник чат больше не видит), changed, previous_lead (number, status, can_archive).
17. Телефония: POST /api/v1/calls
Универсальный приём звонков от любой АТС. Прослойку между АТС и CRM пишете вы (или берёте готовую), она переводит события АТС в вызовы этого метода. Пример для Novofon и Zadarma в разделе 19.
Модель
Звонок в CRM определяется парой «компания + call_id», где call_id это идентификатор звонка на стороне АТС. Каждое событие звонка (начало, ответ, конец, готовая запись) присылайте отдельным запросом с одним и тем же call_id:
- Первый запрос с новым
call_idзаводит звонок в ленте заявки. - Следующие запросы с тем же
call_idдописывают поля звонка: итог, длительность, время, запись. Непереданные и пустые поля сохраняют прежние значения. - Повтор одного и того же запроса ничего не портит. Если не получили ответ, смело отправляйте ещё раз.
Хранить у себя соответствие «звонок АТС -> заявка CRM» не нужно: заявку по номеру клиента находит сама CRM.
Как CRM находит или создаёт заявку
При первом событии звонка (в компании ещё нет звонка с этим call_id):
- Нужен
client_phone, номер клиента. Без него ответ404 CALL_NOT_FOUND: звонок не заведён и ничего не сохранено. Так штатно выглядит, например, событие готовой записи исходящего звонка, который не сохранялся (см. ниже), или событие, которое пришло раньше начала звонка. Некорректный номер (меньше 7 цифр) в первом событии:400. - CRM ищет активную заявку компании (этап «Новая заявка» или «В работе», не в архиве) с этим номером (российские номера и номера без «+» сравниваются по последним 10 цифрам, номер с кодом другой страны целиком, как в разделе 8; номер короче 10 цифр активную заявку не находит). Для входящего звонка с 10.11.2026 заявка должна быть создана не раньше 30 суток назад, старше не в счёт; исходящий звонок ищет активную заявку без этого ограничения. Нашла: звонок попадает в неё.
- Не нашла:
- входящий звонок создаёт новую заявку: телефон клиента, источник
source(по умолчанию «Звонок», правило сlineниже), услугаservice, ответственный менеджер по умолчанию, а если он не назначен, владелец компании (у ключа сотрудника, который видит только свои заявки, сам сотрудник, ниже). Сотрудники получают уведомление «Новая заявка» по своим настройкам; - исходящий звонок создаёт заявку, только если в компании включена настройка Настройки компании -> «Исходящий звонок на номер без заявки создаёт новую заявку». Если выключена (так по умолчанию), звонок не сохраняется, ответ
200сskipped: true.
- входящий звонок создаёт новую заявку: телефон клиента, источник
Отдельных уведомлений о звонках нет: звонок виден в ленте заявки.
Источник и услуга новой заявки из звонка (с 07.11.2026): поле source (service) запроса, если оно передано; иначе строка номера из поля line в настройках трекинга («Настройки компании» -> «Трекинг», раздел «Телефонные номера», Сквозная аналитика); иначе источник «Звонок» и услуга не задана. Номер line сравнивается по цифрам (8 в начале как 7, выключенные строки не учитываются). Интеграции, которые сами передают source, работают как раньше; ответ звонка не меняется. Если звонок создал новую заявку, CRM ещё ищет клик по этому номеру на сайте за 10 минут до звонка и записывает визит заявки (раздел 10); звонок на рекламный номер без клика связывается с единственным посетителем из рекламы за 20 минут до звонка (Сквозная аналитика).
Ключ сотрудника
Прежний ключ компании работает как описано в этом разделе. Ключ сотрудника передаёт звонки с правами сотрудника:
- Звонок, который попал в заявку, невидимую сотруднику (видимость «только свои», заявка другого менеджера), всё равно записывается в эту заявку, но её номер не раскрывается: ответ
201или200сlead_number: nullиhidden: true(created_leadкак обычно). - Звонок должен создать заявку, а у сотрудника нет права «Создавать заявки»:
403 PERMISSION_DENIEDсpermission: "leads.create"и текстом «Нет права создавать заявки. Обратитесь к администратору компании.». Ничего не сохраняется: ни заявки, ни звонка, ни записи. - Заявку из звонка по ключу сотрудника, который видит только свои заявки, получает сам сотрудник. Если он видит все заявки компании, заявка уходит менеджеру по умолчанию, а без него владельцу, право «Менять ответственного» для этого не нужно (как у
POST /api/v1/leadsбезmanager). - Запись по ссылке (
record_url) к звонку в невидимой сотруднику заявке не принимается: звонок сохранён, в ответеrecording: "rejected"иrecording_errorс кодомPERMISSION_DENIED(«Запись не принята: звонок в заявке, которая вам не видна»). Файл записи к такому звонку (раздел 18):404 CALL_NOT_FOUND. - События звонка и событие «создана» у заявки из звонка пишутся без автора и без пометки «через API», как у ключа компании.
Для телефонии всей компании выпускайте ключ администратору или сотруднику с видимостью «все заявки» и правом «Создавать заявки»: тогда новые заявки из звонков получает менеджер по умолчанию.
Поля
| Поле | Обязательно | Описание |
|---|---|---|
call_id | да | Идентификатор звонка в АТС: от 1 до 100 символов из латинских букв, цифр и _ . -, хотя бы одна буква или цифра |
client_phone | в первом событии | Номер клиента (не меньше 7 цифр). В последующих событиях можно не передавать; некорректное значение (например, anonymous) в них игнорируется |
direction | нет | in (входящий, по умолчанию) или out (исходящий). Принимаются также incoming, outgoing, inbound, outbound. Другое значение: ошибка 400 |
line | нет | Ваш номер или линия: куда звонил клиент (для входящего) или с какого номера звонили (для исходящего). До 100 символов. По нему новая заявка получает источник и услугу из «Телефонных номеров» трекинга (выше) |
status | нет | Итог звонка, см. словарь ниже. До 100 символов |
duration | нет | Длительность разговора в секундах, целое число от 0 до 604800 |
started_at | нет | Время начала звонка в ISO-8601 со смещением часового пояса, например 2026-09-28T14:05:00+03:00 или 2026-09-28T11:05:00Z; смещение можно писать и как +0300 или +03 (2026-09-28 14:05:00+03, так печатает время PostgreSQL). Время без смещения отклоняется с ошибкой 400 |
source | нет | Источник для новой заявки. Не передан: из строки номера line, иначе «Звонок» |
service | нет | Услуга для новой заявки, любой текст. Не передана: из строки номера line |
record_url | нет | Ссылка на запись разговора, см. ниже |
Словарь status (регистр и разделители не важны: no answer, no_answer, NO-ANSWER одинаковы):
| Значение | В карточке |
|---|---|
answered | Принят |
no answer | Нет ответа |
missed | Пропущен |
busy | Занято |
cancel, canceled, cancelled | Отменён |
failed | Ошибка |
congestion | Перегрузка |
unallocated number | Номер не существует |
no money | Недостаточно средств |
unallowed destination | Направление запрещено |
Другое значение показывается в карточке как есть.
Время и часовой пояс
CRM хранит время в UTC и показывает каждому сотруднику в его часовом поясе. Поэтому started_at обязан содержать смещение: 2026-09-28 14:05:00 без смещения можно прочитать по-разному, и в карточке оказалось бы неверное время. Если АТС присылает время без пояса, прослойка должна дописать пояс, в котором работает АТС (обычно он задан в настройках кабинета АТС). Пример в разделе 19.
Запись разговора ссылкой
Если АТС отдаёт запись по ссылке, передайте её в record_url (в том же событии или отдельным запросом с тем же call_id). CRM поставит запись в очередь, скачает её в течение нескольких минут и прикрепит к звонку.
Требования к ссылке:
- схема
httpилиhttps, порт 80 или 443, длина до 2000 символов; - доступна из интернета без авторизации (подписанная ссылка АТС подходит). Логин и пароль в самой ссылке (
https://user:pass@...) не принимаются; - ведёт в интернет: IP-адрес в ссылке допустим только публичный (IPv6 только из глобального диапазона
2000::/3), имена внутренней сети (localhost, имя без точки, домены.local,.internalи подобные) не принимаются. Ссылки во внутреннюю сеть CRM не скачивает; - живёт не меньше нескольких часов: при временной ошибке CRM повторяет скачивание (всего 5 попыток) с растущими паузами примерно в 2, 4, 8 и 16 минут, на все попытки уходит от получаса и дольше. Если АТС позволяет задать срок жизни ссылки, задайте сутки;
- сервер записи присылает начало ответа (заголовки) не позже чем через 60 секунд, в том числе на каждом редиректе, и отдаёт файл целиком не дольше чем за 10 минут. Если в ответе есть заголовок
Content-Length, CRM через 30 секунд после начала передачи прикидывает, успеет ли файл докачаться за эти 10 минут, и прерывает попытку, если не успеет; безContent-Lengthсредняя скорость отдачи должна быть не ниже 8 КБ/с. Запись, которая не укладывается в эти пределы ни в одной из 5 попыток, к звонку не прикрепляется; - формат mp3, wav, ogg, webm, m4a или aac, размер до 200 МБ. Формат CRM определяет по содержимому файла, а не по расширению в ссылке, так что ссылка без расширения (например,
.../records/123/download) подходит. Если по ссылке пришёл не аудиофайл (страница ошибки или входа, JSON), это считается временной ошибкой скачивания: CRM повторит попытку, и после 5 неудачных попыток запись к звонку не прикрепляется.
Повтор той же ссылки для того же звонка не ставит вторую загрузку (ответ recording: "duplicate").
Если запись по ссылке принять нельзя, звонок и заявка всё равно сохраняются: ответ обычный (201 или 200), а в нём recording: "rejected" и поле recording_error с причиной:
recording_error.code | Причина | Что делать |
|---|---|---|
VALIDATION_ERROR | Ссылка не подходит под требования выше, подробности в recording_error.details | Исправить ссылку и прислать её тем же call_id |
QUOTA_EXCEEDED | Хранилище компании заполнено | Освободить место, затем прислать ссылку снова |
QUEUE_FULL | Очередь записей переполнена (раздел 5) | Прислать ссылку тем же call_id через несколько минут |
PERMISSION_DENIED | Ключ сотрудника, звонок в заявке, которую сотрудник не видит (выше) | Передавать звонки ключом администратора или сотрудника, который видит все заявки |
Ответ recording: "queued" значит только, что ссылка принята в очередь: размер записи CRM узнаёт после скачивания. Если запись не поместилась в оставшееся место хранилища или скачать её не удалось (часть отказов окончательна сразу, остальные после 5 попыток), к звонку она не прикрепляется, и ни в одном ответе API об этом уже не сообщается. Когда запись не поместилась, администраторы компании с подключённым Telegram получают уведомление «Хранилище компании заполнено» (не чаще раза в сутки). Освободите место и пришлите ту же ссылку снова тем же call_id. Если запись важна, держите запас места или загружайте её файлом (раздел 18): там отказ приходит в ответе сразу.
Проверяйте поле recording в ответе: если запись важна, а пришло rejected, сохраните ссылку у себя и повторите позже или загрузите файл методом из раздела 18.
Если ссылки нет, а файл есть, загрузите его методом из раздела 18.
Ответы
Звонок заведён этим запросом, 201 Created:
{ "success": true, "data": { "lead_number": 128, "call_id": "1727517900.1234", "created_lead": true } }
Звонок уже был, поля дописаны, 200 OK:
{ "success": true, "data": { "lead_number": 128, "call_id": "1727517900.1234", "created_lead": false, "recording": "queued" } }
created_lead:true, если этот запрос создал новую заявку;hidden(только ключ сотрудника):true, если звонок записан в заявку, которую сотрудник не видит;lead_numberтогдаnull;recording(только если переданrecord_url):queuedпоставлена в очередь,duplicateэта ссылка уже была,rejectedзапись не принята (звонок при этом сохранён);recording_error(только приrecording: "rejected"):code,messageи, для негодной ссылки,details.
Запись не принята, звонок сохранён, 200 OK:
{
"success": true,
"data": {
"lead_number": 128,
"call_id": "1727517900.1234",
"created_lead": false,
"recording": "rejected",
"recording_error": {
"code": "VALIDATION_ERROR",
"message": "Ссылка на запись не принята",
"details": ["record_url: допустим только порт 80 или 443"]
}
}
}
Исходящий звонок на номер без заявки при выключенной настройке, 200 OK:
{ "success": true, "data": { "lead_number": null, "call_id": "out-777", "skipped": true, "reason": "no_lead_for_outgoing" } }
Пример: входящий звонок
Начало звонка:
curl -X POST https://crm.linkodium.com/api/v1/calls \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"call_id": "1727517900.1234",
"direction": "in",
"client_phone": "+79991234567",
"line": "+73422000000",
"started_at": "2026-09-28T14:05:00+05:00",
"source": "Реклама Яндекс"
}'
Конец звонка (тот же call_id):
curl -X POST https://crm.linkodium.com/api/v1/calls \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "call_id": "1727517900.1234", "status": "answered", "duration": 185 }'
Готовая запись:
curl -X POST https://crm.linkodium.com/api/v1/calls \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "call_id": "1727517900.1234", "record_url": "https://records.example-pbx.com/abc123.mp3" }'
Пример: PHP
<?php
function crmCall(array $fields): array
{
$ch = curl_init('https://crm.linkodium.com/api/v1/calls');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($fields, JSON_UNESCAPED_UNICODE),
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-API-Key: ' . getenv('CRM_API_KEY')],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$body = curl_exec($ch);
return [curl_getinfo($ch, CURLINFO_HTTP_CODE), json_decode((string)$body, true)];
}
[$code, $resp] = crmCall([
'call_id' => '1727517900.1234',
'direction' => 'in',
'client_phone' => '+79991234567',
'started_at' => (new DateTimeImmutable('now'))->format(DATE_ATOM), // со смещением
]);
18. Запись звонка файлом: POST /api/v1/calls/recording
Для АТС, которые не дают публичную ссылку на запись: прослойка скачивает файл сама и отправляет его в CRM.
- Тело: только
multipart/form-dataс полямиcall_idиfile. JSON или форма без файла:400 VALIDATION_ERROR(ссылку на запись передавайте в/api/v1/calls, полеrecord_url). - Звонок с этим
call_idуже должен быть передан в/api/v1/calls, иначе404 CALL_NOT_FOUND. По одному файлу CRM заявку не создаёт: в файле нет номера клиента. - Ключ сотрудника: звонок в заявке, которую сотрудник не видит, отвечает тем же
404 CALL_NOT_FOUND(«Звонок с таким call_id не найден: сначала передайте его в /api/v1/calls»), файл не сохраняется. Если заявку передали другому менеджеру во время загрузки, ответ тоже404, а файл отбрасывается. - Форматы: mp3, wav, ogg, webm, m4a, aac. CRM проверяет содержимое файла: файл, в котором распознан другой тип (HTML, картинка, архив, документ), отклоняется с ошибкой
400. Если тип по содержимому определить нельзя (так бывает у mp3 без заголовка), решает расширение имени файла из списка выше. Пустой файл:400. - Размер: до 100 МБ, больше:
413 PAYLOAD_TOO_LARGE. - Место в хранилище компании: если его не хватает,
403 QUOTA_EXCEEDED. - Одновременно с одного IP-адреса: до 8 загрузок, сверх этого
429 RATE_LIMITEDсRetry-After. - Повтор того же файла (то же содержимое) для того же звонка второй записи не создаёт: ответ
200сduplicate: trueи данными ранее принятой записи. Имя файла при этом значения не имеет. - У одного звонка может быть несколько записей (например, разговор в двух частях или каналы клиента и оператора отдельными файлами). Разные файлы принимаются все, даже с одинаковыми именем и размером. Имена всё же лучше давать разные (
call-1727517900.1234-1.mp3,...-2.mp3): под этим именем запись видят и скачивают менеджеры. - Если расширение в имени файла не совпадает с форматом, который CRM определила по содержимому, в имени оно заменяется на верное (поле
file_nameответа).
Ответ 201 Created:
{
"success": true,
"data": {
"lead_number": 128,
"call_id": "1727517900.1234",
"recording": { "id": "5d0c7a4e-8f3b-4f5e-9a51-2b7f0c1d9e20", "file_name": "call.mp3", "size": 482113, "type": "audio/mpeg" }
}
}
Повтор уже принятого файла, 200 OK:
{
"success": true,
"data": {
"lead_number": 128,
"call_id": "1727517900.1234",
"duplicate": true,
"recording": { "id": "5d0c7a4e-8f3b-4f5e-9a51-2b7f0c1d9e20", "file_name": "call.mp3", "size": 482113, "type": "audio/mpeg" }
}
}
Пример: curl
curl -X POST https://crm.linkodium.com/api/v1/calls/recording \
-H "X-API-Key: $CRM_API_KEY" \
-F "call_id=1727517900.1234" \
-F "file=@/tmp/call-1727517900.1234.mp3"
Пример: PHP
<?php
$callId = '1727517900.1234';
$part = 1; // номер части, если АТС режет разговор на несколько файлов
$ch = curl_init('https://crm.linkodium.com/api/v1/calls/recording');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'call_id' => $callId,
'file' => new CURLFile('/tmp/call.mp3', 'audio/mpeg', 'call-' . $callId . '-' . $part . '.mp3'),
],
CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('CRM_API_KEY')],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// 201 - принята, 200 - этот файл уже был. 429, 5xx и обрыв связи: повторить позже.
if (!in_array($code, [200, 201], true)) {
error_log('CRM recording: HTTP ' . $code . ' ' . $body);
}
19. Пример прослойки для Novofon и Zadarma
Novofon и Zadarma сообщают о звонках вебхуками (уведомлениями о событиях АТС) на адрес, который вы указываете в кабинете АТС. Прослойка это небольшой скрипт на вашем сервере: принимает вебхук, переводит его в вызов /api/v1/calls и сразу отвечает АТС.
Соответствие событий
| Событие АТС | Поля АТС | Вызов CRM |
|---|---|---|
NOTIFY_START (входящий, начало) | pbx_call_id, caller_id, called_did, call_start | call_id = pbx_call_id, direction = in, client_phone = caller_id, line = called_did, started_at = call_start с поясом |
NOTIFY_END (входящий, конец) | pbx_call_id, caller_id, called_did, duration, disposition, call_start | call_id = pbx_call_id, direction = in, client_phone = caller_id, status = disposition, duration = duration |
NOTIFY_OUT_START (исходящий, начало) | pbx_call_id, destination, internal, call_start | call_id = pbx_call_id, direction = out, client_phone = destination, started_at = call_start с поясом |
NOTIFY_OUT_END (исходящий, конец) | pbx_call_id, destination, caller_id, duration, disposition | call_id = pbx_call_id, direction = out, client_phone = destination, line = caller_id, status = disposition, duration = duration |
NOTIFY_RECORD (запись готова) | pbx_call_id, call_id_with_rec | ссылку получить методом API АТС pbx/record/request, затем call_id = pbx_call_id, record_url = ссылка |
Важно:
- В
call_idпередавайтеpbx_call_id: он одинаков у всех событий одного звонка.call_id_with_recнужен только для запроса ссылки на запись. client_phoneпередавайте в каждом событии, где номер есть: если первое событие потеряется, звонок заведёт следующее.call_startу этих АТС приходит без часового пояса, во времени, заданном в кабинете АТС. Прослойка должна дописать этот пояс (в примере константаPBX_TIMEZONE).- Ссылку на запись АТС готовит не сразу; Novofon советует запрашивать её примерно через 40 секунд после уведомления. Поэтому прослойка сначала отвечает АТС, а ссылку запрашивает потом, с паузой и повторами (в примере ниже). Если ссылку так и не получили, запишите это в журнал: иначе запись пропадёт молча.
- Ответ
404 CALL_NOT_FOUNDнаNOTIFY_RECORDштатен для исходящего звонка, если в компании выключена настройка «Исходящий звонок на номер без заявки создаёт новую заявку»: такой звонок не сохранялся, и запись к нему прикладывать некуда. Для входящего звонка тот же ответ значит, что начало и конец звонка до CRM не дошли (например, CRM ответила429или5xx), и запись потеряна. Прослойка не знает направление звонка поNOTIFY_RECORD, поэтому пример пишет в журнал и404. - Если в ответе
recording: "rejected", запись не принята, хотя звонок сохранён. Причину смотрите вrecording_error(раздел 17).
Пример прослойки (PHP)
<?php
// Прослойка АТС -> Linkodium CRM. Адрес этого скрипта укажите в кабинете АТС
// как адрес уведомлений о звонках.
const CRM_URL = 'https://crm.linkodium.com/api/v1/calls';
const PBX_TIMEZONE = 'Europe/Moscow'; // пояс из настроек кабинета АТС
// Проверка адреса при подключении уведомлений (Novofon, Zadarma).
if (isset($_GET['zd_echo'])) {
exit(preg_replace('/[^A-Za-z0-9]/', '', (string)$_GET['zd_echo']));
}
// Здесь же стоит проверить подпись уведомления (заголовок Signature)
// по документации АТС: иначе прислать «звонок» сможет кто угодно.
function crmCall(array $fields): void
{
$ch = curl_init(CRM_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(array_filter($fields, fn($v) => $v !== '' && $v !== null),
JSON_UNESCAPED_UNICODE),
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-API-Key: ' . getenv('CRM_API_KEY')],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$resp = json_decode((string)$body, true);
if (!in_array($code, [200, 201], true)) {
// Ответ и тело сохраните в журнал: по ним видно, что не так с запросом.
// 429, 5xx и обрыв связи стоит повторить позже (раздел 4). 404 на
// запись исходящего звонка штатен (звонок не сохранялся), на запись
// входящего значит потерянный звонок: его тоже видно здесь.
error_log('CRM calls: HTTP ' . $code . ' ' . $body);
} elseif (($resp['data']['recording'] ?? '') === 'rejected') {
// Звонок сохранён, запись нет: причина в recording_error (раздел 17).
error_log('CRM calls: запись не принята ' . json_encode($resp['data']['recording_error'] ?? []));
}
}
// Время АТС «2026-09-28 14:05:00» -> «2026-09-28T14:05:00+03:00».
function pbxTime(string $raw): string
{
$dt = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $raw, new DateTimeZone(PBX_TIMEZONE));
return $dt ? $dt->format(DATE_ATOM) : '';
}
$p = $_POST;
switch ($p['event'] ?? '') {
case 'NOTIFY_START':
crmCall([
'call_id' => $p['pbx_call_id'] ?? '',
'direction' => 'in',
'client_phone' => $p['caller_id'] ?? '',
'line' => $p['called_did'] ?? '',
'started_at' => pbxTime($p['call_start'] ?? ''),
// Источник по номеру линии: каждой рекламной линии свой.
'source' => ['73422000000' => 'Сайт', '79670000000' => 'Реклама'][$p['called_did'] ?? ''] ?? 'Звонок',
]);
break;
case 'NOTIFY_END':
crmCall([
'call_id' => $p['pbx_call_id'] ?? '',
'direction' => 'in',
'client_phone' => $p['caller_id'] ?? '',
'line' => $p['called_did'] ?? '',
'status' => $p['disposition'] ?? '',
'duration' => (int)($p['duration'] ?? 0),
'started_at' => pbxTime($p['call_start'] ?? ''),
]);
break;
case 'NOTIFY_OUT_START':
crmCall([
'call_id' => $p['pbx_call_id'] ?? '',
'direction' => 'out',
'client_phone' => $p['destination'] ?? '',
'started_at' => pbxTime($p['call_start'] ?? ''),
]);
break;
case 'NOTIFY_OUT_END':
crmCall([
'call_id' => $p['pbx_call_id'] ?? '',
'direction' => 'out',
'client_phone' => $p['destination'] ?? '',
'line' => $p['caller_id'] ?? '',
'status' => $p['disposition'] ?? '',
'duration' => (int)($p['duration'] ?? 0),
'started_at' => pbxTime($p['call_start'] ?? ''),
]);
break;
case 'NOTIFY_RECORD':
// Ссылку АТС готовит не сразу (Novofon советует ждать около 40 секунд),
// а АТС ждёт ответа на уведомление. Поэтому сначала отвечаем АТС, потом
// ждём и запрашиваем ссылку. В боевой прослойке надёжнее очередь задач
// (cron, supervisor); здесь для краткости пауза после ответа (PHP-FPM).
header('Content-Type: application/json');
echo json_encode(['success' => true]);
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
// Ссылку на запись выдаёт API АТС (метод pbx/record/request, параметр
// call_id = call_id_with_rec, срок жизни ссылки lifetime). Используйте
// официальную библиотеку АТС; pbxRecordLink() здесь условная функция.
$url = '';
for ($try = 1; $try <= 3 && !$url; $try++) {
sleep(40);
$url = pbxRecordLink($p['call_id_with_rec'] ?? '', $p['pbx_call_id'] ?? '');
}
if (!$url) {
error_log('PBX: ссылку на запись не получили, звонок ' . ($p['pbx_call_id'] ?? ''));
} else {
crmCall(['call_id' => $p['pbx_call_id'] ?? '', 'record_url' => $url]);
}
exit;
}
header('Content-Type: application/json');
echo json_encode(['success' => true]);
Если ссылку на запись получить нельзя, но файл скачать можно, отправьте его методом из раздела 18 с тем же call_id.
20. Переход со старых адресов
Старые адреса /api/lead.add (и /api/leads), /api/lead.call, /api/call.record оставлены только для интеграций, подключённых до выхода API v1, и будут отключены. Новые интеграции подключайте только к v1.
С 14.10.2026 старые адреса не принимают запросы без ключа: такой запрос получает 401 UNAUTHORIZED с текстом «Этот адрес больше не принимает запросы без ключа. Подключайтесь к API v1: …» и ссылкой на этот раздел. Работает только прежний ключ компании (выпущенный до появления ключей сотрудников) в заголовке X-API-Key или Authorization: Bearer: вызов попадает в компанию ключа. Новый ключ компании не выпускается, поэтому новые интеграции подключайте к v1 с ключом сотрудника.
| Было | Стало |
|---|---|
POST /api/lead.add с прежним ключом компании, в ответе lead_id | POST /api/v1/leads с ключом, в ответе lead_number |
POST /api/lead.call с lead_id, phone, called_did, disposition, call_start без пояса | POST /api/v1/calls с call_id, client_phone, line, status, started_at с поясом. lead_id не нужен: заявку находит CRM |
POST /api/call.record с lead_id, record_url | record_url в POST /api/v1/calls с тем же call_id |
| Прослойка хранит у себя соответствие «звонок -> заявка» | Не нужно |
Ключ, который не прошёл проверку, даёт 401 UNAUTHORIZED. Ключ сотрудника старые адреса не принимают: 403 KEY_NOT_ALLOWED, с ним работайте через /api/v1/leads и /api/v1/calls.
Отличия старого /api/call.record от записи ссылкой в v1: этот адрес принимает только запись, поэтому отказ отвечает на весь запрос. Негодная ссылка (требования те же, что в разделе 17): 400 VALIDATION_ERROR с причиной в error.details; хранилище заполнено: 403 QUOTA_EXCEEDED; очередь переполнена: 429 QUEUE_FULL с заголовком Retry-After.
21. Импорт через API
Тот же мастер, что кнопка «Импорт» в списке заявок (Выгрузка и импорт): файл, сопоставление колонок, запуск, шаги, отчёт об ошибках. Область leads:import и право сотрудника «Загружать заявки из файла (импорт)» (по умолчанию у менеджеров выключено). Файл XLSX или CSV до 20 МБ, до 10 000 строк. У сотрудника одновременно идёт один импорт.
| Адрес | Что делает |
|---|---|
GET /api/v1/leads/import | Последний импорт сотрудника current (или null), поля сопоставления fields (key, label), required_any (нужно хотя бы одно из имени, телефона, email), допустимые duplicates и manager_modes, limits |
POST /api/v1/leads/import | Загрузить файл: multipart/form-data, поле file. Ответ 201: job и state (имя и формат файла, число строк, колонки с образцом значений, автоматическое сопоставление) |
GET /api/v1/leads/import/{job} | Состояние импорта state. У загруженного: preview_rows (номера строк файла у строк образца, как «Строка» в отчёте) и default_manager ({name, owner}: кто получит заявки в режиме default, owner: true у владельца; null: заявки без ответственного). У запущенного: params.manager_mode, режим, применённый на деле: default, user, column или self (ключ сотрудника без права «Менять ответственного») |
POST /api/v1/leads/import/{job}/start | Запустить: map ({"<номер колонки с 0>": "<поле>"}, можно передать автоматическое сопоставление из состояния как есть), header (первая строка заголовки, по умолчанию true), status (номер или название, по умолчанию «Новая заявка»), manager_mode (default, user, column), manager (при user обязателен: uuid сотрудника или пустая строка, тогда заявки без ответственного; без поля или с null ответ 400), source, duplicates (skip, append, create). default (и без manager_mode): менеджер по умолчанию компании, без него владелец. Выбор ответственного (user, column) требует права «Менять ответственного» (без него 403). Сотрудник без этого права ответственным всех заявок импорта получает себя при любом manager_mode, в том числе default; если право снимут во время импорта, себя получают оставшиеся строки |
POST /api/v1/leads/import/{job}/step | Обработать следующую часть строк (до 8 секунд на запрос). Повторяйте, пока state.status равно running; в ответе счётчики созданных, дополненных и пропущенных строк. busy: true: шаг не выполнен, потому что импорт занят другим запросом или на платформе одновременно идёт много импортов; повторите шаг через 1-2 секунды, а не сразу: повтор без паузы только занимает очередь |
POST /api/v1/leads/import/{job}/cancel | Остановить идущий импорт (обработанное остаётся) или удалить загруженный и завершённый: {deleted, state} |
GET /api/v1/leads/import/{job}/errors.xlsx | Отчёт: строки с ошибками и пропуски с исходными данными, номером строки и причиной |
Ошибки: 400 VALIDATION_ERROR (файл не разобран, сопоставление без имени, телефона и email), 403 PERMISSION_DENIED (leads.import; leads.reassign у user и column; если право «Загружать заявки из файла» сняли во время импорта, шаг его останавливает), 404 (чужой или удалённый импорт), 409 CONFLICT (у сотрудника уже идёт импорт), 413, 429 RATE_LIMITED с Retry-After. Заявки из импорта отмечены в ленте пометкой via: "import".
Загруженный и завершённый (с отчётом) импорт хранится сутки после последнего изменения, идущий импорт без новых шагов двое суток после последнего шага; потом он удаляется и отвечает 404.
curl -X POST https://crm.linkodium.com/api/v1/leads/import \
-H "X-API-Key: $CRM_API_KEY" \
-F "file=@/tmp/leads.xlsx"
curl -X POST https://crm.linkodium.com/api/v1/leads/import/$JOB/start \
-H "X-API-Key: $CRM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"map": {"0": "name", "1": "phone", "2": "email"}, "duplicates": "append"}'
curl -X POST https://crm.linkodium.com/api/v1/leads/import/$JOB/step \
-H "X-API-Key: $CRM_API_KEY"
22. Расшифровка звонков
CRM расшифровывает новые записи сама. Через API можно узнать состояние расшифровки и запросить её для записи, которая не расшифрована (полученной до включения расшифровки или неудавшейся), как кнопки «Расшифровать» и «Повторить» в карточке.
| Адрес | Область | Что делает |
|---|---|---|
GET /api/v1/calls/recordings/{uuid}/transcript | leads:read | Расшифровка записи: recording, state (none не запрашивали, pending, processing, done, dead), text, language, duration, error_code, error, updated_at |
POST /api/v1/calls/recordings/{uuid}/transcribe | calls:transcribe | Поставить запись в очередь. Ответ 202: recording, state (queued, running, done, failed), transcript (объект как в ленте) |
uuid записи берётся из recordings[].uuid события call ленты (раздел 12). Запись не найдена или заявка не видна сотруднику: 404. Расшифровка выключена в настройках компании или не настроена: 409 FEATURE_DISABLED. Очередь заполнена: 429 QUEUE_FULL с Retry-After: 60. Готовый текст появляется в ленте через несколько минут; запись длиннее 60 минут не расшифровывается. Запись уходит сервису распознавания речи.
23. Чаты: правка, удаление, стикеры, голосовые
Всё, что сотрудник делает в разделе «Чаты» кроме ответа текстом и файлами (раздел 16). Права те же, что в CRM: видимость по режиму доступа к чатам, ответ по праву ответа, удаление из CRM и блокировка только администратору.
| Адрес | Область | Что делает |
|---|---|---|
GET /api/v1/chats/meta | chats:read | Данные раздела: режим доступа, непрочитанные, ждущие ответа, заполнено ли хранилище, пределы текста, файлов и голосовых, каналы, возможности видов каналов (стикеры, окна правки и удаления) |
GET /api/v1/chats/changes | chats:read | Журнал изменений видимых чатов: без cursor пустой список и текущий курсор, затем события после него (message_new, message_update, message_delete, contact_update, contact_read, read_all, conversation_delete). reset: true: перечитайте список чатов |
GET /api/v1/chats/{uuid} | chats:read | Шапка переписки: chat, текущая заявка, заявки клиента, совпадение телефона |
POST /api/v1/chats/read-all | chats:read | «Отметить все прочитанными»: {marked, more, unread} |
POST /api/v1/chats/{uuid}/dismiss | chats:write | «Ответ не нужен»: снять отметку «Ждёт ответа» |
GET /api/v1/chats/{uuid}/stickers | chats:read | Стикеры для ответа: недавние, полученные от клиента, наборы |
GET /api/v1/chats/stickers/{uuid} | chats:read | Файл стикера (v=thumb: превью) |
POST /api/v1/chats/{uuid}/sticker | chats:write | {"sticker": "<uuid>", "reply_to": "<uuid>"}: стикер клиенту; ответ 201 {message} |
POST /api/v1/chats/{uuid}/voice | chats:write | Голосовое: multipart/form-data, file (аудио до 10 МБ) и duration (до 900 секунд), место в хранилище компании (403 QUOTA_EXCEEDED, если его не хватает); ответ 201 {message} |
POST /api/v1/chats/messages/{uuid}/edit | chats:edit | {"text": "..."}: изменить своё сообщение в окне правки канала |
POST /api/v1/chats/messages/{uuid}/delete-at-client | chats:edit | Удалить своё сообщение у клиента в окне удаления канала |
POST /api/v1/chats/messages/{uuid}/retry | chats:write | Повторить неотправленное сообщение (автор или администратор) |
POST /api/v1/chats/messages/{uuid}/delete | chats:admin, администратор | {"confirm": true}: удалить сообщение из CRM; ответ {deleted: [uuid]} |
POST /api/v1/chats/{uuid}/delete | chats:admin, администратор | {"confirm": true}: удалить переписку из CRM; ответ {deleted, more}, событие в ленте заявки |
POST /api/v1/chats/{uuid}/phone | chats:write и leads:write | Записать телефон из переписки в заявку: {"phone": "...", "message": "<uuid>", "mode": "check" | "save" | "replace"}. check ищет совпадение и пишет телефон, только если в заявке его нет; save при другом телефоне в заявке отвечает 409; replace заменяет |
GET /api/v1/chats/{uuid}/visitor | chats:read и visitors:read | Карточка посетителя чата на сайте: на сайте сейчас, страница, email, источник, устройство, согласие. Источник source (метки UTM и сайт, откуда пришёл) null, если текущая заявка клиента сотруднику не видна; mail.opt_out true, если от писем «Вам ответили в чате» отписался этот посетитель или посетитель этой же компании с тем же email |
POST /api/v1/chats/{uuid}/block | chats:admin, администратор | {"blocked": true}: заблокировать посетителя виджета (false: разблокировать) |
GET /api/v1/chats/{uuid}/avatar | chats:read | Аватар клиента |
Отказы: не автор сообщения 403 PERMISSION_DENIED (permission: chats.author); нет права ответа (режим «отвечает ответственный», клиент другого сотрудника) 403 PERMISSION_DENIED: у стикера и голосового permission: chats.mode, как у ответа в разделе 16, у прочих адресов раздела chats.reply; окно правки или удаления истекло, сообщение не в том состоянии 409 CONFLICT (error.reason: window, state); у клиента нет заявки 409 (no_lead). Удаление у клиента не требует права ответа: своё сообщение автор убирает и тогда, когда ответ закрыт режимом доступа. При отключённом канале или непринятом токене удаление у клиента невозможно (409 CHAT_UNAVAILABLE), и в MessageDTO can_delete_at_client тогда false.
24. Почта: ящики, участники, удаление
Всё, что сотрудник делает в разделе «Почта» кроме чтения и отправки (раздел 15). Только ящики, где сотрудник участник; действия управления ящиком (участники, отключение, имя отправителя, пароль, серверы) требуют права управления ящиком: 403 PERMISSION_DENIED с permission: mail.manage. Текст в error.message для человека и зависит от действия (например, «Управлять доступом к ящику может участник с правом управления»): проверяйте permission, а не текст. Почта приостановлена: 409 SERVICE_PAUSED. Пароли передаются только в теле, не попадают в ответы и журналы.
| Адрес | Область | Что делает |
|---|---|---|
GET /api/v1/mail/meta | mail:read | Данные раздела: можно ли подключать ящики, пауза, хранилище, пределы отправки и загрузки, провайдеры почты с серверами |
GET /api/v1/mail/changes | mail:read | Журнал изменений ящиков сотрудника: без cursor пустой список и текущий курсор, затем события (thread_update, thread_remove, account_update, reset) и gone_accounts (ящики, доступ к которым пропал) |
GET /api/v1/mail/accounts/{uuid} | mail:read | Ящик целиком: состояние, ошибки, папка отправленных, загрузка писем (sync), свои настройки (me); настройки серверов (servers, без паролей) только управляющему ящиком и ключу с mail:manage |
POST /api/v1/mail/accounts/{uuid}/refresh | mail:read | «Обновить»: {queued} |
POST /api/v1/mail/accounts/{uuid}/backfill | mail:manage | «Загрузить ещё»: письма на шаг раньше |
POST /api/v1/mail/accounts/connect | mail:manage | Подключить ящик: email, password, необязательно smtp_password, sender_name (или display_name, как поле в ответе GET /api/v1/mail/accounts; оба с разными значениями: 400), provider, login. Серверы imap и smtp (host, port, security) обязательны, кроме адресов Яндекса, Mail.ru, Gmail, Рамблера, iCloud и Yahoo (домены в providers[].domains ответа GET /api/v1/mail/meta): им CRM подставляет серверы по домену адреса, а переданные должны совпасть с таблицей, иначе 400. provider серверы не подставляет: для своего домена на Яндекс 360, VK WorkMail или Google Workspace передайте серверы провайдера из providers того же ответа. Ответ 202 {account, status: "checking"}, итог проверки в GET /mail/accounts/{uuid}. Адрес уже подключён в компании: без confirm ответ 400 CONFIRM_REQUIRED с error.join_confirm (imap_host, smtp_host: сервер, на котором CRM проверит ваш пароль); повторите с "confirm": true, ответ 202 {join: {op}}, итог в GET /api/v1/mail/joins/{op} |
GET /api/v1/mail/joins/{op} | mail:manage | Итог присоединения: state (pending, ok, fail), account, error |
POST /api/v1/mail/accounts/{uuid}/settings | mail:manage | Подпись signature, уведомления notify (свои у каждого участника); sender_name (или display_name), login, password, smtp_password, imap, smtp (только управляющему). Смена сервера или логина без password: 400. display_name, равный нынешнему имени (объект ящика прислан обратно целиком), сменой имени не считается; оба поля с разными значениями: 400 |
GET /api/v1/mail/accounts/{uuid}/members | mail:manage | Участники ящика; управляющему ещё кандидаты candidates |
POST /api/v1/mail/accounts/{uuid}/members | mail:manage | {"user": "<uuid>", "can_manage": bool}: дать доступ |
POST /api/v1/mail/accounts/{uuid}/members/{user}/remove | mail:manage | {"confirm": true}: убрать участника (последнего управляющего убрать нельзя: 409) |
POST /api/v1/mail/accounts/{uuid}/leave | mail:manage | {"confirm": true}: выйти из ящика самому |
POST /api/v1/mail/accounts/{uuid}/disconnect | mail:manage | {"confirm": true}: отключить ящик для всех и удалить его письма из CRM |
POST /api/v1/mail/messages/{uuid}/retry | mail:send | Повторить неотправленное письмо (автор или управляющий ящиком) |
POST /api/v1/mail/threads/{uuid}/delete | mail:remove | {"confirm": true, "messages": ["<uuid>"]}: удалить письма треда (до 100) или весь тред без messages; ответ {deleted, thread_deleted}. Письма сразу исчезают из CRM, а на сервере почты служба переносит их в «Корзину» ящика, как кнопка «Удалить» в CRM (если папки «Корзина» на сервере нет, письмо у любого провайдера остаётся на сервере в своей папке: CRM его там не стирает и корзину сама не создаёт) |
POST /api/v1/mail/threads/{uuid}/unbind | mail:send | {"lead": 128}: отвязать переписку от заявки |
POST /api/v1/mail/threads/{uuid}/lead-create | leads:create и mail:send | «Создать заявку» из письма: title, name, email, phone, message (по умолчанию из письма, см. lead_prefill), manager, confirm_duplicate. manager: uuid сотрудника или null (без ответственного), не передан: менеджер по умолчанию компании, без него владелец; так только с правом «Менять ответственного», без него ответственный всегда сам сотрудник ключа, а manager с другим сотрудником или null даёт 403 (leads.reassign). Новая заявка: 201 {lead, lead_number, created: true, duplicate: false, thread}. Клиент уже в активной заявке, которую сотрудник видит: переписка привязывается к ней, ответ 200 {lead, lead_number, created: false, duplicate: true, matched_by, thread}. lead (заявка целиком) приходит только ключу с областью leads:read, иначе null. 409 DUPLICATE_FOUND с error.reason: confirm_duplicate (только у переписки с неподтверждённым отправителем: письмо не прошло проверку подлинности или от адреса клиента в ней нет входящих писем; номер найденной заявки в error.lead): повторите с confirm_duplicate: true; duplicate_hidden (клиент в заявке, которую сотрудник ключа не видит: другого сотрудника или без ответственного; номера в ответе нет): повтор не поможет, привязать переписку может ответственный той заявки или администратор |
Подключение ящика: не больше 10 попыток в час на ключ, кроме пределов сотрудника и компании, как в CRM.
Удаление писем (mail:remove) меняет и сам почтовый ящик: письма уходят в «Корзину» на сервере почты (в своей папке их больше не видят и другие почтовые программы), а корзину провайдер очищает по своим правилам. Если корзины на сервере нет, письмо там остаётся на месте, удалено оно только из CRM. Отключение ящика (disconnect) и выход из него (leave) письма на сервере почты не трогают.
25. Компания, сотрудники, интеграции
Настройки компании, права доступа, сотрудники и приглашения, услуги и быстрые ссылки, API-ключи, Telegram, MAX, виджет и Авито, а также профиль, уведомления, компании сотрудника и обращения в поддержку описаны на отдельной странице: API: компания, сотрудники, интеграции и профиль. Административные адреса доступны только ключу администратора или владельца, области управления компанией и профилем сотрудник выпускает только себе.
Настройки сквозной аналитики (сайты, телефонные номера, Яндекс Метрика, ДАРА, цели конверсий, журналы касаний, визитов и конверсий) администратор меняет через адреса /api/v1/tracking... с областями tracking:read и tracking:write: API компании, раздел «Трекинг». Визит заявки и отчёт по источникам доступны любому сотруднику с leads:read (раздел 10, раздел 9).
Чек-лист «Настройка CRM», список «Начало работы» сотрудника и пример заявки (адреса /api/v1/onboarding...): API компании, раздел «Настройка CRM».
Уход из компании (POST /api/v1/company/leave) без transfer_to отдаёт открытые заявки менеджеру по умолчанию, а если он не назначен, недоступен или уходит сам, владельцу компании; если и владелец недоступен (учётная запись отключена), заявки остаются без ответственного. "transfer_to": "none" оставляет их без ответственного всегда. Ключи, привязанные к самому уходящему или исключённому сотруднику, перестают действовать при любом значении revoke_keys (401); без отзыва они снова заработают, если сотрудника пригласят обратно. PATCH /api/v1/integrations/{uuid}/settings у виджета проверяет обе формы (поля канала и поля виджета) до записи: при ошибке в любой из них ничего не сохраняется.
- Исключение сотрудника (
POST /api/v1/members/{uuid}/remove) безtransfer_toотдаёт его открытые заявки по правилу ухода: менеджеру по умолчанию, если он работает и это не исключаемый, иначе владельцу, если он работает, иначе заявки остаются без ответственного. Исключение владельца всегда отвечает403 PERMISSION_DENIEDсpermission: "company.owner"(раньше сtransfer_to, равным ему самому, было400). - Ответы исключения
{moved, invites_dropped, keys_revoked, default_manager_changed, default_manager}и ухода{left, moved, keys_revoked, default_manager_changed, default_manager}:default_manager_changedравноtrue, если ушедший был менеджером по умолчанию и им стал получатель его заявок (при"none"владелец компании);default_manager: текущий менеджер по умолчанию{uuid, name}илиnull. PATCH /api/v1/permissions: если права компании дольше 10 секунд сохраняет другой запрос, ответ409 CONFLICT, ничего не записано; повторите через несколько секунд.- Обращение в поддержку
POST /api/v1/support/requests({subject, message}): тема до 200 символов, текст до 10 000. Ключ обращения обязателен: заголовокIdempotency-Keyили полеclient_request_id(1-255 видимых символов ASCII, как в разделе 6; если переданы оба, они должны совпадать), без ключа400 VALIDATION_ERROR. UUID v4 в любом регистре становится полемrequestстрочными буквами, любой другой ключ превращается в производный UUID. Ответ202{request, state, ticket, text, duplicate}. Статус:GET /api/v1/support/requests/{request}, гдеrequestвзят из ответа; спрашивайте раз в несколько секунд, покаstateне станетsentилиfailed(sending: отправляется;delayed: CRM повторит отправку сама;sent: обращение в поддержке, номер вticket;failed: отправьте обращение заново с новым ключом). Тот же ключ с другой темой или текстом:422 IDEMPOTENCY_MISMATCHв течение суток, позже409 CONFLICT. Не больше 60 обращений в час:429,Retry-Afterравен числу секунд до конца часового окна (до 3600).503 SERVICE_UNAVAILABLE: приём обращений через API закрыт (безRetry-After) или у сотрудника 20 недоставленных обращений либо заполнена общая очередь (Retry-After: 300).
26. ИИ-ассистенты (MCP)
Тот же API доступен ИИ-ассистентам по протоколу MCP (Model Context Protocol) по адресу https://crm.linkodium.com/api/v1/mcp: Claude, ChatGPT, Cursor и другие клиенты ищут заявки, читают переписку и готовят ответы с правами сотрудника. Вход через OAuth 2.1 (приложение само откроет страницу CRM для согласия) или по API-ключу сотрудника в заголовке Authorization: Bearer. Адреса OAuth для клиента: метаданные /.well-known/oauth-protected-resource и /.well-known/oauth-authorization-server, согласие /oauth/authorize/, регистрация клиента POST /api/v1/oauth/register, токены POST /api/v1/oauth/token, отзыв POST /api/v1/oauth/revoke (PKCE S256). MCP (/api/v1/mcp) и /api/v1/oauth/register, /token, /revoke принимают только POST, метаданные /.well-known/... отдаются на GET, страница согласия /oauth/authorize/ открывается в браузере. Все они отвечают своим форматом (JSON-RPC у MCP, ошибки OAuth по RFC 6749), а не форматом раздела 3. Каждое изменение заявки через ассистента видно в ленте с пометкой via: "mcp". Данные посетителя (область visitors:read: IP и браузер, в том числе в тексте событий) ассистенту не отдаются при любом ключе. Подключение, разрешения и список инструментов: Подключение ИИ-ассистентов по MCP.
27. Изменения API
12.11.2026: решение антиспама в ответе приёма, данные Авито в чатах. Прежние запросы и ответы не меняются, добавлены поля.
POST /api/v1/leads: полеspam_check_wait, в ответе ключspam_check{state, verdict, live, action, reasons}(раздел 8).- Чаты (раздел 16): у чата
item(объявление Авито), у сообщенияexternal(avito_app: ответ из приложения Авито). - Документация дополнена по коду, ответы не менялись: остальные поля сообщения чата (
auto,service,forward,album,sticker,location,venue,contact_card, размеры файла; раздел 16), отметки письмаauto,bounce,spoof_warning,auth_failи полный объект ящика вGET /api/v1/mail/accounts(раздел 15), допустимые значенияspam_check_wait(раздел 8).
11.11.2026: цель «Звонок от 30 с» и канал «Авито». Прежние запросы и ответы не меняются, добавлены значения.
PATCH /api/v1/tracking/conversions/settings: флагgoals.call_target; у конверсий и визита значениеgoal: "call_target"(цельcrm_call_target, первый входящий звонок заявки с разговором от 30 секунд; API компании, Сквозная аналитика).- Чаты:
channel.type: "avito"(раздел 16), событиеcreatedсsource: "avito"(раздел 12), канал Авито вGET /api/v1/integrations(API компании; подключается только в кабинете).
10.11.2026: канбан заявок. Прежние запросы и ответы не меняются, добавлен адрес.
GET /api/v1/leads/board: колонки по этапам со счётчиками и страницами заявок, как вид «Канбан» в CRM (раздел 9).
10.11.2026: антиспам и модули общего кода (Антиспам). Прежние запросы и ответы не меняются, добавлены поля. Повторное обращение (раздел 8) и входящий звонок (раздел 17) подшиваются только к активной заявке, созданной не раньше 30 суток назад; старше: новая заявка.
POST /api/v1/leadsи/api/lead.add: поле ловушкиlkt_hp(раздел 8).- Ключ
spam_checkвGETодной заявки (раздел 10); событие смены статуса антиспамом сvia: "antispam". - Администратору: модули общего кода в
/api/v1/trackingи у сайтов, отметка ключаskip_antispamу любого ключа, компании и сотрудника (API компании). - Заявка по ключу сотрудника с
tracking_token,form_pageилиlanding_pageпроверяется на спам как заявка по ключу компании (раздел 8). - MCP:
spam_checkв ответеget_lead.
10.11.2026: трекинг. Прежние запросы и ответы не меняются, добавлены поле и значения.
- Номера трекинга: поле
channel(API компании); у визита и в отчёте новые значения каналаmapsиother(раздел 9). - Входящий звонок на рекламный номер получает канал
adи без клика; несколько кликов по номеру от одного посетителя дают точностьprobableпо последнему клику. - Цель «обращение» не ставится заявке, созданной сразу в «Спаме» или ушедшей в «Спам» до отправки; заявки, созданные импортом, целей не дают.
08.11.2026: новая карточка заявки. Прежние запросы и ответы не меняются, добавлены поля и адреса.
GET /api/v1/leads/{номер}/history: история клиента (раздел 10).POSTиDELETE /api/v1/leads/{номер}/comments/{uuid}/pin: закреп комментария (раздел 13); ключpinned_commentвGETзаявки.POST /api/v1/leads/{номер}/statusпринимаетamountиcommentвместе со статусом (раздел 11).- Выгрузка XLSX: «Сумма сделки» и маркетинговые колонки; «IP» и «Браузер» только с
visitors:read. - MCP:
amountиcommentуset_lead_status,pinуadd_lead_comment.
07.11.2026: сквозная аналитика (Сквозная аналитика). Прежние запросы и ответы не меняются, добавлены новые поля и адреса.
- Новые области
tracking:readиtracking:write(администратор;tracking:writeвключаетtracking:readи выпускается только себе) и адреса/api/v1/tracking...(API компании). GET /api/v1/leads/{номер}/visit: визит заявки (раздел 10);GET /api/v1/tracking/report: отчёт «Источники обращений» (раздел 9). Оба сleads:read.- Заявка: новый ключ
amount(сумма сделки, число илиnull),PATCH /api/v1/leads/{номер}принимаетamount(раздел 10). Если ваш код сравнивает набор ключей заявки с закрытым списком, добавьте его. POST /api/v1/leadsи/api/lead.add: полеtracking_token(раздел 8).POST /api/v1/calls: еслиsourceилиserviceне переданы, новая заявка берёт их из строки номераlineв настройках трекинга, иначе как прежде «Звонок» (раздел 17).
18.10.2026: исправления по аудиту API и уточнения документации по коду.
Изменилось поведение:
- Обработчик запросов не ответил вовремя: вместо HTML-страницы
502или504ответ503 SERVICE_UNAVAILABLEв JSON сRetry-After: 5на адресах API, в том числе MCP, OAuth и старых адресах (разделы 3 и 4). POST /api/v1/leads:lead(заявка целиком) приходит только ключу с областьюleads:read, в том числе у повторного обращения; ключ формы сайта с однойleads:createполучаетlead: null(раздел 8). Email с кириллицей (info@сайт.рф) принимается при создании и правке заявки и в/api/lead.add.POST /api/v1/leadsпо ключу сотрудника:sourceсобытияcreatedтеперьapi(былоweb), в карточке заявки «Заявка создана через API» вместо «вручную»; такие события, записанные раньше, переписаны наapi. Заявка из формы CRM остаётсяweb, от ИИ-ассистентаwebсvia: mcp(разделы 8 и 12).- Повторное обращение и поиск заявки звонком: номер с кодом другой страны (записан с «+», код не
7) сравнивается целиком, а не по последним 10 цифрам:+84912345678больше не находит заявку с+74912345678(разделы 8 и 17). - Предел запросов с одного IP-адреса к API v1 поднят: около 100 запросов в секунду, пачка до 1000 разом; прежний предел давал
429общей прослойке нескольких компаний за одним адресом и синхронизации в несколько потоков, хотя лимит ключа ещё не был исчерпан (раздел 5). merge-candidates: телефон через 8 и через +7 находит одну заявку; новая причина отказа у заявки, которую другой сотрудник только что вывел из «Провала»:409 CONFLICT(раздел 11).- Лента:
started_atзвонка всегда в UTC сZилиnull(раздел 12);POST /api/v1/callsпринимает смещение+0300и+03(раздел 17). ПовторDELETE /api/v1/comments/{uuid}тем же ключом:already: trueвместо404(раздел 13). - Лента: параметр
updated_sinceи полеupdated_atу каждого события; события звонков, дополненные позже, и события, перенесённые объединением, приходят сupdated_sinceкак изменённые (раздел 12). - Тело письма:
html_too_big: trueи уstate: too_big, картинки письма вhtmlссылаются на/api/v1/mail/parts/{uuid};GET /api/v1/mail/parts/{uuid}?retry=1заново скачивает вложение вerror(раздел 15). - Ящик: имя отправителя принимается и полем
display_name; удаление писем в ящике без «Корзины» на сервере больше не стирает их там, письмо остаётся в своей папке;display_name, равный нынешнему имени, сменой имени не считается (раздел 24). - «Создать заявку» из письма (
POST /api/v1/mail/threads/{uuid}/lead-create):lead(заявка целиком) приходит только ключу с областьюleads:read, иначеnull, как уPOST /api/v1/leads(раздел 24). - Импорт: при
manager_mode: userполеmanagerобязательно (пустая строка: без ответственного); в состоянииpreview_rows,default_managerиparams.manager_mode; шаг отвечаетbusy: true, пока CRM обрабатывает другие импорты; файл XLSX с паролем получает отказ «Файл защищён паролем»; начатый импорт ждёт продолжения двое суток (раздел 21). - Звонки: запись по ссылке, которая после скачивания не поместилась в хранилище, к звонку по-прежнему не прикрепляется, но теперь администраторы компании с подключённым Telegram получают уведомление «Хранилище компании заполнено» (раздел 17).
- Чаты:
can_delete_at_client: falseу отключённого канала и непринятого токена; телефон с+8(+84...) вPOST /api/v1/chats/{uuid}/phoneостаётся международным, а не становится+7(раздел 23). - Компания (раздел 25): исключение без
transfer_toвыбирает получателя заявок по правилу ухода из компании, исключение владельца:403(раньше400); ответы исключения и ухода несутdefault_manager_changedиdefault_manager; у администратора и владельцаchats.modeв правахall;PATCH /api/v1/permissionsво время другой правки прав:409 CONFLICT, ничего не записано; флаги настроек канала принимают и"true","false". Обращение в поддержку принимает заголовокIdempotency-Keyи любой допустимый ключ (раздел 6),Retry-Afterу429равен остатку часового окна. - ИИ-ассистенты (раздел 26): данные посетителя (
visitors:read) ассистенту не отдаются при любом ключе, в том числе IP в тексте событий;send_emailпишет на адрес, который оставил клиент или ввёл сотрудник; повторная привязка уже привязанной переписки не пишет событие в ленту.
Документация приведена к коду (поведение прежнее):
- Поиск
searchидёт и по полюclient_needs, и по телефону и email объединённых заявок (раздел 9). past_leadsв ленте: только прошлые заявки с перепиской в ящиках сотрудника, полеmail_threads_total(раздел 12).- Почта сотрудника без ящиков: пустые списки вместо
404; тело письма: текст до 256 КБ, HTML больше 2 МБ не хранится (state: too_big), состояниеno_space(раздел 15). - Удаление писем (
mail:remove) переносит их и в «Корзину» почтового ящика (разделы 1 и 24). - Подключение ящика: когда серверы обязательны, присоединение к уже подключённому адресу через
400 CONFIRM_REQUIREDсerror.join_confirm; ответы201и200у «Создать заявку» из письма (раздел 24). - Импорт без права «Менять ответственного»: ответственный всех заявок сам сотрудник при любом
manager_mode(раздел 21). 403 QUOTA_EXCEEDEDу голосового (раздел 23),429 QUEUE_FULLу старого/api/call.record,401у заблокированной компании и отключённой учётной записи (раздел 4).- MCP и OAuth: только
POSTу/api/v1/mcpи/api/v1/oauth/*, метаданные наGET(раздел 26); пустая строка вmanagerуcreate_lead(раздел 8); кнопка «Отметить все прочитанными» (разделы 16 и 23). - Событие «создана» у заявки, созданной звонком, пишется без автора, как события звонков (раздел 1);
recording: "queued"значит только, что ссылка принята в очередь (раздел 17). emailс ошибкой в формате даёт400 VALIDATION_ERROR, заявка не создаётся (раздел 8);sinceв ленте отбирает события по времени создания (раздел 12); повтор удаления комментария описан и в разделе 6.- Компания и поддержка (раздел 25): обращение в поддержку, его ключ, состояния
sending,delayed,sent,failed, пределы темы и текста, ответы422,409и503.
15.10.2026: исправления по аудиту API.
- Список с
updated_sinceне отдаёт правки последних 30 секунд (раздел 9). Раньше заявка, чья запись завершалась позже более свежей правки другой заявки, могла навсегда выпасть из синхронизации поupdated_since. Отметкаupdated_atпоследней полученной заявки по-прежнему годится для следующего опроса, если запись заявки завершается быстрее 30 секунд (обычный случай); безupdated_sinceсписок прежний. - Предпросмотр объединения (
dry_run: true, раздел 11, иmerge_leadsу ИИ-ассистента) без областиvisitors:readбольше не показывает вpreview.fillIP и браузер посетителя. PATCH /api/v1/integrations/{uuid}/settingsу виджета проверяет поля формы виджета до записи (API компании): ошибка в них (400 VALIDATION_ERROR) больше не оставляет сохранёнными поля формы «Канал» из той же правки.POST /api/v1/api-keysс сотрудником не из компании:404 NOT_FOUNDс текстом «Сотрудник не найден в компании» (без «Обновите страницу.»).- В ленте заявки (
GET /api/v1/leads/{номер}/eventsиget_lead_eventsу ИИ-ассистента) номер другой заявки вprevious_lead_number(событиеcreated) иmoved_to_lead_number(событиеchat) приходитnull, если сотрудник ключа эту заявку не видит (раздел 12). Раньше номер приходил всегда, и сотрудник с видимостью «только свои» узнавал номер заявки коллеги, который карточка прячет. Ключ компании видит номера как раньше. - Если скачивание файла (вложение заявки, запись звонка, файл чата, фото сотрудника) обрывается на стороне хранилища, ответ об ошибке больше не несёт заголовки
Content-LengthиContent-Rangeсамого файла. Раньше клиент получал короткий ответ об ошибке с длиной файла и ждал недостающие байты до обрыва соединения. - Ответ на письмо без
toиcc(mode: "reply"или"reply_all", раздел 15, иsend_emailу ИИ-ассистента): имя отправителя длиннее 100 символов в получателях по умолчанию обрезается до 100 символов. Раньше такой ответ получал400, хотя имя никто не набирал. - Уход из компании (
POST /api/v1/company/leave) безtransfer_to: если и менеджер по умолчанию, и владелец недоступны (учётная запись отключена), открытые заявки остаются без ответственного (раздел 25). Раньше уход в таком случае отклонялся с ошибкой «Получатель заявок не состоит в компании». - Запись разговора ссылкой (раздел 17): формат CRM определяет по содержимому файла, а не по расширению в ссылке, поэтому ссылка без расширения подходит. Ответ не с аудиофайлом (страница ошибки или входа, JSON) считается временной ошибкой скачивания и повторяется, после 5 неудачных попыток запись к звонку не прикрепляется. Раньше такая страница прикреплялась к звонку как запись.
- Документация уточнена по коду: поиск
searchнаходит телефон в любом формате записи (раздел 9); в строке получателей письма имя с запятой или адресом пишется в кавычках (раздел 15); поляprevious_lead_numberиmoved_to_lead_numberв ленте (раздел 12); ключи самого уходящего или исключённого сотрудника перестают действовать при любомrevoke_keys(раздел 1, API компании). - Документация уточнена по коду: справочник
GET /api/v1/managersключу компании недоступен, ответственного ключ компании указывает email сотрудника (раздел 8); мастер импорта в CRM открывается кнопкой «Импорт» (раздел 21); у отказа управления ящиком признакpermission: mail.manage, текст зависит от действия (раздел 24).
14.10.2026: старые адреса без ключа отключены, права сотрудника распространены на почту и телефонию.
- Старые адреса
/api/lead.add,/api/leads,/api/lead.call,/api/call.recordбольше не принимают запросы без ключа:401 UNAUTHORIZEDс текстом, который ведёт на v1 (раздел 20). Прежний ключ компании (X-API-KeyилиAuthorization: Bearer) там работает как раньше, ключ сотрудника получает403 KEY_NOT_ALLOWED. Если ваша форма или АТС слали запросы на эти адреса без ключа, переведите их наPOST /api/v1/leadsиPOST /api/v1/callsс ключом. - Новый текст
401на адресах v1 называет оба пути выпуска ключа: администратор в настройках компании или сотрудник сам себе в профиле (раздел 2). mail:readиmail:sendстали областями «только себе» (раздел 1): на ключ другого сотрудника не выпускаются (403 PERMISSION_DENIED,permission: "keys.self_only"), а у ключей, выпущенных так раньше, не действуют (403 SCOPE_REQUIRED, вGET /api/v1/meих нет). Текст отказа выпуска теперь с группой области: «Область «Почта: Чтение» сотрудник выпускает только себе в профиле».- Телефония по ключу сотрудника действует с его правами (раздел 17): звонок в невидимой ему заявке отвечает
lead_number: nullиhidden: true, запись к такому звонку не принимается (recording_error.code: PERMISSION_DENIED, файл:404 CALL_NOT_FOUND), без права «Создавать заявки» звонок, который создал бы заявку, получает403 PERMISSION_DENIED(leads.create), заявку из звонка при видимости «Только свои» получает сам сотрудник, при «Все заявки компании» менеджер по умолчанию (без него владелец) независимо от права «Менять ответственного». Ключи компании работают как раньше. - Заявка через
POST /api/v1/leadsпо ключу сотрудника теперь присылает уведомление «Новая заявка» и самому сотруднику ключа по его настройкам (раздел 8). - Ответственный заявки через
POST /api/v1/leadsпо ключу сотрудника зависит от того, какие заявки видит сотрудник (раздел 8): при «Только свои» всегда сам сотрудник, полеmanagerне учитывается; при «Все заявки компании» безmanagerменеджер по умолчанию (раньше без права «Менять ответственного» сам сотрудник), сmanagerуказанный сотрудник, другой сотрудник только с правом «Менять ответственного» (без права403, как раньше). Пустая строка вmanagerу ключа сотрудника больше не означает «без ответственного». У ИИ-ассистента (create_lead) правила прежние. - Поле
managerпринимает email сотрудника вместоuuidвPOST /api/v1/leadsиPATCH /api/v1/leads/{номер}; ключ компании теперь тоже понимаетmanagerпри создании (раздел 8). - Менеджер по умолчанию не назначен: новые заявки (API, формы сайтов, звонки, почта, импорт, чаты) получает владелец компании, а не остаются без ответственного.
GET /api/v1/companyпо-прежнему показывает вdefault_managerсаму настройку (null, если не назначен). - Без области
visitors:readиз текста событийduplicate_requestв ленте убрана строка с IP посетителя (раздел 12). - Чаты при видимости «только свои»:
phoneчата без своего телефона клиента берётся из заявки только видимой сотруднику, иначеnull; поискqпо#номеруи телефону заявки находит только чаты видимых заявок; в карточке посетителяsourcenullу невидимой заявки,mail.opt_outучитывает только отписки в этой компании (разделы 16 и 23). - Квота отправки писем (100 в час с адреса ящика) и
quotaящика считают только письма этой компании (раздел 15). - Документация уточнена по коду, поведение не менялось: общий предел тела 110 МБ для файлов одного запроса (раздел 5), форма ответа
mergeсdry_run(раздел 11), поведениеupdated_since,archivedиstatus(раздел 9),viewсleadиcontact, повтор письма в течение суток (раздел 15),error.reasonуDUPLICATE_FOUND(разделы 4 и 24), неизвестные поля иmultipart/form-dataтолько уPOST(раздел 3), лимит изменений (раздел 5),ym_client_idдлиннее 64 символов (раздел 8), пример прослойки АТС (раздел 19).
09.10.2026: API покрывает всё, что сотрудник делает в кабинете. Прежние адреса, их запросы, ответы и ошибки не изменились; новые поля в прежних ответах только добавлены.
- Новые области выдаются только по выбору при выпуске: прежние ключи их не получают.
- Прежние области открывают и новые адреса своего раздела: например,
leads:writeотмечает спам,chats:writeотправляет стикеры и голосовые,mail:sendотвязывает переписку от заявки. Данные посетителя, участники и настройки ящиков, компания и профиль требуют новых областей. - Области управления компанией и профилем сотрудник выпускает только себе.
- Лента заявки без фильтра types теперь может содержать события типа action и пометку via: mcp. Если ваш код проверяет тип события по закрытому списку, добавьте их.
- Новые адреса: сводка и кандидаты объединения заявок, спам, импорт, расшифровка звонков (разделы 9, 11, 21, 22), чаты и почта (разделы 23 и 24), компания, сотрудники, интеграции и профиль (API компании), MCP (раздел 26).
- Новые поля:
author.avatar_url,past_leadsиunbound_mail_countв ленте;totalсписка сwith_total=1;ip_addressиuser_agentзаявки (только сvisitors:read); поля чата и сообщения (раздел 16);lead_prefillпереписки почты; состояние ящика вGET /mail/accounts(serversтолько сmail:manage);leadsиmessage.threadв ответе отправки письма;badgeу отметки чата прочитанным;chatв ответе на сообщение в чат. - Новые коды ошибок:
CONFIRM_REQUIRED,DUPLICATE_FOUND,FEATURE_DISABLED,QUEUE_FULL(раздел 4).