Работа с эндпоинтами Developer API: примеры, постраничный вывод, idempotency

Обновлено: 11 ноября 2026 · 8 мин чтения

Эндпоинт это один адрес и одна операция API: метод (GET чтение, POST создание, PATCH изменение, DELETE удаление) и путь. Все эндпоинты, их параметры и примеры ответов есть на developer.clomni.ai на странице «API reference». Рядом с каждым эндпоинтом указаны нужные scope, достаточно одного из них. Эндпоинт, которого нет в списке, закрыт для токенов разработчика и возвращает 403 endpoint_not_available. Какой эндпоинт и scope нужны для какой задачи, описано в статье «Какие scope нужны: scope и endpoint по Вашей задаче» и в статьях «Scope и endpoint: …».

Шаги

  1. 1

    Как строится запрос

    Адрес начинается с https://api.clomni.ai/v1, токен передаётся в заголовке Authorization: Bearer <token>, тело в JSON (Content-Type: application/json). Номер workspace в адресе не пишется.

    • id диалога в API совпадает с номером, который виден в панели: «диалог 1042» означает GET /conversations/1042.
    • Обычные эндпоинты возвращают время в секундах Unix, эндпоинты синхронизации в формате ISO 8601 (2026-11-08T16:00:00Z). Часовой пояс UTC.
    • Телефон пишется в международном формате: +994501234567.
  2. 2

    Читайте диалоги и сообщения

    GET /conversations (scope conversations:read) возвращает диалоги страницами по 25. Параметры: status (open, resolved, pending, snoozed, all, по умолчанию open), assignee_type (me, unassigned, assigned, all), inbox_id, page.

    curl "https://api.clomni.ai/v1/conversations?status=open&page=1" \
      -H "Authorization: Bearer $CLOMNI_TOKEN"
    const res = await fetch("https://api.clomni.ai/v1/conversations?status=open&page=1", {
      headers: { Authorization: `Bearer ${process.env.CLOMNI_TOKEN}` },
    });
    const { data } = await res.json();
    for (const c of data.payload) console.log(c.id, c.status, c.meta.sender.name);
    import os, requests
    res = requests.get("https://api.clomni.ai/v1/conversations", params={"status": "open", "page": 1},
                       headers={"Authorization": f"Bearer {os.environ['CLOMNI_TOKEN']}"}, timeout=10)
    for c in res.json()["data"]["payload"]:
        print(c["id"], c["status"], c["meta"]["sender"]["name"])

    Поле диалога can_reply показывает, можно ли сейчас написать клиенту свободное сообщение. Сообщения: GET /conversations/{conversation_id}/messages (scope messages:read) отдаёт последние 20 сообщений, для более старых ?before=<самый маленький id>. message_type: 0 входящее, 1 исходящее, 2 системная запись, 3 шаблон. status: sent, delivered, read, failed.

  3. 3

    Отвечайте и управляйте диалогом

    POST /conversations/{conversation_id}/messages, scope messages:send:

    curl -X POST "https://api.clomni.ai/v1/conversations/1042/messages" \
      -H "Authorization: Bearer $CLOMNI_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: 6f1c2a9e-3b7d-4f0a-9c55-0e8b1d2a7f41" \
      -d '{"content": "Здравствуйте, Ваш заказ доставят сегодня."}'

    Канал отправляет сообщение в фоне, поэтому status сначала sent, а результат приходит позже вебхуком message.updated. Если отправить не удалось, причина находится в content_attributes.external_error. Для внутренней заметки, которую клиент не видит, передайте "private": true (scope notes:write).

    • Закрыть, открыть: POST /conversations/{conversation_id}/toggle_status с { "status": "resolved" } (scope conversations:write).
    • Назначить: POST /conversations/{conversation_id}/assignments с { "assignee_id": 12 } или { "team_id": 3 } (scope conversations:assign).
    • Список операторов и команд: GET /agents, GET /teams.
  4. 4

    Создавайте и ищите контакты

    POST /contacts (scope contacts:write):

    curl -X POST "https://api.clomni.ai/v1/contacts" \
      -H "Authorization: Bearer $CLOMNI_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"name": "Лейла Гасанова", "phone_number": "+994501234567", "identifier": "crm-10045"}'

    identifier это ID в Вашей системе, он уникален в пределах workspace. PATCH /contacts/{contact_id} меняет только те поля, которые Вы передали. GET /contacts/search?q=crm-10045 ищет по имени, e-mail, телефону и identifier. Второй контакт с тем же e-mail или телефоном возвращает 422: сначала ищите, потом обновляйте. Ключи custom_attributes нужно заранее создать в workspace.

  5. 5

    Постраничный вывод: обычные списки

    • GET /conversations: page (с 1), страница по 25, ответ data.meta и data.payload.
    • GET /contacts: page, страница по 15, ответ meta.count, meta.current_page, payload.
    • GET /conversations/{conversation_id}/messages: before=<message_id>, каждый раз 20 сообщений.

    Эти списки нужны для показа на экране. Не перебирайте их, чтобы перенести данные целиком: если между страницами приходит новый диалог, порядок сдвигается, и объекты могут потеряться.

  6. 6

    Синхронизация: перенос данных в Вашу базу

    GET /sync/conversations (conversations:read), GET /sync/messages (messages:read), GET /sync/contacts (contacts:read). Параметры: updated_since (ISO 8601), after (next_cursor из предыдущего ответа), limit (1-100, по умолчанию 50). Ответ: data, next_cursor, has_more.

    1. Первая загрузка: начните без updated_since и продолжайте с after=next_cursor, пока не получите has_more: false.
    2. Сохраните самое большое полученное значение updated_at.
    3. В следующий раз updated_since = сохранённое время минус 2 минуты.
    4. Записывайте объект по id (upsert), дубликат отбрасывайте.
    5. Новое получайте сразу вебхуком, а синхронизацию запускайте раз в 5-15 минут.

    В объектах синхронизации id диалога это внутренний ID, номер из панели находится в number (в сообщении conversation_number). Ссылки на файлы живут недолго. Синхронизация ограничена 60 запросами в минуту. Чтобы один раз перенести всю историю, лучше подходит POST /exports (exports:run, 1 задача одновременно, 6 в день).

  7. 7

    Защита от повторной отправки: Idempotency-Key

    Когда обрывается связь, нельзя понять, дошёл ли POST. Для каждой новой операции создавайте новый UUID и передавайте его в заголовке Idempotency-Key, при повторной попытке отправляйте тот же.

    • Работает во всех POST-запросах /v1, кроме /oauth/*. Ключ длиной 1-255 символов, хранится 24 часа в пределах приложения и workspace.
    • Тот же ключ, то же тело: операция не повторяется, сохранённый ответ возвращается с Idempotency-Replayed: true.
    • Тот же ключ, другое тело: 409 idempotency_key_reused. Первый запрос ещё выполняется: 409 idempotency_key_in_progress.
    • Ответы 5xx не сохраняются, с тем же ключом можно повторить.

    Кроме того, у сообщения есть content_attributes.client_echo_id: запрос с тем же значением в том же диалоге в течение 24 часов не создаёт новое сообщение.

  8. 8

    Лента событий: GET /v1/events

    Возвращает события последних 30 дней (такие же, как в вебхуке, с теми же id) от старых к новым. Нужны scope events:read и scope чтения темы (messages:read, conversations:read, contacts:read), иначе список приходит пустым. Параметры: types (например message.created,contact.updated), after, limit (1-100). Если у Вашего сервера нет открытого адреса, читайте ленту каждую минуту, а если есть, забирайте отсюда то, что пропустил вебхук.

Документ