Globask

Серверное 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
401 ключа нет, он неизвестен, отозван или просрочен
403 ключу не хватает способности; аккаунт приостановлен
404 записи нет или она из другого проекта — мы не различаем эти случаи вслух
422 параметр не подходит: неизвестный статус, чужой курсор, пустой текст
429 превышен лимит: 120 запросов в минуту на чтение, 60 на отправку

Про 400 стоит знать заранее. Тело мы разбираем сами и на битом отвечаем прямо: «не разобрано как JSON». Раньше такой запрос получал 422 «Поле body обязательно» — формально верно (полей и правда нет) и практически вредно: ответ отправляет искать ошибку в коде отправки, а не в кодировке. Самая частая причина — не UTF-8: тело собрано в CP1251 или уехало через слой, который его перекодировал.

Чтение

Как листать

Списки отдаются от старых записей к новым и листаются курсором:

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",
            "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
            }
        }
    ],
    "next_after": null
}

Сообщения диалога

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.

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",
                "created_at": "2026-08-25T10:10:00+03:00"
            }
        ]
    }
}

Вложения

Ссылка приходит в сообщении: attachments[].url. Она не работает сама по себе — требует того же заголовка Authorization, что и всё остальное. Подписанных ссылок, живущих без ключа, у нас нет: они утекают из чужих логов и работают у всякого, кто их подобрал.

Отправка

POST /api/v1/conversations/{id}/messages
Content-Type: application/json

{ "body": "Заказ №1024 отправлен, трек 12345.", "client_message_id": "…uuid…" }

Ответ — то же сообщение, что отдаёт чтение, плюс текущее состояние диалога. Код 201 на новое сообщение и 200 на повтор.

Что нужно знать до первой отправки:

  1. Сообщение помечено автоматическим. В ленте виджета оно выглядит как ответ робота, а не оператора. Выдавать вашу систему за живого человека мы не станем: тот, кто ждёт продолжения разговора, не дождётся.
  2. client_message_id — ваш ключ идемпотентности. Он необязателен, но без него сетевой таймаут на вашей стороне обернётся вторым «заказ отправлен» у посетителя. Повтор с тем же ключом возвращает то же самое сообщение.
  3. Закрытый диалог оживает. Уведомление приходит как раз тогда, когда разговор давно закончен, — отказ был бы отказом в главном сценарии. Вместе с диалогом откроется и тема, в которой отвечают менеджеры.
  4. Диалог не перестаёт считаться ждущим. Ваше сообщение отвечает на свой повод, а не на вопрос посетителя, поэтому last_message_at и статус открытого диалога не меняются. Иначе отправка «заказ собран» прятала бы от менеджера человека, который третий час ждёт ответа.
  5. Менеджер видит, что вы ответили. В тему Telegram уходит строка «через API» с текстом сообщения — иначе он не поймёт, почему посетителю уже ответили, и ответит второй раз.

Завести новый диалог через API нельзя: сообщение уходит в существующий, а диалог заводит посетитель. Вложения через API пока не отправляются.