HTTP API

Публичный HTTP-метод для внешних систем — отправка текста в существующий диалог Amobit Chats. Чтение диалогов и истории для AI-клиентов идёт через MCP, настройка каналов и интеграции — через кабинет.

Базовый URL: https://chats.amobit.ioВерсия: /api/v1Формат: JSON, UTF-8

Аутентификация

Токен выпускает владелец или администратор компании: Профиль → Интеграции → API / Webhook → Создать токен. Токен показывается один раз, начинается с amc_ и передаётся в заголовке:

Authorization: Bearer amc_ВАШ_ТОКЕН

Эквивалентная форма, если ваш HTTP-клиент не умеет Authorization:

X-Amobit-Chats-Token: amc_ВАШ_ТОКЕН
Свойство токенаЗначение
Область действияОдна компания (workspace) — тот, для которого выпущен токен.
ПраваОтправка текста в существующие диалоги этой компании. Чтения истории и управления каналами нет. Токен принимается только эндпоинтом /inbound/messages/; настройки интеграции и журнал доставок требуют сессию пользователя.
Срок действияСрок зашит в токен: по умолчанию 365 дней (настраивается на стороне Amobit). Просроченный токен отклоняется с 401; при выпуске API возвращает expires_at, а настройки интеграции — api_token_expires_at.
Хранение на стороне AmobitSHA-256-хеш; полный токен в базе не хранится.
ЗаменаНовый выпуск немедленно отключает предыдущий токен и сообщает новый срок действия.
ОтзывЗамена токена, отключение интеграции или удаление интеграции в кабинете.
Секрет подписиОтдельный секрет для HMAC вебхуков, минимум 16 символов. Токен и секрет — разные значения.

Токены, выпущенные до появления срока действия. Старый формат amc_… без встроенного срока продолжает работать до замены. Перевыпустите его в кабинете, чтобы получить ограниченный срок и expires_at в ответе.

Не передавайте токен в браузер. Это серверный секрет: вызывайте API только с вашего backend. Токен не должен попадать в Git, логи, URL, аналитику и сообщения в мессенджерах.

Отправить сообщение

POST/api/v1/external-integrations/inbound/messages/

Отправляет текст от имени компании в уже существующий диалог. Метод не создаёт новые диалоги и не отправляет медиа.

Заголовки

ЗаголовокОбязательныйОписание
AuthorizationДа*Bearer amc_…
Content-TypeДаapplication/json
X-Amobit-Chats-TokenДа*Альтернатива Authorization, если передать токен в заголовке.

* Достаточно одного из двух заголовков.

Тело запроса

ПолеТипОбяз.Описание
thread_idUUIDУсловноID диалога из webhook. Предпочтительный способ адресации.
channel_profile_idUUIDУсловноID канала; используется вместе с external_chat_id.
channelstring, ≤ 50УсловноТип канала (whatsapp, telegram, max…). Устаревшая пара к external_chat_id.
external_chat_idstring, ≤ 255УсловноID чата на стороне канала, например 79990001122@c.us.
textstring, 1–4000ДаТекст сообщения. Пробелы по краям обрезаются.
idempotency_keystring, ≤ 255НетКлюч повтора: латиница, цифры, . _ : -. Начинается с буквы или цифры. Настоятельно рекомендуется.
metadataobjectНетСлужебные данные вашей системы. Сохраняются с намерением отправки для диагностики и не влияют на доставку.

Как выбирается диалог

  1. thread_id — точный ID диалога в этой компании.
  2. channel_profile_id + external_chat_id — поиск в пределах указанного канала.
  3. channel + external_chat_id — поиск по типу канала без указания профиля (устаревший вариант, для совместимости).

Если подходящих диалогов несколько, API отвечает 409 вместо выбора «наугад»: отправить сообщение не тому адресату хуже, чем вернуть ошибку. Используйте thread_id из webhook — он однозначен.

Идемпотентность

  • Ключ уникален в пределах компании и диалога.
  • Повтор с тем же ключом и тем же текстом возвращает 200 и существующее сообщение — новый дубль не создаётся.
  • Тот же ключ с другим текстом возвращает 409: ключ уже привязан к другому запросу.
  • Проверяется только текст: metadata в сравнении не участвует. Тот же ключ, тот же текст и новые metadata вернут первое сообщение, новые данные не применятся.
  • Без ключа каждый запрос создаёт новое сообщение — при сетевом сбое возможен дубль.

Неизвестные поля игнорируются. Если в имени поля опечатка, ошибки 400 не будет: поле молча отбросится. Передавайте только поля из таблицы выше и проверяйте ответ.

Успешный ответ

201 Created — сообщение записано и принято к отправке. Для мессенджеров это значит, что провайдер принял запрос; для веб-чата провайдера нет — сообщение сразу доставлено в диалог. Тело ответа — объект сообщения (фрагмент):

{
  "id": "f09de45d-0000-4000-9000-000000000003",
  "thread_id": "9fd48a99-f5f3-4c3d-8ca1-1f0e047d5b25",
  "channel": "whatsapp",
  "direction": "outgoing",
  "message_type": "text",
  "text": "Ответ из CRM",
  "preview_text": "Ответ из CRM",
  "delivery_status": "pending",
  "sent_at": "2026-10-20T12:00:00+00:00",
  "provider_transport_id": "",
  "attachments": []
}

delivery_status отражает состояние на момент ответа: pending, sent, delivered, read или failed. 201 означает, что запрос принят, а не что сообщение доставлено получателю.

Ответ — объект сообщения интерфейса, а не отдельный публичный DTO. В нём могут быть служебные поля UI и ссылки на медиа, доступные только в сессии пользователя. Для интеграции опирайтесь на id, thread_id, direction, text, delivery_status, sent_at и attachments[].id/kind/file_name/mime_type/size_bytes; остальные поля не считайте стабильным контрактом.

Коды ответов

КодКогда возвращаетсяЧто делать
201Сообщение записано и принято к отправке: для мессенджеров — провайдер принял запрос, для веб-чата — доставлено в диалог.Сохранить id сообщения.
200Повтор по idempotency_key: возвращается существующее сообщение. Также возможен, когда сообщение записано, но подтверждение провайдера ещё не получено.Проверить delivery_status и id.
202Провайдер принял отправку, но локальная сверка/сохранение не завершены. Повторная отправка провайдеру не выполняется.Повторить тот же запрос с тем же ключом, чтобы получить текущее состояние; сверить историю диалога.
400Ошибка валидации: нет text, слишком длинный текст, неверный ключ, не передан адресат.Исправить тело; текст ошибки — объект с полями.
401Токен отсутствует, отозван или неверен.Выпустить новый токен в кабинете.
402Отправка заблокирована тарифом или подпиской канала.Проверить оплату канала в кабинете; в ответе detail и billing_status.
404Диалог не найден в этой компании.Проверить thread_id / external_chat_id.
409Неоднозначный диалог; канал не подключён для исходящих; группа исключена настройкой канала; ключ идемпотентности занят другим текстом.Разобрать detail; для ключа — использовать новый ключ только для нового сообщения.
502Провайдер не подтвердил отправку; исход неизвестен.Повторить с тем же idempotency_key; сохранить error_id для поддержки.
500Внутренняя ошибка.Повторить с тем же ключом; если повторяется — в поддержку.

Ограничения

Write-first намеренно недоступен. Новый диалог с незнакомым номером затрагивает лимиты провайдера, согласие получателя и антиспам-правила. Для таких сценариев нужен отдельный согласованный процесс, а не публичный API.

Примеры

curl

curl -X POST https://chats.amobit.io/api/v1/external-integrations/inbound/messages/ \
  -H "Authorization: Bearer amc_ВАШ_ТОКЕН" \
  -H "Content-Type: application/json" \
  -d '{
    "thread_id": "9fd48a99-f5f3-4c3d-8ca1-1f0e047d5b25",
    "text": "Здравствуйте! Отвечаем из CRM.",
    "idempotency_key": "crm-outbox-1001",
    "metadata": {"lead_id": "42"}
  }'

Node.js (fetch, Node 18+)

const res = await fetch(
  "https://chats.amobit.io/api/v1/external-integrations/inbound/messages/",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AMOBIT_CHATS_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      thread_id: threadId,
      text,
      idempotency_key: `crm-outbox-${outboxId}`,
    }),
  },
);
const data = await res.json();
if (res.status === 201 || res.status === 200) return data;
if ([202, 502, 500].includes(res.status)) {
  // Повторить с ТЕМ ЖЕ idempotency_key, не создавать новый.
  throw new RetryableError(data.detail || "retry");
}
throw new PermanentError(data);

PHP (curl)

$payload = json_encode([
    'thread_id' => $threadId,
    'text' => $text,
    'idempotency_key' => 'crm-outbox-' . $outboxId,
], JSON_UNESCAPED_UNICODE);

$ch = curl_init('https://chats.amobit.io/api/v1/external-integrations/inbound/messages/');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('AMOBIT_CHATS_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 20,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

Служебные эндпоинты кабинета

Настройки интеграции и выпуск токена использует сам кабинет. Журнал доставок доступен только этим API: в карточке интеграции он пока не отображается. Все методы требуют сессию Amobit ID и CSRF-токен, поэтому не предназначены для вызова из внешней системы. Список приведён для полноты контракта и для поддержки:

МетодПутьНазначение
GET/api/v1/external-integrations/workspaces/{workspace_slug}/Текущие настройки интеграции.
PATCH/api/v1/external-integrations/workspaces/{workspace_slug}/Название, включение, webhook-URL, секрет, список событий.
DELETE/api/v1/external-integrations/workspaces/{workspace_slug}/Отключить интеграцию, очистить URL, секрет и токен.
POST/api/v1/external-integrations/workspaces/{workspace_slug}/rotate-token/Выпустить новый токен (показывается один раз).
GET/api/v1/external-integrations/workspaces/{workspace_slug}/deliveries/Последние 100 доставок вебхуков.

Сессионные запросы выполняются с credentials: include и CSRF-заголовком; к ним применяются роли компании (owner/admin для изменений).

Машиночитаемый контракт

OpenAPI 3.1 описывает этот метод, схемы запроса и ответа, коды ошибок и событие вебхука. Спецификация — источник для генерации клиентов и моков; при расхождении с этой страницей приоритет у поведения API, о расхождении сообщите в поддержку.