Серверное API
Эта страница — для того, кто подключает Globask со стороны своего сервера. Она же контракт: всё, что здесь описано, менять без предупреждения нельзя.
Способов ходить друг к другу три, и они независимы:
| Часть | Кто кого зовёт | Зачем |
|---|---|---|
| Чтение | вы — нас | выгрузка переписки, архив в карточке CRM |
| Отправка | вы — нас | сообщение посетителю из вашей системы |
| Вебхуки | мы — вас | события ваших диалогов в реальном времени |
Чтение и отправка требуют ключа, вебхуки — нет: они настраиваются в кабинете и подписываются секретом.
Ключи
Ключ выпускается в кабинете: проект → вкладка «Ключи API». Ключ принадлежит проекту, а не аккаунту: если ключ утечёт, посторонний может получить доступ к переписке только одного сайта, а не всех ваших проектов. У проекта их может быть несколько — по одному на интеграцию.
У ключа три способности, и выдавать их лучше по отдельности:
| Способность | Что открывает |
|---|---|
| «Чтение» | все GET-запросы ниже |
| «Отправка» | POST сообщения в диалог |
| «Управление диалогом» | взять диалог, передать человеку, заметка, закрыть |
Третья отделена от «Отправки» не для строгости: рассылка, умеющая писать посетителям, не должна заодно уметь закрывать их диалоги.
Ключ показывается один раз — сразу после выпуска. Повторно мы его не
покажем: в базе хранится не он, а его sha256, и показывать нам просто
нечего. Потеряли — отзовите и выпустите новый.
Передаётся он заголовком, и только им:
Authorization: Bearer gsk_ваш_ключ
В адресе ключ не принимается намеренно: адреса оседают в логах прокси, в истории браузера и в чужих системах мониторинга — вместе с ключом.
Все адреса начинаются с https://globask.com/api/v1/. Версия в пути стоит с
первого дня: на вашей стороне живёт код, который мы не сможем переписать по
своей воле.
Проверить ключ:
curl -H "Authorization: Bearer gsk_…" https://globask.com/api/v1/project
{
"project": {
"public_key": "pk_…",
"name": "Мой сайт",
"timezone": "Europe/Moscow"
},
"token": {
"name": "Выгрузка в BI",
"abilities": ["read"],
"expires_at": null
}
}
Ошибки
Коды обычные, тело — {"message": "…"}, у ошибок проверки полей ещё и
errors.
| Код | Когда |
|---|---|
| 400 | тело не разобралось: оборванный JSON или не UTF-8 — и в JSON, и в форме |
| 401 | ключа нет, он неизвестен, отозван или просрочен |
| 403 | ключу не хватает способности; аккаунт приостановлен |
| 404 | записи нет или она из другого проекта — мы не различаем эти случаи вслух |
| 409 | диалог занят другой интеграцией или уже отдан людям — см. «Диалог ведёт бот» |
| 422 | параметр не подходит: неизвестный статус, чужой курсор, пустой текст, не та картинка |
| 429 | превышен лимит частоты — или запросов, или неудачных попыток предъявить ключ |
Про 400 стоит знать заранее. Тело мы разбираем сами и на битом отвечаем прямо: «не разобрано как JSON». Раньше такой запрос получал 422 «Поле body обязательно» — формально верно (полей и правда нет) и практически вредно: ответ отправляет искать ошибку в коде отправки, а не в кодировке. Самая частая причина — не UTF-8: тело собрано в CP1251 или уехало через слой, который его перекодировал.
То же самое проверяется и в форме (multipart/form-data,
x-www-form-urlencoded), которой отправляются картинки. Здесь разбор не
спотыкается вовсе — значения приходят байтами как есть, — поэтому мы
проверяем сами: значения полей, имена полей и имена загружаемых файлов.
Не-UTF-8 в любом из них даёт 400 «Поля запроса не в кодировке UTF-8».
Отдельно про имя файла: оно сохраняется вместе с вложением, и битое имя
сломало бы не эту отправку, а чтение переписки потом.
Про 429 стоит знать две вещи. Лимиты частоты: 120 запросов в минуту на чтение, 60 на отправку и 30 на управление диалогом (взять, сдать, закрыть, заметка). Считаются они на ключ, а не на проект и не на адрес: два ключа дают два бюджета. И второй источник того же кода — 30 неудачных предъявлений ключа в минуту с одного адреса: опечатка в конфиге даёт 429 «Слишком много неудачных попыток», а не 401.
Чтение
Как листать
Списки отдаются от старых записей к новым и листаются курсором:
GET /api/v1/conversations?limit=50&after=<id последней прочитанной записи>
limit— по умолчанию 50, потолок 100;next_afterв ответе — то, что нужно передать вafterза следующей страницей;nullозначает, что страница последняя;afterс неизвестным идентификатором — ошибка 422, а не «начни сначала»: иначе опечатка превратила бы ваш цикл в бесконечную выгрузку одного и того же.
Смещений (page, offset) у нас нет намеренно: пока вы листаете, приходят
новые сообщения, страницы съезжают, и записи начинают дублироваться или
теряться молча.
Порядок «от старых к новым» не меняется никогда. Если вам нужны свежие события первыми — вам нужны не списки, а вебхуки.
Диалоги
GET /api/v1/conversations
GET /api/v1/conversations/{id}
Фильтры: status (open, pending, closed), contact (идентификатор
контакта).
{
"conversations": [
{
"id": "0198f3bf-…",
"status": "open",
"led_by": "humans",
"created_at": "2026-08-25T10:15:30+03:00",
"last_message_at": "2026-08-25T10:19:02+03:00",
"contact": {
"id": "0198f3be-…",
"number": 5,
"name": null,
"email": null
},
"identity": {
"channel": "widget",
"external_id": "v_…",
"page_url": "https://shop.example/cart",
"attributes": { "uid": "7f3a…", "Тариф": "Pro" },
"created_at": "2026-08-25T10:10:00+03:00"
}
}
],
"next_after": null
}
identity — как посетитель пишет в этом диалоге. В attributes лежит то, что
ваша страница положила в window.globask.user, — такое, каким
оно пришло с последним сообщением посетителя. Атрибутов нет — пустой объект.
Эти значения задаёт браузер посетителя, и подделать их можно из консоли. Показать менеджеру тариф или номер заказа они годятся, а решать по ним, чей аккаунт перед вами, — нет. Как подписать значение на своём сервере, чтобы ему можно было верить, — в документации виджета.
Сообщения диалога
GET /api/v1/conversations/{id}/messages
{
"messages": [
{
"id": "0198f3c0-…",
"direction": "in",
"body": "Сколько стоит доставка?",
"author": { "type": "contact", "via": null, "name": null },
"content_type": "text",
"attachments": [],
"created_at": "2026-08-25T10:15:30+03:00"
}
],
"next_after": null
}
direction — in от посетителя, out к посетителю. author.type — contact,
user (менеджер), unlinked_agent, bot (автоответ или ваша собственная
отправка) и system.
content_type бывает трёх значений: text, image (у сообщения есть
вложение) и email_prompt — это наша просьба к посетителю оставить почту.
Последняя приходит обычным сообщением бота; если строите архив переписки,
решите заранее, показывать её или отбрасывать.
author.via отвечает на вопрос «это не я ли сам?». У bot два разных
источника: via: "api" — сообщение, которое отправили вы через это же API,
via: null — наш автоответ. Различать их нужно всерьёз: если вы подписаны на
message.created.out, ваша собственная отправка вернётся к вам событием, и
без via вы запишете своё же сообщение вторым. У остальных типов автора
via всегда null.
system — это внутренние заметки менеджеров. Посетитель их не видит, а вы
видите: это ваши данные, и архив, умалчивающий половину разговора, врал бы о
его содержании. Если вам нужна только переписка с посетителем — отбросьте
system у себя.
Контакты
GET /api/v1/contacts
GET /api/v1/contacts/{id}
Один контакт приходит вместе с идентичностями — способами, которыми человек с вами связывался:
{
"contact": {
"id": "0198f3be-…",
"number": 5,
"name": null,
"email": null,
"blocked": false,
"created_at": "2026-08-25T10:10:00+03:00",
"identities": [
{
"channel": "widget",
"external_id": "v_…",
"page_url": "https://shop.example/cart",
"attributes": { "uid": "7f3a…", "Тариф": "Pro" },
"created_at": "2026-08-25T10:10:00+03:00"
}
]
}
}
Атрибуты со страницы стоят в идентичности, а не в самом контакте, намеренно:
name и email мы знаем сами, а attributes — то, что сказала страница.
Вложения
Вложение приходит в сообщении объектом:
"attachments": [
{
"id": "0198f3c1-…",
"url": "https://globask.com/api/v1/attachments/{id}",
"mime": "image/png",
"size": 84213,
"width": 1280,
"height": 853
}
]
Ссылка не работает сама по себе — требует того же заголовка
Authorization и способности «Чтение», что и всё остальное. Подписанных
ссылок, живущих без ключа, у нас нет: они утекают из чужих логов и работают у
всякого, кто их подобрал.
Отправка
POST /api/v1/conversations/{id}/messages
Content-Type: application/json
{ "body": "Заказ №1024 отправлен, трек 12345.", "client_message_id": "…uuid…" }
Ответ — то же сообщение, что отдаёт чтение, плюс текущее состояние диалога. Код 201 на новое сообщение и 200 на повтор.
Что нужно знать до первой отправки:
- Сообщение помечено автоматическим. В ленте виджета оно выглядит как ответ робота, а не оператора. Выдавать вашу систему за живого человека мы не станем: тот, кто ждёт продолжения разговора, не дождётся.
client_message_id— ваш ключ идемпотентности. Он необязателен, но без него сетевой таймаут на вашей стороне обернётся вторым «заказ отправлен» у посетителя. Повтор с тем же ключом возвращает то же самое сообщение.- Закрытый диалог оживает. Уведомление приходит как раз тогда, когда разговор давно закончен, — отказ был бы отказом в главном сценарии. Вместе с диалогом откроется и тема, в которой отвечают менеджеры.
- Диалог не перестаёт считаться ждущим. Ваше сообщение отвечает на свой
повод, а не на вопрос посетителя, поэтому
last_message_atи статус открытого диалога не меняются. Иначе отправка «заказ собран» прятала бы от менеджера человека, который третий час ждёт ответа. - Менеджер видит, что вы ответили. В тему Telegram уходит строка «через API» с текстом сообщения — иначе он не поймёт, почему посетителю уже ответили, и ответит второй раз.
Завести новый диалог через API нельзя: сообщение уходит в существующий, а диалог заводит посетитель.
Картинка
Картинка отправляется тем же запросом, полем file в multipart/form-data:
curl https://globask.com/api/v1/conversations/{id}/messages \
-H "Authorization: Bearer gsk_…" \
-F "body=Фон убран." \
-F "client_message_id=…uuid…" \
-F "file=@result.webp"
- Одна картинка на сообщение. Нужно несколько — отправьте несколько сообщений.
- JPEG, PNG, WebP или GIF, до 5 МБ. Тип определяется по содержимому файла, а не по имени и заголовку. Не картинка или файл больше — код 422 с объяснением.
- Текст необязателен: без
bodyуйдёт одна картинка. Без текста и без файла — 422. - Метаданные съёмки вырезаются из JPEG до сохранения, в том числе координаты.
- Посетитель видит картинку в ленте чата, менеджер — в кабинете и в теме Telegram: фото с пометкой «через API», текст — следом отдельным сообщением.
- Повтор с тем же
client_message_idне сохраняет файл второй раз.
В ответе картинка приходит в message.attachments — в той же форме, что и при
чтении.
Разметка в тексте
Текст понимается как markdown — в панели чата и в теме Telegram он приходит размеченным, а ссылки становятся кликабельными.
{ "body": "Готово: [посмотреть заказ](https://shop.example/orders/1024)" }
Поддерживается то, что осмысленно в пузыре чата: ссылки (в том числе просто
адрес в тексте), жирный, курсив, зачёркнутый, код и блок кода.
Заголовки и списки приводятся к обычным строкам — верстать документ в
переписке не стоит.
Если разметки в тексте нет, ничего и не происходит: сообщение уходит как есть. HTML в теле не поддерживается и вырезается — размечайте markdown'ом.
Исходный текст остаётся тем, что вы прислали: чтение возвращает его в body
без изменений.
Диалог ведёт бот
Всё, что выше, — про содержание переписки. Этот раздел про то, кто её ведёт: ваш бот или менеджеры. Нужна способность «Управление диалогом».
Смысл простой. Бот заявляет, что берёт разговор на себя, отвечает посетителю обычной отправкой, а когда упирается — сдаёт диалог людям и объясняет, почему. Менеджеры при этом видят в теме Telegram, что происходит, и не отвечают вторыми.
Взять диалог
POST /api/v1/conversations/{id}/claim
Ответ — { "claimed": true, "conversation": { … } }. Поле led_by у диалога
принимает значения bot и humans.
- Повтор тем же ключом отвечает
200иclaimed: false. Это не ошибка: ретраить безопасно. 409— диалог занят: его ведёт другая интеграция либо последним в нём ответил живой менеджер. Влезать в такой разговор мы не даём.
Диалог, где менеджер отвечал давно, а посетитель написал снова, свободен: занятым считается тот, где человек ответил последним.
Передать человеку
POST /api/v1/conversations/{id}/handover
Content-Type: application/json
{ "reason": "Клиент просит человека" }
Причина необязательна, но её стоит писать: она уходит в тему Telegram и попадает в историю диалога. Менеджер по ней понимает, чего от него хотят.
Ответ — { "released": true, "conversation": { … } }; повтор отвечает 200
и released: false, ретраить безопасно. 409 — диалог ведёт другая
интеграция: сдать его может только тот ключ, который его взял.
Открытый диалог после передачи считается ждущим ответа — иначе передача человеку была бы передачей в никуда: диалог не попал бы ни в фильтр «Ждут ответа», ни в сводку. Закрытый при этом остаётся закрытым: передавать завершённый разговор некому.
Заметка менеджерам
POST /api/v1/conversations/{id}/notes
Content-Type: application/json
{ "body": "Клиент оплатил счёт №1024" }
Заметку видят менеджеры — в кабинете и в теме. Посетитель её не видит
никогда. Это то же самое, что команда /note в теме, только вашими руками.
Ответ — 201 и созданное сообщение: заметка это запись в ленте, и у неё есть
свой идентификатор.
Закрыть диалог
POST /api/v1/conversations/{id}/close
Закрывает и тему в Telegram — ровно как команда /close. Новое сообщение
посетителя откроет диалог снова.
Как бот узнаёт, что пора замолчать
Ответ менеджера — из темы или из кабинета — снимает владение автоматически. Никакого запроса для этого не нужно: человек не обязан помнить про ваш API, а его вмешательство — самый сильный сигнал из возможных.
Вам об этом приходит вебхук conversation.handover — тот же, что и при вашей
собственной передаче. Получив его, бот должен замолчать: дальше отвечает
человек. Отличить один случай от другого можно по тому, вы ли только что
вызывали handover.
Отдельно об этом стоит помнить, если бот отвечает медленно: пока он думает, менеджер вправе ответить первым, и разговор станет человеческим.
Пока диалог ведёт бот
- Автоответ молчит. Иначе посетитель получил бы подряд ответ вашего бота и наше «сейчас нерабочее время» — вторая фраза была бы неправдой.
- Доставка менеджерам продолжается. Сообщения посетителя по-прежнему уходят в тему Telegram: замолчавший бот не должен делать посетителя невидимым для людей.
- В теме видно, кто отвечает. Взятие диалога и передача его людям показываются отдельными строками.