# Polygram API Документация внешнего API Polygram. Базовый адрес: https://api.polygram.chat/api. Ключ доступа передаётся в заголовке X-Api-Key. # Начало работы Через API ваша CRM, сайт или скрипт пишет клиентам, ставит теги, назначает чаты и запускает workflow в Polygram. Для ИИ-ассистента нажмите **Копировать для LLM** или дайте ему ссылку https://polygram.chat/docs/llms.txt. ## Базовый адрес ``` https://api.polygram.chat/api ``` Пути в статьях даны от него. Например, `POST /public/contacts/tag` отправляйте на `https://api.polygram.chat/api/public/contacts/tag`. Тело запроса передавайте в JSON с заголовком `Content-Type: application/json`. Без заголовка запрос получит ошибку проверки полей. ## Ключ доступа Ключ может создать сотрудник с правом на настройку интеграций. По умолчанию оно есть у ролей Владелец и Директор. 1. Откройте Настройки, раздел Ключи доступа. 2. Нажмите **Ключ**. 3. Введите название до 100 символов, например Сайт компании. 4. Нажмите **Создать**. 5. Скопируйте ключ. Второй раз Polygram его не покажет. Ключ начинается с `ak_`, за ним идут 48 шестнадцатеричных символов. В списке ключей видны первые 10 символов, дата создания, автор и время последнего использования. Если ключ потерялся, отзовите его кнопкой с корзиной и создайте новый. Отозванный ключ перестаёт работать со следующего запроса. Ключ открывает все методы API для данных своей компании. ### Как передать ключ Подходит любой из двух заголовков: ``` X-Api-Key: ak_... ``` ``` Authorization: Bearer ak_... ``` Если переданы оба, используется `X-Api-Key`. ## Ответы Успешный ответ приходит в JSON. Код зависит от метода: | Код | Методы | |---|---| | 200 | [Написать клиенту](https://polygram.chat/docs/api/contacts-message), [Назначить чаты](https://polygram.chat/docs/api/contacts-assign), [Найти клиентов](https://polygram.chat/docs/api/contacts-lookup), [Выгрузка по чатам](https://polygram.chat/docs/api/analytics-chats) | | 201 | [Поставить тег](https://polygram.chat/docs/api/contacts-tag), [Уведомить сотрудников](https://polygram.chat/docs/api/notifications), [Задачи и выплаты](https://polygram.chat/docs/api/tasks) | | 202 | [Запустить workflow](https://polygram.chat/docs/api/workflows-trigger) | Успешным считайте любой код 2xx. ## Ошибки В теле ошибки всегда есть `statusCode` и `message`: ```json { "statusCode": 404, "message": "Контакт с crm_accounts.id = 1042 не найден", "error": "Not Found" } ``` Если поля не прошли проверку, приходит код 400 и список полей в `errors`. В `field` указан путь к полю через точку: ```json { "statusCode": 400, "message": "Validation failed", "errors": [ { "field": "match.field", "message": "Только буквы, цифры, _ . -" }, { "field": "text", "message": "Invalid input: expected string, received undefined" } ] } ``` Текст в `message` написан для человека и может меняться. В программе ориентируйтесь на код ответа. | Код | Когда | |---|---| | 400 | Поля не прошли проверку | | 401 | Нет ключа (`API key is required`), ключ неверный или отозван (`Invalid API key`) | | 403 | Только у [уведомлений](https://polygram.chat/docs/api/notifications#oshibki): среди получателей нет ни одного сотрудника компании | | 404 | Не найден объект запроса: контакт, workflow, категория тегов | | 409 | Запрос нельзя выполнить в текущем состоянии: у клиента нет переписки, workflow выключен | | 422 | Запрос корректный, но выполнить его нельзя: окно 24 часа закрыто, сотрудник отключён | | 500 | Ошибка на стороне Polygram: `{"statusCode": 500, "message": "Internal server error"}` | Ключ проверяется раньше полей, поэтому запрос без ключа получит 401, даже если тело неверное. ## Лимиты Число запросов не ограничено. Ограничения на размер полей указаны в описании каждого метода. [Выгрузка по чатам](https://polygram.chat/docs/api/analytics-chats) 30 секунд отдаёт на одинаковый запрос тот же ответ из памяти, поэтому опрашивайте её не чаще. ## Повторы Повторяйте запрос при ответе 500 и выше, при обрыве соединения и таймауте. При кодах 400–499 сначала исправьте запрос, иначе ответ будет тем же. Можно ли повторять запрос, зависит от метода: | Метод | Повтор после таймаута | |---|---| | [Написать клиенту](https://polygram.chat/docs/api/contacts-message) | Безопасен с тем же `idempotencyKey`. Без ключа клиент получит сообщение дважды | | [Запустить workflow](https://polygram.chat/docs/api/workflows-trigger) | Безопасен с тем же `request_id`. Без него workflow запустится ещё раз | | [Задачи](https://polygram.chat/docs/api/tasks) | Безопасен с тем же `external_id`. Без него появится вторая задача | | [Поставить тег](https://polygram.chat/docs/api/contacts-tag), [Назначить чаты](https://polygram.chat/docs/api/contacts-assign) | Безопасен: повтор с теми же данными ничего не меняет | | [Найти клиентов](https://polygram.chat/docs/api/contacts-lookup), [Выгрузка по чатам](https://polygram.chat/docs/api/analytics-chats), [Оклад](https://polygram.chat/docs/api/tasks#ustanovit-oklad) | Безопасен | | [Уведомить сотрудников](https://polygram.chat/docs/api/notifications) | Сотрудник получит уведомление дважды | ## Поиск контакта по полю Методы [Написать клиенту](https://polygram.chat/docs/api/contacts-message), [Поставить тег](https://polygram.chat/docs/api/contacts-tag), [Назначить чаты](https://polygram.chat/docs/api/contacts-assign) и [Найти клиентов](https://polygram.chat/docs/api/contacts-lookup) ищут клиента по полю карточки, которое заполняет ваша система. ```json "match": { "field": "crm_id", "value": "1042" } ``` | Поле | Тип | Ограничения | |---|---|---| | `field` | строка | 1–64 символа: латинские буквы, цифры, `_`, `.`, `-` | | `value` | строка или число | Число превращается в строку. Пробелы по краям обрезаются. На пустое значение придёт ошибка 400 | Ключ поля указан рядом с его названием: откройте Настройки, раздел Поля контакта. Ключи пишутся строчными буквами. `CRM_ID` не найдёт поле `crm_id`. ### Обычное поле Если в `field` нет точки, это ключ поля карточки, например `crm_id`. Контакт подходит, если значение поля как строка совпадает с `value`. Поле, сохранённое числом `1042`, найдётся и по `"1042"`, и по `1042`. ### Колонка поля-списка Если в `field` есть точка, это колонка поля типа Список, например `crm_accounts.id`. До первой точки стоит ключ поля, после неё ключ колонки. Контакт подходит, если колонка равна `value` хотя бы в одной записи списка. Так можно найти клиента, у которого в вашей системе несколько аккаунтов. ```json "match": { "field": "crm_accounts.id", "value": "1042" } ``` Сохраняйте колонку строкой. Если `id` записан числом `1042`, методы Написать клиенту, Поставить тег и Назначить чаты такую запись не найдут. Найти клиентов найдёт. ### Несколько контактов с одним значением Если значение есть у нескольких контактов: - Написать клиенту выбирает самую свежую переписку среди всех найденных контактов; - Поставить тег ставит тег каждому; - Назначить чаты назначает все диалоги всех найденных контактов; - Найти клиентов берёт данные контакта, который изменялся последним, и дополняет недостающее из остальных. ### Если контакт не найден | Метод | Ответ | |---|---| | [Написать клиенту](https://polygram.chat/docs/api/contacts-message) | 404 | | [Поставить тег](https://polygram.chat/docs/api/contacts-tag) | 201, `"matched": 0` | | [Назначить чаты](https://polygram.chat/docs/api/contacts-assign) | 200, `"found": false` | | [Найти клиентов](https://polygram.chat/docs/api/contacts-lookup) | 200, значения нет в `found` и `contacts` | | [Запустить workflow](https://polygram.chat/docs/api/workflows-trigger) | 404. Этот метод ищет контакт по `contact.id`, `external_id` или телефону | # Написать клиенту Отправляет текстовое сообщение клиенту в его самую свежую переписку. Клиента метод ищет по полю карточки. ``` POST /public/contacts/message ``` ## Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `match.field` | строка | да | Ключ поля или колонка списка, 1–64 символа: латиница, цифры, `_ . -`. См. [поиск по полю](https://polygram.chat/docs/api/vvedenie#poisk-kontakta-po-polyu) | | `match.value` | строка или число | да | Значение поля. Пробелы по краям обрезаются. На пустое значение придёт ошибка | | `text` | строка | да | Текст сообщения, 1–4000 символов после обрезки пробелов по краям | | `source` | строка | нет | Подпись источника для события в ленте, 1–60 символов. По умолчанию `Внешняя система` | | `idempotencyKey` | строка | нет | Ключ повтора, 1–100 символов. С тем же ключом сообщение второй раз не отправится | ```json { "match": { "field": "crm_accounts.id", "value": "1042" }, "text": "Ваш заказ готов, можно забирать.", "source": "CRM", "idempotencyKey": "order-1042-ready" } ``` ## Куда уходит сообщение Polygram берёт переписки всех найденных контактов и выбирает самую свежую по времени последнего сообщения из тех, куда можно писать: - канал переписки подключён и работает; - в WhatsApp и Instagram клиент писал в последние 24 часа. Так требует Meta. Метод отправляет только текст. Шаблоны WhatsApp, файлы и кнопки отправляйте другим способом. ## Ответ `200 OK`. Ответ приходит, когда сообщение ушло в канал, но не позже чем через 20 секунд. ```json { "sent": true, "status": "SENT", "error": null, "duplicate": false, "conversationId": "cmg1x2y3z0001ab12cd34ef56", "messageId": "cmg1x2y3z0002ab12cd34ef56", "channel": "WhatsApp: Отдел продаж" } ``` | Поле | Описание | |---|---| | `sent` | `true`, если сообщение ушло в канал | | `status` | `SENT`, `DELIVERED`, `READ`: сообщение ушло. `PENDING`: отправка не закончилась за 20 секунд. `FAILED`: канал вернул ошибку | | `error` | Причина при `sent: false`: текст ошибки канала или `Отправка ещё идёт` | | `duplicate` | `true`, если сообщение с этим `idempotencyKey` уже было и новое не отправлялось. Остальные поля тогда описывают первое сообщение | | `conversationId` | Диалог, в который ушло сообщение | | `messageId` | Сообщение в Polygram | | `channel` | Тип и название канала: `WhatsApp: …`, `Telegram: …`, `Instagram: …` | При `sent: false` код ответа тоже 200. Сообщение уже создано и стоит в очереди. Polygram делает до трёх попыток с растущей паузой, поэтому ответ с `FAILED` приходит только через 20 секунд. Чтобы узнать результат после `PENDING`, повторите запрос с тем же `idempotencyKey`. Придёт текущий статус, нового сообщения не будет. Чтобы отправить заново после `FAILED`, возьмите новый ключ. ## Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку | | 401 | Нет ключа доступа или он неверный | | 404 | Нет контакта с таким значением поля | | 409 | У контакта нет ни одной переписки | | 422 | Все каналы клиента отключены, или все его переписки в WhatsApp и Instagram вне окна 24 часа | Случай 422 видно по тексту ошибки: `Канал клиента отключён` или `Клиент не писал больше суток…`. ## Что происходит в Polygram - Сообщение сразу появляется в переписке у сотрудников, без автора-сотрудника. - В ленту диалога пишется событие Сообщение через внешнюю систему. - Диалог поднимается в списке. - Отметка о прочтении и назначение диалога не меняются. - Запущенные в диалоге workflow продолжают работать: сообщение не считается ответом оператора. - Пуш-уведомления сотрудникам не отправляются. Повтор с тем же `idempotencyKey` возвращает первое сообщение, даже если в повторе другой `text`. ## Пример ```bash curl -X POST https://api.polygram.chat/api/public/contacts/message \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "match": { "field": "crm_accounts.id", "value": "1042" }, "text": "Ваш заказ готов, можно забирать.", "source": "CRM", "idempotencyKey": "order-1042-ready" }' ``` Запрос может идти до 20 секунд, поэтому таймаут клиента ставьте больше. # Поставить тег Ставит найденным по полю контактам тег из категории и снимает с них остальные теги этой категории. ``` POST /public/contacts/tag ``` Используйте метод для статусов заказа. У контакта в категории остаётся один тег. Чтобы снять тег или изменить теги в нескольких категориях за один запрос, используйте [Поставить и снять теги](https://polygram.chat/docs/api/contacts-tags). ## Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `match.field` | строка | да | Ключ поля или колонка списка, 1–64 символа: латиница, цифры, `_ . -`. См. [поиск по полю](https://polygram.chat/docs/api/vvedenie#poisk-kontakta-po-polyu) | | `match.value` | строка или число | да | Значение поля. Пробелы по краям обрезаются. На пустое значение придёт ошибка | | `category` | строка | да | Название категории тегов, 1–100 символов, без учёта регистра | | `tag` | строка | да | Название тега, 1–100 символов, без учёта регистра | | `source` | строка | нет | Подпись источника для события в ленте, 1–60 символов. По умолчанию `Внешняя система` | | `final` | логическое | нет | Только для колонки списка: статус итоговый, без движения. См. [несколько аккаунтов](#neskolko-akkauntov-u-klienta) | ```json { "match": { "field": "crm_id", "value": "1042" }, "category": "Статус заказа", "tag": "Отправлен", "source": "CRM" } ``` Сначала создайте категорию: откройте Настройки, раздел Теги. Если тега в категории нет, Polygram создаст его серым, даже когда контакт не найден. Чтобы сотрудники не меняли статус вручную, включите у категории **Ставит только внешняя система**. Сотрудники будут видеть теги, но не смогут их поставить или снять. ## Ответ `201 Created`. ```json { "matched": 1, "changed": 1, "tag": "Отправлен" } ``` | Поле | Описание | |---|---| | `matched` | Сколько контактов нашлось по полю | | `changed` | У скольких контактов изменились теги категории | | `tag` | Название тега, как оно записано в категории | Если контакт не найден, придёт `matched: 0` без ошибки. ## Несколько аккаунтов у клиента Если `match.field` указывает на колонку списка (`crm_accounts.id`), статус относится к одной записи списка. Тег в категории у контакта при этом один. 1. Статус и время сохраняются в запись списка, где колонка равна `match.value`. 2. Контакт получает самый поздний статус среди записей с движением. 3. Статус с `final: true`, например Доставлен, уступает статусам с движением. Если у всех записей статусы итоговые, ставится самый поздний из них. Поэтому тег контакта может отличаться от `tag` в запросе. Например, у аккаунта `1042` статус В работе, а для аккаунта `1043` приходит Доставлен с `final: true`. У контакта остаётся тег В работе, а статус записи `1043` сохраняется. Для обычного поля `final` ни на что не влияет. Если сотрудник удалит запись списка в карточке, тег пересчитается по оставшимся записям. Это работает только в категориях, где включено **Ставит только внешняя система**. ## Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку | | 401 | Нет ключа доступа или он неверный | | 404 | Нет категории тегов с таким названием | ## Что происходит в Polygram Для каждого контакта, у которого изменились теги категории: - в ленту всех диалогов контакта пишется событие о том, что тег изменился, добавлен или снят; - запускаются workflow с запуском **Поставили тег** на поставленный тег и **Сняли тег** на снятые. Workflow запускается в открытом диалоге контакта с последней активностью, а если открытых нет, то в последнем диалоге. Если у контакта нет диалогов, workflow не запускаются; - обновляются счётчики в списке чатов. Если нужный тег уже стоит и других тегов категории нет, ничего не меняется: события в ленте не появляются, workflow не запускаются. Поэтому запрос можно безопасно повторять. Пуш-уведомления сотрудникам не отправляются. ## Пример ```bash curl -X POST https://api.polygram.chat/api/public/contacts/tag \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "match": { "field": "crm_accounts.id", "value": "1042" }, "category": "Статус заказа", "tag": "Доставлен", "final": true, "source": "CRM" }' ``` # Поставить и снять теги Ставит и снимает теги контактам, найденным по полю. За один вызов можно изменить теги в нескольких категориях. ``` POST /public/contacts/tags ``` Метод работает только с категориями и тегами, которые уже есть в Polygram. Их список отдаёт [Категории тегов](https://polygram.chat/docs/api/tag-categories). Чем отличается от [Поставить тег](https://polygram.chat/docs/api/contacts-tag): - можно снимать теги, а не только ставить; - за один запрос меняются теги из разных категорий; - новые теги не создаются: незнакомое название вернёт ошибку; - записи списка не меняются, `final` нет. ## Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `match.field` | строка | да | Ключ поля или колонка списка, 1–64 символа: латиница, цифры, `_ . -`. См. [поиск по полю](https://polygram.chat/docs/api/vvedenie#poisk-kontakta-po-polyu) | | `match.value` | строка или число | да | Значение поля. Пробелы по краям обрезаются. На пустое значение придёт ошибка | | `add` | массив | нет | Теги, которые нужно поставить, до 50 штук | | `remove` | массив | нет | Теги, которые нужно снять, до 50 штук | | `add[].category`, `remove[].category` | строка | да | Название категории, 1–100 символов, без учёта регистра | | `add[].tag`, `remove[].tag` | строка | да | Название тега в этой категории, 1–100 символов, без учёта регистра | | `source` | строка | нет | Подпись источника для события в ленте, 1–60 символов. По умолчанию `Внешняя система` | Нужен хотя бы один тег в `add` или в `remove`. ```json { "match": { "field": "crm_id", "value": "1042" }, "add": [ { "category": "Статус аккаунта", "tag": "Активен" }, { "category": "Интересы", "tag": "Опт" } ], "remove": [ { "category": "Интересы", "tag": "Розница" } ], "source": "CRM" } ``` ## Как меняются теги - Тег из `add` ставится, тег из `remove` снимается. Остальные теги контакта не меняются. - В категории, где можно выбрать только один тег, поставленный тег заменяет прежний: остальные теги этой категории снимаются сами. - Если тег уже стоит или уже снят, с ним ничего не происходит. Поэтому запрос можно безопасно повторять. - Метод меняет теги и в категориях с **Ставит только внешняя система**. ## Ответ `200 OK`. ```json { "matched": 1, "changed": 1 } ``` | Поле | Описание | |---|---| | `matched` | Сколько контактов нашлось по полю | | `changed` | У скольких контактов изменились теги | Если контакт не найден, придёт `matched: 0` без ошибки. ## Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку или нет ни `add`, ни `remove` | | 401 | Нет ключа доступа или он неверный | | 422 | Нет категории или тега с таким названием. В ответе перечислено всё, что не нашлось | | 422 | Один и тот же тег есть и в `add`, и в `remove` | | 422 | В `add` два тега из категории, где можно выбрать только один | При ошибке 422 теги не меняются ни у одного контакта. ## Что происходит в Polygram Для каждого контакта, у которого изменились теги: - в ленту всех диалогов контакта пишется событие о том, какие теги добавлены и сняты; - запускаются workflow с запуском **Поставили тег** на каждый поставленный тег и **Сняли тег** на каждый снятый. Workflow запускается в открытом диалоге контакта с последней активностью, а если открытых нет, то в последнем диалоге. Если у контакта нет диалогов, workflow не запускаются; - обновляются счётчики в списке чатов. Пуш-уведомления сотрудникам не отправляются. ## Пример Снять тег: ```bash curl -X POST https://api.polygram.chat/api/public/contacts/tags \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "match": { "field": "crm_id", "value": "1042" }, "remove": [{ "category": "Интересы", "tag": "Розница" }], "source": "CRM" }' ``` # Категории тегов Отдаёт категории тегов компании и теги в каждой из них. ``` GET /public/contacts/tag-categories ``` Используйте метод, чтобы узнать точные названия категорий и тегов для [Поставить и снять теги](https://polygram.chat/docs/api/contacts-tags). ## Ответ `200 OK`. Категории идут в том же порядке, что в настройках. ```json { "categories": [ { "name": "Статус аккаунта", "selection": "SINGLE", "managed_externally": true, "tags": [ { "name": "Активен", "color": "#34C759" }, { "name": "Заблокирован", "color": "#FF3B30" } ] }, { "name": "Интересы", "selection": "MULTI", "managed_externally": false, "tags": [ { "name": "Опт", "color": "#007AFF" }, { "name": "Розница", "color": "#8E8E93" } ] } ] } ``` | Поле | Описание | |---|---| | `name` | Название категории | | `selection` | `SINGLE`: у контакта один тег из категории. `MULTI`: несколько | | `managed_externally` | `true`: теги ставит только внешняя система, сотрудники их не меняют | | `tags[].name` | Название тега | | `tags[].color` | Цвет тега в формате `#RRGGBB` | ## Ошибки | Код | Когда | |---|---| | 401 | Нет ключа доступа или он неверный | ## Пример ```bash curl https://api.polygram.chat/api/public/contacts/tag-categories \ -H "X-Api-Key: ak_..." ``` # Назначить чаты Назначает все диалоги найденного по полю контакта на сотрудника или снимает назначение. ``` POST /public/contacts/assign ``` Метод назначает диалоги во всех каналах контакта, включая закрытые. Когда клиент напишет снова, диалог уже будет у этого сотрудника. ## Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `match.field` | строка | да | Ключ поля или колонка списка, 1–64 символа: латиница, цифры, `_ . -`. См. [поиск по полю](https://polygram.chat/docs/api/vvedenie#poisk-kontakta-po-polyu) | | `match.value` | строка или число | да | Значение поля. Пробелы по краям обрезаются. На пустое значение придёт ошибка | | `assigneeId` | строка или `null` | да | ID сотрудника Polygram, 1–64 символа. `null` снимает назначение | | `previousAssigneeId` | строка или `null` | нет | ID прежнего сотрудника, 1–64 символа. Нужен только при `assigneeId: null` | | `notify` | логическое | нет | Отправить сотруднику пуш Вам назначен диалог. По умолчанию `true` | Чтобы узнать ID сотрудника, откройте Настройки, раздел Пользователи, и нажмите **Скопировать ID** под его именем. ```json { "match": { "field": "crm_id", "value": "1042" }, "assigneeId": "cmf8k2l3m0000xy12ab34cd56" } ``` ## Назначить С `assigneeId` назначаются все диалоги контакта, которые сейчас не на этом сотруднике: и свободные, и чужие. `previousAssigneeId` в этом случае не учитывается. ## Снять назначение `assigneeId: null` снимает назначение только с диалогов, которые сейчас на `previousAssigneeId`. Диалоги других сотрудников не меняются. ```json { "match": { "field": "crm_id", "value": "1042" }, "assigneeId": null, "previousAssigneeId": "cmf8k2l3m0000xy12ab34cd56" } ``` Без `previousAssigneeId` назначение не снимется, придёт `"updated": 0`. ## Ответ `200 OK`. ```json { "found": true, "updated": 2 } ``` | Поле | Описание | |---|---| | `found` | `true`, если по полю нашёлся хотя бы один контакт | | `updated` | Сколько диалогов изменилось. Диалоги, где менять было нечего, не считаются | Если контакт не найден, придёт `{"found": false, "updated": 0}` без ошибки. Если контакт найден, но диалогов у него нет, придёт `{"found": true, "updated": 0}`. ## Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку | | 401 | Нет ключа доступа или он неверный | | 422 | Сотрудника `assigneeId` нет в компании, или он отключён | Сотрудник проверяется, только если контакт найден. Если контакта нет, придёт `found: false` даже при неверном `assigneeId`. ## Что происходит в Polygram Для каждого изменённого диалога: - в ленту пишется событие о назначении сотрудника или о снятии назначения; - открытые экраны сотрудников сразу показывают изменение; - при назначении, если `notify` не `false`, сотрудник получает пуш Назначен диалог, по одному на каждый диалог. Пуш учитывает настройки уведомлений сотрудника (Настройки, раздел Уведомления): выключенные пуши и тихие часы. Статус диалога (открыт или закрыт) не меняется, workflow не запускаются. Повтор с тем же сотрудником ничего не меняет и пушей не отправляет. ## Пример ```bash curl -X POST https://api.polygram.chat/api/public/contacts/assign \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "match": { "field": "crm_accounts.id", "value": "1042" }, "assigneeId": "cmf8k2l3m0000xy12ab34cd56", "notify": false }' ``` # Найти клиентов Проверяет, для каких значений поля в Polygram есть контакт, и возвращает ник Telegram и телефон клиента. ``` POST /public/contacts/lookup ``` Так ваша система покажет, кто из пользователей уже переписывался с компанией и как с ним связаться. ## Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `field` | строка | да | Ключ поля или колонка списка, 1–64 символа: латиница, цифры, `_ . -`. См. [поиск по полю](https://polygram.chat/docs/api/vvedenie#poisk-kontakta-po-polyu) | | `values` | массив строк или чисел | да | Значения поля, не больше 1000 элементов. Числа превращаются в строки, пробелы по краям обрезаются, пустые значения и повторы отбрасываются | Лимит в 1000 элементов проверяется до удаления повторов. ```json { "field": "crm_accounts.id", "values": ["1042", "1043", 1044] } ``` Этот метод находит колонку списка, даже если она сохранена числом. ## Ответ `200 OK`. ```json { "found": ["1042", "1044"], "contacts": [ { "value": "1042", "username": "@ivan_petrov", "phone": "+77000000000" }, { "value": "1044", "username": null, "phone": "+77000000001" } ] } ``` | Поле | Описание | |---|---| | `found` | Значения, для которых нашёлся контакт, в произвольном порядке | | `contacts[].value` | Значение поля | | `contacts[].username` | Ник Telegram в виде `@nick` или `null` | | `contacts[].phone` | Телефон в виде `+` и цифр или `null` | Значения без контакта в ответ не попадают. На пустой `values` придут пустые массивы. ### Откуда берутся ник и телефон - Ник берётся из полей карточки `username` или `telegram`, ссылка `t.me/nick` превращается в `@nick`. Если поля пустые, ник берётся из имени диалога Telegram, когда оно начинается с `@`. - Телефон берётся из полей карточки `phone` или `whatsapp`, иначе из номера переписки WhatsApp. Номер `8…` из 11 цифр превращается в `+7…`. Значение, где меньше 10 или больше 15 цифр, телефоном не считается. Если значение есть у нескольких контактов, данные берутся у контакта, который изменялся последним, а недостающее дополняется из остальных. ## Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку, в `values` больше 1000 элементов | | 401 | Нет ключа доступа или он неверный | ## Что происходит в Polygram Метод только читает данные. ## Пример ```bash curl -X POST https://api.polygram.chat/api/public/contacts/lookup \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "field": "crm_accounts.id", "values": ["1042", "1043"] }' ``` # Запустить workflow Ставит в очередь запуск workflow в диалоге контакта. У workflow должен быть выбран запуск **Запрос извне**. ``` POST /public/workflows/{id}/trigger ``` В `{id}` передайте ID workflow. Готовый адрес можно взять в самом workflow: откройте узел запуска, выберите **Запрос извне** и скопируйте адрес из блока **Адрес для внешней системы**. Workflow должен быть опубликован и включён. ## Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `contact.id` | строка | одно из трёх | ID контакта в Polygram | | `contact.external_id` | строка | одно из трёх | Идентификатор клиента в канале: номер WhatsApp, ID в Telegram или Instagram | | `contact.phone` | строка | одно из трёх | Телефон, 5–32 символа | | `data` | объект | нет | Любые данные. В workflow доступны как `$trigger.data.<поле>` | | `request_id` | строка | нет | Ключ повтора, 1–200 символов. Второй запрос с тем же ключом workflow не запустит | Передайте хотя бы один признак контакта. Если их несколько, используется первый по порядку: `id`, затем `external_id`, затем `phone`. ```json { "contact": { "phone": "+7 700 000 00 00" }, "data": { "order_id": "1042", "amount": 15000 }, "request_id": "payment-1042" } ``` Искать контакт по полю карточки (`match`) этот метод не умеет. ### Как ищется контакт - По `id` ищется точное совпадение. - По `external_id` ищется точное совпадение с идентификатором клиента в любом канале. - Из `phone` берутся только цифры. Подойдёт номер переписки WhatsApp из этих цифр (с `+` или без) и поле карточки `phone`, равное `+цифры`, `цифры` или строке из запроса как есть. Если номер в карточке записан с пробелами, он найдётся, только когда в запросе он записан так же. ## Ответ `202 Accepted`. ```json { "accepted": true, "contact_id": "cmf9a1b2c0000de34fg56hi78", "conversation_id": "cmf9a1b2c0001de34fg56hi78" } ``` | Поле | Описание | |---|---| | `accepted` | Запуск поставлен в очередь | | `contact_id` | Найденный контакт. Его можно передавать в `contact.id` в следующих запросах | | `conversation_id` | Диалог, в котором запустится workflow | Workflow запускается в открытом диалоге контакта с последней активностью. Если открытых диалогов нет, берётся последний. Код 202 подтверждает только, что запуск принят. Запуск будет пропущен без ошибки, если: - `request_id` уже запускал этот workflow; - у workflow включено **Один раз на контакт**, и для контакта он уже выполнялся; - в workflow выбраны роли в блоке **Для кого**, а диалог ведёт сотрудник другой роли или диалог ни на кого не назначен; - workflow выключили или сняли с публикации между запросом и запуском. ## Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку или не передан ни один признак контакта | | 401 | Ключ доступа не передан или неверный | | 404 | Workflow с таким ID не найден или не найден контакт | | 409 | У workflow выбран другой запуск, не **Запрос извне**; workflow не опубликован или выключен; по `external_id` или `phone` нашлось несколько контактов, в этом случае передайте `contact.id` | | 422 | У контакта нет ни одного диалога | ## Что происходит в Polygram - Workflow выполняет свои шаги в выбранном диалоге: отправляет сообщения, ставит теги, назначает диалог. - В ленте диалога появляются события запуска и завершения workflow. - Без `request_id` каждый запрос запускает workflow заново, даже с теми же данными. - С `request_id` запрос можно повторять: ключ запоминается для этого workflow навсегда. ## Пример ```bash curl -X POST https://api.polygram.chat/api/public/workflows/cmf7z8y9x0000ab12cd34ef56/trigger \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "contact": { "external_id": "77000000000" }, "data": { "order_id": "1042" }, "request_id": "order-1042-paid" }' ``` # Уведомить сотрудников Отправляет сотрудникам уведомление: запись в списке уведомлений Polygram и пуш на их устройства. В уведомлении только получатели и текст. ``` POST /public/notifications ``` ## Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `user_ids` | массив строк | да | ID сотрудников, от 1 до 100. Повторы отбрасываются | | `title` | строка | нет | Заголовок, до 120 символов. Если его нет, в пуше будет заголовок Уведомление | | `body` | строка | да | Текст, 1–2000 символов | | `link` | строка | нет | Куда ведёт нажатие на уведомление, до 500 символов | Чтобы узнать ID сотрудника, откройте Настройки, раздел Пользователи, и нажмите **Скопировать ID** под именем сотрудника. Чтобы уведомление открывало диалог, передайте `link` вида `/inbox?conversation=`. ID диалога возвращают методы [Написать клиенту](https://polygram.chat/docs/api/contacts-message) и [Запустить workflow](https://polygram.chat/docs/api/workflows-trigger). В вебе нажатие открывает любой `link`, в мобильном приложении только ссылку на диалог. ```json { "user_ids": ["cmf8k2l3m0000xy12ab34cd56"], "title": "Оплата получена", "body": "Клиент оплатил заказ 1042" } ``` ## Ответ `201 Created`. ```json { "sent": 1, "skipped": 0 } ``` | Поле | Описание | |---|---| | `sent` | Скольким сотрудникам создано уведомление | | `skipped` | Сколько ID отброшено, потому что сотрудник отключён или не из этой компании | ## Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку | | 401 | Ключ доступа не передан или неверный | | 403 | Ни один ID не подошёл: все сотрудники отключены или не из этой компании | ## Что происходит в Polygram - Уведомление появляется в списке уведомлений сотрудника с подписью Внешняя система. На открытых экранах оно появляется сразу. - Пуш уходит на устройства сотрудника, если он не выключил пуши и у него сейчас не тихие часы. - Ответ приходит раньше, чем доставлен пуш. Если пуш не дошёл, уведомление всё равно будет в списке. Каждый повтор запроса создаёт новое уведомление. ## Пример ```bash curl -X POST https://api.polygram.chat/api/public/notifications \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "user_ids": ["cmf8k2l3m0000xy12ab34cd56"], "title": "Оплата получена", "body": "Клиент оплатил заказ 1042", "link": "/inbox?conversation=cmg1x2y3z0001ab12cd34ef56" }' ``` # Задачи и выплаты Два метода передают в Polygram данные о сотрудниках из вашей системы: задачи для раздела Задачи и оклад для профиля сотрудника. Чтобы узнать ID сотрудника, откройте Настройки, раздел Пользователи, и нажмите **Скопировать ID** под именем сотрудника. ## Создать задачу Создаёт задачу или обновляет задачу с тем же `external_id`. ``` POST /public/tasks ``` ### Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `external_id` | строка | нет | ID задачи в вашей системе, 1–200 символов. Запрос с тем же ID обновляет задачу | | `assignee_user_id` | строка или `null` | нет | ID сотрудника-исполнителя. Если не передать, задача будет общей и её увидят все сотрудники | | `title` | строка | да | Заголовок, 1–200 символов | | `body` | строка или `null` | нет | Описание, до 4000 символов | | `due_at` | строка или `null` | нет | Срок в ISO 8601 с часовым поясом: `2026-10-20T18:00:00+05:00` или `2026-10-20T13:00:00Z` | | `link` | строка или `null` | нет | Ссылка, до 500 символов. В задаче она открывается кнопкой **Открыть** | | `task_type` | строка | нет | `GENERIC` (по умолчанию) или `EDITING`, регистр не важен. По задаче `EDITING` приходит уведомление о просрочке | ```json { "external_id": "task-1042", "assignee_user_id": "cmf8k2l3m0000xy12ab34cd56", "title": "Связаться с клиентом по заказу 1042", "due_at": "2026-10-20T18:00:00+05:00", "task_type": "EDITING" } ``` ### Ответ `201 Created`. ```json { "id": "cmga1b2c30000xy12ab34cd56" } ``` В `id` приходит ID задачи в Polygram. При обновлении по `external_id` возвращается ID той же задачи. ### Обновление по external_id Запрос с тем же `external_id` перезаписывает все поля задачи. Поля, которых нет в запросе, очищаются, а `task_type` становится `GENERIC`. Поэтому передавайте задачу целиком. Статус при обновлении не меняется. Выполненная задача остаётся выполненной. Без `external_id` каждый запрос создаёт новую задачу. ### Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку или `assignee_user_id` не сотрудник этой компании | | 401 | Ключ доступа не передан или неверный | ### Что видят сотрудники - Открытые задачи показываются в разделе Задачи над напоминаниями, в вебе и в мобильном приложении. Сотрудник видит свои задачи и общие. - Сотрудник отмечает задачу кнопкой **Выполнено** и возвращает кнопкой **В работу**. Выполненная задача пропадает из списка. Ваша система об этом не узнаёт. - Просроченный срок выделяется красным. - При создании задачи уведомление не приходит. Задачи `EDITING` проверяются раз в 5 минут. Когда срок открытой задачи прошёл, исполнитель и руководители получают уведомление Просрочка по редактуре с заголовком задачи. Руководителями считаются владелец и роли с правом настраивать рабочие часы. Уведомление приходит один раз. Если перенести срок в будущее запросом с тем же `external_id`, при новой просрочке оно придёт снова. ## Установить оклад Записывает оклад сотрудника. Оклад видит только сам сотрудник в профиле мобильного приложения, в блоке **Мой оклад**. ``` POST /public/salary ``` ### Тело запроса | Поле | Тип | Обязательное | Описание | |---|---|---|---| | `user_id` | строка | да | ID сотрудника | | `amount` | целое число или `null` | да | Сумма, 0 и больше. `null` очищает оклад вместе с валютой и подписью | | `currency` | строка или `null` | нет | Валюта, 1–8 символов, например `KZT` | | `note` | строка или `null` | нет | Подпись, до 120 символов, например Октябрь 2026 | ```json { "user_id": "cmf8k2l3m0000xy12ab34cd56", "amount": 350000, "currency": "KZT", "note": "Октябрь 2026" } ``` Каждый запрос заменяет оклад целиком: если не передать `currency` и `note`, они очистятся. ### Ответ `201 Created`. ```json { "success": true } ``` ### Ошибки | Код | Когда | |---|---| | 400 | Поля не прошли проверку или `user_id` не сотрудник этой компании | | 401 | Ключ доступа не передан или неверный | Запрос с теми же данными можно повторять. Сотруднику уведомление не приходит. ## Пример ```bash curl -X POST https://api.polygram.chat/api/public/tasks \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "external_id": "task-1042", "title": "Связаться с клиентом по заказу 1042", "due_at": "2026-10-20T18:00:00+05:00" }' ``` ```bash curl -X POST https://api.polygram.chat/api/public/salary \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "user_id": "cmf8k2l3m0000xy12ab34cd56", "amount": 350000, "currency": "KZT" }' ``` # Выгрузка по чатам Возвращает статистику по дням: новые диалоги и сообщения по каждому каналу, число ответов и скорость ответа по каждому сотруднику. ``` GET /public/analytics/chats ``` ## Параметры запроса | Параметр | Тип | Обязательный | Описание | |---|---|---|---| | `from` | строка | да | Первый день, `YYYY-MM-DD` | | `to` | строка | да | Последний день включительно, `YYYY-MM-DD`. Не раньше `from`, период не больше 62 дней | | `tz` | строка | нет | Часовой пояс IANA, по которому считаются дни и часы. По умолчанию `Asia/Almaty` | | `dayStartHour` | число | нет | Час начала суток, 0–23. По умолчанию 0. При `6` день длится с 06:00 до 06:00 следующего дня | | `channelIds` | строка | нет | Только эти каналы: `?channelIds=a&channelIds=b` или `?channelIds=a,b`. Если не передать, вернутся все каналы компании | Почасовые массивы всегда идут по часам на часах, 0–23, даже если задан `dayStartHour`. ## Ответ `200 OK`. ```json { "tz": "Asia/Almaty", "dayStartHour": 0, "generatedAt": "2026-10-17T09:30:00.000Z", "days": [ { "date": "2026-10-16", "channels": [ { "channelId": "cmf1a2b3c0000xy12ab34cd56", "type": "WHATSAPP", "name": "Отдел продаж", "newContacts": 12, "newContactsByHour": [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 1, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0], "inbound": 148, "outboundHuman": 131, "outboundOther": 9, "unansweredNew": 1, "newContactsBought": 3, "newContactsBoughtByHour": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] } ], "operators": [ { "userId": "cmf8k2l3m0000xy12ab34cd56", "name": "Иван Петров", "answers": 42, "medianMin": 3.5, "avgMinUpTo2h": 7.2, "p90Min": 28.4, "pctWithin5Min": 61.9, "messagesByHour": [0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 15, 9, 6, 11, 14, 10, 9, 7, 0, 0, 0, 0, 0] } ] } ] } ``` В `days` есть каждый день периода, даже без данных. В каждом дне есть все каналы компании или все каналы из `channelIds`. ### Канал за день | Поле | Описание | |---|---| | `channelId`, `type`, `name` | Канал. `type` принимает значения `WHATSAPP`, `INSTAGRAM` или `TELEGRAM` | | `newContacts` | Сколько диалогов создано в канале за день | | `newContactsByHour` | То же по часам, 24 числа | | `inbound` | Входящие сообщения | | `outboundHuman` | Исходящие от сотрудников | | `outboundOther` | Исходящие без сотрудника: workflow, автоответы, [сообщения через API](https://polygram.chat/docs/api/contacts-message) | | `unansweredNew` | Сколько новых диалогов дня на момент запроса не получили ни одного исходящего, в том числе автоматического | | `newContactsBought` | У скольких контактов из новых диалогов дня сейчас стоит тег Купил из категории Статус | | `newContactsBoughtByHour` | То же по часу создания диалога | Удалённые и системные сообщения не считаются. ### Сотрудник за день | Поле | Описание | |---|---| | `userId`, `name` | Сотрудник | | `answers` | Сколько раз сотрудник ответил клиенту | | `medianMin` | Медиана времени ответа, минуты | | `avgMinUpTo2h` | Среднее время ответа без ответов дольше 2 часов, минуты | | `p90Min` | 90-й перцентиль времени ответа, минуты | | `pctWithin5Min` | Доля ответов за 5 минут и быстрее, % | | `messagesByHour` | Исходящие сообщения сотрудника по часам, 24 числа | Дробные числа округлены до десятых. Если ответов не было, в полях времени приходит `null`. Сотрудники отсортированы по убыванию `answers`. ### Как считается ответ Обращением считается входящее сообщение, перед которым в диалоге было исходящее или не было ничего. Несколько входящих подряд считаются одним обращением, и время отсчитывается от первого. Ответом считается первое исходящее сообщение сотрудника после обращения. Сообщения workflow и автоответы ответом не считаются. Ответ засчитывается сотруднику, который ответил, и дню, когда написал клиент. Если клиент написал вчера, а сотрудник ответил сегодня, ответ попадёт во вчерашний день. Обращения без ответа в статистику сотрудников не входят. ## Ошибки | Код | Когда | |---|---| | 400 | Неверный формат даты, неизвестный часовой пояс, `dayStartHour` вне 0–23, `to` раньше `from`, период длиннее 62 дней | | 401 | Ключ доступа не передан или неверный | ## Особенности - Одинаковый запрос в течение 30 секунд отдаётся из памяти, поэтому данные могут отставать на это время. - Чужие и несуществующие ID в `channelIds` пропускаются без ошибки. ## Пример ```bash curl "https://api.polygram.chat/api/public/analytics/chats?from=2026-10-01&to=2026-10-16&tz=Asia/Almaty&dayStartHour=6" \ -H "X-Api-Key: ak_..." ``` # Запросы к вашему API Workflow может сам вызывать API вашей CRM. Шаг **Внешний вызов** отправляет запрос, а выпадающий список в форме **Запросить данные** берёт варианты из вашего API. ## Подключение Адрес и ключ вашего API хранятся в подключении: откройте Настройки, раздел Подключения к API. Шаг workflow ссылается на подключение и указывает только путь, поэтому ключ в workflow не попадает. | Поле | Что задаёт | |---|---| | Базовый адрес | Полный адрес с `http://` или `https://`, например `https://crm.example.com/api/v1` | | Авторизация | Один из способов ниже | | Заголовки | До 20 заголовков, которые уходят с каждым запросом | | Способ | Что приходит на ваш сервер | |---|---| | Без авторизации | Ничего | | Bearer-токен | `Authorization: Bearer <токен>` | | Логин и пароль (Basic) | `Authorization: Basic ` | | Ключ в заголовке | `<имя заголовка>: <ключ>`, имя задаётся в подключении | Ключ хранится в зашифрованном виде. При редактировании подключения поле ключа пустое. Путь шага добавляется к базовому адресу. Если путь выходит за базовый адрес (`../`, `//host`, полный адрес с другим доменом), запрос не уходит. Адрес с логином и паролем (`https://user:pass@…`) тоже отклоняется. Шаг можно настроить и без подключения, с полным адресом. Тогда авторизацию задайте заголовком в самом шаге. ## Запрос ### Метод и адрес Методы: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. У `GET` и `DELETE` тела нет. В пути и в поле **Параметры адреса** можно использовать переменные: `/accounts/$contact.custom.crm_id`. Значения кодируются для адреса. Если у переменной нет значения, в адресе остаётся её имя, например `$contact.custom.crm_id`. ### Заголовки | Заголовок | Когда есть | |---|---| | Заголовки подключения | Всегда | | Заголовки шага | Если заданы в шаге. Значения могут содержать переменные. Одноимённый заголовок шага заменяет заголовок подключения | | Авторизация | Если у подключения есть ключ. Заменяет одноимённый заголовок из настроек | | `Idempotency-Key` | Всегда, если шаг не задал свой заголовок с этим именем. Значение описано в разделе [Повторы и Idempotency-Key](#povtory-i-idempotency-key) | | `Accept: application/json, text/plain, */*` | Если шаг не задал свой `Accept` | | `Content-Type` | Если есть тело и шаг не задал свой. Значение зависит от вида тела | | `Content-Length` | Если есть тело | Polygram не задаёт `User-Agent`. Чтобы CRM знала, какой сотрудник запустил workflow, добавьте в шаг заголовок со значением `$me.id`, например `X-Snippex-User-Id: $me.id`. Доступны также `$me.name` и `$me.email`. Если workflow запущен без сотрудника, например входящим сообщением, в заголовке останется текст `$me.id`. Заголовок, в значении которого есть перевод строки, отклоняется до отправки. ### Тело | Вид тела в шаге | `Content-Type` | Что приходит | |---|---|---| | Поля | `application/json` | JSON-объект из полей, см. ниже | | Вручную (JSON) | `application/json` | JSON, который написал автор workflow. Значения переменных экранируются для JSON | | Форма | `application/x-www-form-urlencoded` | `a=1&b=2`, значения кодируются для адреса | | Текст | `text/plain; charset=utf-8` | Текст как есть | | Без тела | нет | Тела нет | Вид **Поля** собирает JSON из пар имя и значение: - имя через точку создаёт вложенный объект: из `client.phone` получится `{"client": {"phone": "…"}}`; - тип поля определяет JSON-тип значения. | Тип | JSON | Правило | |---|---|---| | Текст | строка | Значение как есть | | Число | число | Пробелы убираются, запятая считается точкой: из `1 500,5` получится `1500.5`. Пустое значение даёт `null`. Если значение не число, запрос не уходит | | Да/нет | `true` / `false` | `true`, `1`, `да`, `yes` дают `true`. `false`, `0`, `нет`, `no` и пустое значение дают `false`. С другим значением запрос не уходит | | Как есть | любой | Значение разбирается как JSON. Если значение состоит из одной переменной (`$vars.order`), уходит её значение, и объект остаётся объектом | Переменная без значения даёт в поле `null`. Пример. Поля шага: | Имя | Значение | Тип | |---|---|---| | `login` | `$vars.login` | Текст | | `client.name` | `$contact.name` | Текст | | `client.phone` | `$contact.phone` | Текст | | `tariff_id` | `$vars.tariff` | Число | | `trial` | `да` | Да/нет | Тело запроса: ```json { "login": "ivan.petrov", "client": { "name": "Иван Петров", "phone": "+7 700 000 00 00" }, "tariff_id": 3, "trial": true } ``` ## Таймауты и размер ответа - Polygram ждёт ответа 15 секунд. Автор workflow может поставить от 1 до 60 секунд в поле **Ждать ответа, сек** в разделе **Надёжность**. - Варианты для выпадающего списка Polygram ждёт 10 секунд и не повторяет запрос. - Ответ больше 1 МБ Polygram обрывает и считает ошибкой. Такой запрос не повторяется. ## Повторы и Idempotency-Key Polygram повторяет запрос, если: - ответа нет из-за сетевой ошибки или вышло время ожидания; - пришёл ответ `429`; - пришёл ответ `500` или выше. Остальные коды, в том числе `4xx` и `3xx`, не повторяются. По умолчанию Polygram делает 2 повтора: всего до 3 запросов. Автор workflow может поставить от 0 до 3 в поле **Повторов при сбое** в разделе **Надёжность**. Перед повторами Polygram ждёт 1, 3 и 9 секунд. Если в ответе `429` или `5xx` есть заголовок `Retry-After` в секундах, пауза берётся из него, но не больше 10 секунд. Дату в `Retry-After` Polygram не учитывает. Если время ожидания вышло, ваш сервер мог уже обработать запрос. Повтор придёт с тем же `Idempotency-Key`. Проверяйте этот заголовок и на повтор возвращайте результат первого запроса, тогда запись не создастся дважды. | Откуда запрос | `Idempotency-Key` | |---|---| | Шаг с включённым **Выполнять один раз** | Ключ операции после подстановки переменных, например `crm_account:9b1c…` | | Обычный шаг | `:`. Ключ тот же, если упавший запуск повторили из журнала | | Кнопка **Пробный запрос** в редакторе | `test:<время в миллисекундах>`, каждый раз новый | | Варианты выпадающего списка | `form-options::<ключ поля>` | У однократной операции ключ не меняется и после ошибки: следующий запуск придёт с тем же `Idempotency-Key`. Если вы запоминаете ответы по этому ключу, не запоминайте ответы с ошибкой, иначе повтор получит ту же ошибку. **Пробный запрос** в редакторе workflow отправляет на ваш сервер настоящий запрос, один раз и без повторов. В карточку клиента он ничего не пишет. ## Редиректы Polygram не переходит по редиректам и считает ответ `3xx` ошибкой шага. Указывайте в подключении адрес, который отвечает сразу. ## Адреса внутренней сети Polygram отправляет запрос только на публичный адрес. Перед отправкой он узнаёт IP домена и не отправляет запрос, если это: - `localhost`; - адреса `10.0.0.0/8`, `127.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `100.64.0.0/10`, `0.0.0.0/8`, `192.0.0.0/16`, `198.18.0.0/15`, `224.0.0.0` и выше; - IPv6 `::`, `::1`, адреса на `fc` и `fd`, адреса от `fe8` до `fef`. Соединение идёт на тот IP, который Polygram проверил. Такой запрос не повторяется. Поддерживаются только `http` и `https`. ## Успех и ошибка Успехом считается ответ с кодом `2xx`. Всё остальное считается ошибкой: другой код, отсутствие ответа, запрос, который не удалось собрать. Тело ответа разбирается как JSON, если в `Content-Type` есть `json` или тело начинается с `{` или `[`. Иначе оно остаётся текстом. Что Polygram сохраняет из ответа, описано в статье [Ответы: что Polygram сохраняет](https://polygram.chat/docs/api/otvety). У шага три выхода: | Выход | Когда | |---|---| | Успех | Ответ `2xx` | | Ошибка | Ответ не `2xx` или ответа нет | | Уже выполнено | Однократная операция уже была, запрос не отправлялся | Если к выходу **Ошибка** ничего не подключено, workflow на ошибке останавливается. ## Как отдать ошибку, чтобы её понял сотрудник При ошибке Polygram пишет в переменную `$vars.http_error` текст: ``` Внешний сервис ответил <код>: <тело ответа> ``` Тело берётся как есть, первые 500 символов, JSON не разбирается. Например, ответ `{"error":"Логин занят"}` даст текст `Внешний сервис ответил 409: {"error":"Логин занят"}`. Если ответа нет, в переменной будет описание сбоя, например `Нет ответа за 15 с`. Сотрудник видит этот текст: - в уведомлении, если автор workflow вставил `$vars.http_error` в шаг **Уведомить сотрудника** на выходе **Ошибка**; - в ленте диалога, если выход **Ошибка** не подключён и workflow остановился. В ленте будет `Workflow «…» не выполнен — <текст>`, первые 300 символов; - в окне шага **Показать сотруднику**, если workflow остановился до него. Поэтому на ошибку отвечайте кодом `4xx` и короткой понятной сотруднику фразой на русском, например с `Content-Type: text/plain`: ``` HTTP/1.1 409 Conflict Content-Type: text/plain; charset=utf-8 Логин ivan.petrov уже занят ``` Код последнего ответа хранится в `$vars.http_status`, по нему workflow может ветвиться. После успешного ответа `$vars.http_error` пуст. ## Однократные операции Шаг с включённым **Выполнять один раз** отправляет запрос один раз на каждый ключ операции. Ключ задаётся шаблоном с переменными, например `crm_account:$contact.id`, и для одного клиента он одинаковый во всех запусках. - Перед запросом Polygram отмечает операцию как начатую. Если второй запуск с тем же ключом начнётся, пока первый не закончился, он не отправит запрос и пойдёт по выходу **Ошибка**. - Выполненной операция считается только после успеха. После ошибки следующий запуск отправит запрос снова, с тем же `Idempotency-Key`. - Если операция уже выполнена, запрос не уходит, и workflow идёт по выходу **Уже выполнено**, а если он не подключён, по выходу **Успех**. Переменные `$vars`, сохранённые из первого ответа, восстанавливаются. - Если ключ не собрался из-за переменной без значения, запрос не уходит, и workflow идёт по выходу **Ошибка**. Сотрудник сбрасывает отметку в карточке клиента, раздел **Внешние операции**. После сброса workflow выполнит операцию заново. Если ваш сервер принял запрос, а ответ до Polygram не дошёл, операция остаётся начатой. Следующий запуск получит ошибку `Операция с этим ключом уже выполняется…`. Проверьте результат у себя и попросите сотрудника сбросить операцию. ## Журнал Каждый запрос и ответ записываются в журнал запуска workflow. Журнал видят сотрудники с доступом к workflow. Значения заголовков `Authorization`, `Cookie`, `X-…-Key`, `X-…-Token` заменяются на `***`. Тело запроса в журнале обрезается до 2000 символов, тело ответа до 4000. # Ответы вашего API Шаг **Внешний вызов** сохраняет значения из ответа вашего API по правилам в разделе **Сохранить из ответа**. Правила срабатывают только при ответе `2xx`. Как Polygram разбирает тело и что считает ошибкой, описано в статье [Запросы Polygram к вашему API](https://polygram.chat/docs/api/vash-api#uspeh-i-oshibka). ## Путь в ответе | Путь | Что берётся | |---|---| | `data.account.id` | Значение по ключам через точку | | `items.0.id` | Элемент массива по номеру, с нуля | | `@status` | Код ответа, например `201` | | пусто | Весь ответ: объект, массив или текст | Если по пути ничего нет, или там `null`, или пустая строка, правило пропускается и прежнее значение не затирается. Пример ответа: ```json { "data": { "account": { "id": 1042, "username": "ivan.petrov" } }, "items": [{ "id": "A-7" }] } ``` | Путь | Значение | |---|---| | `data.account.id` | `1042` | | `data.account.username` | `"ivan.petrov"` | | `items.0.id` | `"A-7"` | | `data.account.email` | правило пропускается | ## Куда сохраняется ### В переменную Значение попадает в `$vars.<ключ>` и доступно следующим шагам этого запуска: в теле следующего запроса, в условиях, в тексте уведомления. В карточку клиента оно не записывается. ### В поле контакта Значение записывается в поле карточки клиента `<ключ>` и доступно как `$contact.custom.<ключ>` в этом и следующих workflow. Объект и массив сохраняются как есть. Если у правила есть название, а такого поля в настройках ещё нет, Polygram создаст поле с этим названием: числовое, если пришло число, иначе текстовое. Существующее поле Polygram не меняет. ### В список контакта Поле-список хранит у клиента несколько записей, например несколько аккаунтов в вашей CRM. Правило указывает поле (`crm_accounts`) и колонку (`id`). Правила с одним и тем же полем собираются в одну запись: | Путь в ответе | Куда | Поле | Колонка | |---|---|---|---| | `data.account.id` | в список контакта | `crm_accounts` | `id` | | `data.account.username` | в список контакта | `crm_accounts` | `username` | Ответ из примера выше добавит клиенту запись: ```json { "id": "1042", "username": "ivan.petrov" } ``` - В список пишутся только строки, числа и да/нет, и все они сохраняются строкой: из `1042` получится `"1042"`. Объект, массив и `null` пропускаются. - Если поля `crm_accounts` ещё нет, Polygram создаст поле-список. Колонки берутся из правил по порядку, первая колонка (`id`) становится уникальной. - Если у клиента уже есть запись с таким же значением уникальной колонки, Polygram её обновит. Иначе добавит запись в конец списка. - Если запись с таким значением уникальной колонки есть у другого клиента, Polygram её не сохранит, и шаг пойдёт по выходу **Ошибка** с текстом `«1042» уже привязан к другому контакту (Иван Петров)`. - Если поле с этим ключом есть, но оно не список, шаг пойдёт по выходу **Ошибка** с текстом `Поле контакта «…» — не список, записи в него не добавить`. Обе ошибки возникают после ответа вашего API. Если у шага включено **Выполнять один раз**, операция считается выполненной, и следующий запуск пойдёт по выходу **Уже выполнено**. Записи списка видны в карточке клиента. По колонке списка клиента можно найти во входящем API: `"match": { "field": "crm_accounts.id", "value": "1042" }`, см. [Поставить тег](https://polygram.chat/docs/api/contacts-tag). ### Однократные операции Если операция уже выполнена и запрос не отправляется, восстанавливаются только значения, сохранённые в переменную из первого ответа. Поля и списки клиента записаны ещё в первый раз. ## Показ сотруднику Шаг **Показать сотруднику** открывает окно с данными из переменной запуска. Чтобы показать ответ вашего API целиком, в шаге **Внешний вызов** сохраните пустой путь в переменную, например `crm_info`, и выберите `$vars.crm_info` в шаге **Показать сотруднику**. Окно открывается у сотрудника, который нажал кнопку workflow. Если workflow запустил не сотрудник, окно получит ответственный за диалог. Окно работает в веб-версии и в мобильном приложении. ### Готовая карточка Если ответ совпадает с форматом карточки, окно строится точно по нему: ```json { "title": "Аккаунт ivan.petrov", "url": "https://crm.example.com/accounts/1042", "sections": [ { "title": "Аккаунт", "items": [ { "label": "ID", "value": "1042" }, { "label": "Логин", "value": "ivan.petrov" }, { "label": "Заказ", "value": "№ 5531", "href": "https://crm.example.com/orders/5531" } ] }, { "title": "Последние заказы", "table": { "columns": ["Номер", "Статус", "Сумма"], "rows": [ ["5531", "Отправлено", "12 400"], ["5480", "Доставлено", "8 900"] ] } } ] } ``` | Поле | Тип | Обязательное | Что делает | |---|---|---|---| | `title` | string | нет | Подзаголовок окна, рядом с именем клиента | | `url` | string | нет | Ссылка внизу окна. Только `http://` и `https://` | | `sections` | array | да | Разделы окна, по порядку | | `sections[].title` | string | нет | Заголовок раздела | | `sections[].items` | array | нет | Строки с подписью и значением | | `items[].label` | string | да | Подпись | | `items[].value` | string | да | Значение. Строка с пустым значением не показывается | | `items[].href` | string | нет | Делает значение ссылкой. Только `http://` и `https://` | | `sections[].table` | object | нет | Таблица: в `columns` заголовки, в `rows` строки из ячеек | Все значения, включая ячейки таблицы, должны быть строками. Если хотя бы одно поле другого типа (например, `"value": 1042`), ответ не считается карточкой и раскладывается по общим правилам ниже. Заголовок окна задаётся в шаге **Показать сотруднику**. Если он пуст, берётся подпись кнопки или название workflow. Значение без ссылки сотрудник копирует нажатием, в мобильном приложении долгим нажатием. ### Любой другой ответ Ответ другого вида Polygram раскладывает так: - простые поля верхнего уровня идут первым разделом, по строке на ключ; - вложенный объект становится отдельным разделом с ключом в заголовке, у более глубоких уровней подписи через точку: `address.city`; - массив объектов становится таблицей, колонками служат ключи объектов; - массив простых значений выводится одной строкой через запятую; - `true` и `false` показываются как да и нет; - `null` и пустые строки не показываются; - текстовый ответ выводится одной строкой с подписью Ответ. Подписями служат ключи JSON как есть. Чтобы сотрудник видел русские подписи, отдавайте ответ в формате карточки. ### Ограничения | Что | Предел | |---|---| | Разделов | 30 | | Строк в разделе | 100 | | Строк таблицы | 200 | | Колонок таблицы | 20 | | Длина текста | 1000 символов, дальше обрезается | Если разделов нет, в окне написано `Данных нет`. Если workflow остановился до показа, в окне будет текст ошибки, см. [Как отдать ошибку](https://polygram.chat/docs/api/vash-api#kak-otdat-oshibku-chtoby-ee-ponyal-sotrudnik). # Варианты для форм Выпадающий список в форме **Запросить данные** может брать варианты из вашего API: тарифы, промокоды, шаблоны. Polygram запрашивает список, когда сотрудник открывает форму. ## Настройка в workflow Автор workflow включает у поля **Выбор из списка** переключатель **Варианты из подключения** и заполняет: | Поле | Пример | Что задаёт | |---|---|---| | Подключение | ваша CRM | Базовый адрес и ключ, см. [Подключение](https://polygram.chat/docs/api/vash-api#podklyuchenie) | | Адрес списка | `/tariffs` | Путь внутри базового адреса | | Где массив в ответе (`itemsPath`) | `items` | Путь к массиву. Если пусто, массивом считается весь ответ | | Подпись (`labelPath`) | `name` | Текст, который видит сотрудник | | Значение (`valuePath`) | `id` | Значение, которое уходит в workflow | | Заголовки | `X-Snippex-User-Id: $me.id` | До 10 заголовков, значения могут содержать переменные | ## Запрос ``` GET https://crm.example.com/api/v1/tariffs Authorization: Bearer <ключ подключения> X-Snippex-User-Id: 3f2c… Idempotency-Key: form-options::tariff Accept: application/json, text/plain, */* ``` - Метод всегда `GET`, без тела. - Polygram отправляет постоянные заголовки подключения, заголовки поля, авторизацию, `Idempotency-Key` вида `form-options::<ключ поля>` и `Accept`. - В адресе и заголовках доступны данные сотрудника, который открыл форму (`$me.id`, `$me.name`, `$me.email`), и данные клиента и диалога: `$contact.id`, `$contact.custom.<поле>`, `$conversation.id`. Переменные запуска (`$vars`) здесь недоступны. - Polygram запрашивает список целиком. Фильтр передайте в адресе: `/tariffs?active=1`. - Polygram ждёт ответа 10 секунд и сам запрос не повторяет. Приложение сотрудника при ошибке запрашивает список ещё раз, так что запросов может быть два. Ограничения на адрес те же, что у шага **Внешний вызов**: только внутри базового адреса, без внутренних адресов и редиректов, см. [Запросы Polygram к вашему API](https://polygram.chat/docs/api/vash-api). ## Ответ Ответ должен быть JSON-массивом объектов или объектом, в котором массив лежит по `itemsPath`. Из каждого элемента Polygram берёт подпись по `labelPath` и значение по `valuePath`. Пути пишутся через точку, как в [ответах](https://polygram.chat/docs/api/otvety#put-v-otvete). ```json { "items": [ { "id": 3, "name": "Премиум", "price": 4900 }, { "id": 5, "name": "Базовый", "price": 1900 }, { "id": 7, "name": "" } ] } ``` При `itemsPath: items`, `labelPath: name`, `valuePath: id` сотрудник увидит варианты `Премиум`, `Базовый` и `7`. Если он выберет `Премиум`, в `$vars.<ключ поля>` попадёт `"3"`. Как разбирается ответ: - значение и подпись приводятся к строке: из `3` получится `"3"`, объект и массив дают пустую строку; - элемент без значения пропускается; - если подписи нет, показывается значение; - из элементов с одинаковым значением остаётся первый; - берутся первые 1000 вариантов; - если по `itemsPath` лежит не массив, список будет пустым, и ошибкой это не считается. Значение по умолчанию задаёт автор workflow в поле **Значение по умолчанию**. ## Кеширование Сервер Polygram хранит полученный список 60 секунд. Ключ кеша составляется из подключения и настроек поля (адрес, пути, заголовки) до подстановки переменных. Поэтому в течение минуты другой сотрудник или другой клиент с той же формой получит тот же список, даже если в адресе или заголовках есть `$me.id` или `$contact.id`. Ответы с ошибкой не кешируются. ## Ошибки Ошибкой считается код не `2xx`, отсутствие ответа за 10 секунд, ответ больше 1 МБ и запрещённый адрес. Тогда поле в форме становится текстовым, сотрудник вводит значение вручную, и оно уходит в workflow без проверки по списку. Значение из выпадающего списка при отправке формы тоже не сверяется со списком. Проверяйте его в вашем API, когда workflow его пришлёт. ## Форма без запроса Polygram не запрашивает список, если форма не показывается. Так бывает, когда у формы включено **Не показывать, если всё уже известно** и все обязательные поля уже заполнены значениями по умолчанию. ## Варианты из списка клиента Варианты можно брать и из поля-списка клиента, для этого есть источник **Варианты из списка контакта**. Например, из поля `crm_accounts`: значение из колонки `id`, подпись из `username`. Ваш API при этом не вызывается. Если у клиента одна запись, она выбирается сама. Если записей нет, а поле обязательное, workflow останавливается с ошибкой `У контакта нет записей для «…» — сначала привяжите аккаунт`. # Интеграция с CRM Сотрудник заводит клиенту аккаунт в вашей CRM прямо из чата, а CRM сообщает Polygram о заказах и менеджерах. Аккаунты клиента хранятся в поле-списке `crm_accounts` с колонками `id` (уникальная) и `username`. | Что происходит | Кто вызывает | Кого | |---|---|---| | Сотрудник создаёт аккаунт | Polygram | Вашу CRM | | Меняется статус заказа | Ваша CRM | Polygram, `POST /public/contacts/tag` | | Назначен менеджер | Ваша CRM | Polygram, `POST /public/contacts/assign` | | CRM пишет клиенту | Ваша CRM | Polygram, `POST /public/contacts/message` | | Сотрудник смотрит данные из CRM | Polygram | Вашу CRM | ## Подготовка 1. Откройте Настройки, раздел Подключения к API, и добавьте подключение: базовый адрес `https://crm.example.com/api/v1`, авторизация **Bearer-токен**, ключ, который выдала ваша CRM. 2. Создайте ключ доступа к API Polygram для вызовов из CRM, см. [Ключ доступа](https://polygram.chat/docs/api/vvedenie#klyuch-dostupa). ## Создать аккаунт из чата Workflow: 1. Запуск **Кнопка в диалоге** с названием Создать аккаунт. 2. Шаг **Запросить данные** у того, кто нажал кнопку: поле `login` с типом Текст и поле `tariff` с типом Выбор из списка и вариантами из подключения: адрес `/tariffs`, подпись `name`, значение `id`. 3. Шаг **Внешний вызов**: `POST /accounts` через подключение, тело Поля, заголовок `X-Snippex-User-Id: $me.id`. 4. **Сохранить из ответа**: `data.account.id` и `data.account.username` в список контакта `crm_accounts`, колонки `id` и `username`. 5. На выходе **Ошибка** шаг **Уведомить сотрудника** с текстом `CRM ответила: $vars.http_error`. Сотрудник нажимает **Действия** в шапке диалога, выбирает **Создать аккаунт** и заполняет форму. Когда форма открывается, Polygram запрашивает варианты для поля `tariff`: ``` GET https://crm.example.com/api/v1/tariffs Authorization: Bearer <ключ> ``` ```json [ { "id": 3, "name": "Премиум" }, { "id": 5, "name": "Базовый" } ] ``` Формат ответа описан в статье [Варианты для форм](https://polygram.chat/docs/api/varianty). Запрос на создание аккаунта: ``` POST https://crm.example.com/api/v1/accounts Authorization: Bearer <ключ> X-Snippex-User-Id: 3f2c… Idempotency-Key: : Content-Type: application/json ``` | Имя поля | Значение | Тип | |---|---|---| | `login` | `$vars.login` | Текст | | `tariff_id` | `$vars.tariff` | Число | | `client.name` | `$contact.name` | Текст | | `client.phone` | `$contact.phone` | Текст | ```json { "login": "ivan.petrov", "tariff_id": 3, "client": { "name": "Иван Петров", "phone": "+7 700 000 00 00" } } ``` Ответ CRM при успехе: ``` HTTP/1.1 201 Created Content-Type: application/json ``` ```json { "data": { "account": { "id": 1042, "username": "ivan.petrov" } } } ``` Клиенту добавится запись `{ "id": "1042", "username": "ivan.petrov" }` в поле `crm_accounts`. Второй аккаунт того же клиента добавится второй записью. Ответ CRM при ошибке: ``` HTTP/1.1 409 Conflict Content-Type: text/plain; charset=utf-8 Логин ivan.petrov уже занят ``` Сотрудник получит уведомление `CRM ответила: Внешний сервис ответил 409: Логин ivan.petrov уже занят`. На стороне CRM: - проверяйте `Idempotency-Key`: если время ожидания выйдет, Polygram повторит запрос с тем же ключом; - сохраните `X-Snippex-User-Id`, если хотите потом назначать этому сотруднику диалоги. Подробнее: [Запросы Polygram к вашему API](https://polygram.chat/docs/api/vash-api), [Ответы: что Polygram сохраняет](https://polygram.chat/docs/api/otvety). ## Статус заказа Когда в CRM меняется статус заказа, CRM ставит клиенту тег из категории статусов. Клиента Polygram ищет по колонке списка `crm_accounts.id`. ```bash curl -X POST https://api.polygram.chat/api/public/contacts/tag \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "match": { "field": "crm_accounts.id", "value": "1042" }, "category": "Статус заказа", "tag": "Отправлено", "source": "CRM", "final": true }' ``` Для статусов, после которых заказ больше не движется, например `Отправлено` и `Доставлено`, передавайте `final: true`. Если у клиента несколько аккаунтов, такой статус не перекроет текущий статус другого аккаунта. Остальные теги категории снимаются. Поля, ответ и ошибки описаны в статье [Поставить тег](https://polygram.chat/docs/api/contacts-tag). ## Менеджер Когда в CRM клиенту назначают менеджера, CRM назначает на него диалоги клиента в Polygram. В `assigneeId` передайте ID сотрудника Polygram, например полученный из `X-Snippex-User-Id`. ```bash curl -X POST https://api.polygram.chat/api/public/contacts/assign \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "match": { "field": "crm_accounts.id", "value": "1042" }, "assigneeId": "3f2c…" }' ``` `"assigneeId": null` снимает назначение. Подробнее: [Назначить чаты](https://polygram.chat/docs/api/contacts-assign). ## Сообщение клиенту CRM пишет клиенту в его последнюю переписку. ```bash curl -X POST https://api.polygram.chat/api/public/contacts/message \ -H "X-Api-Key: ak_..." \ -H "Content-Type: application/json" \ -d '{ "match": { "field": "crm_accounts.id", "value": "1042" }, "text": "Ваш заказ № 5531 отправлен", "source": "CRM", "idempotencyKey": "order-5531-shipped" }' ``` Вызов с тем же `idempotencyKey` второй раз сообщение не отправляет. Подробнее: [Написать клиенту](https://polygram.chat/docs/api/contacts-message). ## Информация из CRM Workflow: 1. Запуск **Кнопка в диалоге** с названием Информация из CRM. В **Показывать** выбрано **Если заполнено** и поле `crm_accounts`, поэтому у клиента без аккаунта кнопки нет. 2. Шаг **Запросить данные**: поле `account` с типом Выбор из списка и источником **Варианты из списка контакта**: поле `crm_accounts`, значение `id`, подпись `username`, поле обязательное. Если аккаунт у клиента один, он подставляется сам, и форма не показывается. 3. Шаг **Внешний вызов**: `GET /accounts/$vars.account/summary` через подключение. 4. **Сохранить из ответа**: пустой путь в переменную `crm_info`. 5. Шаг **Показать сотруднику** с переменной `$vars.crm_info`. Запрос: ``` GET https://crm.example.com/api/v1/accounts/1042/summary Authorization: Bearer <ключ> ``` Ответ в формате карточки: ```json { "title": "ivan.petrov", "url": "https://crm.example.com/accounts/1042", "sections": [ { "title": "Аккаунт", "items": [ { "label": "Тариф", "value": "Премиум" }, { "label": "Баланс", "value": "4 900 ₸" } ] }, { "title": "Заказы", "table": { "columns": ["Номер", "Статус"], "rows": [["5531", "Отправлено"], ["5480", "Доставлено"]] } } ] } ``` У сотрудника, который нажал кнопку, откроется окно с этими разделами. Формат карточки и правила для другого JSON описаны в статье [Ответы: что Polygram сохраняет](https://polygram.chat/docs/api/otvety#pokaz-sotrudniku).