Перейти к содержимому
AISARAISAR
REST API

Сообщения

Сообщения — основная единица переписки внутри треда. API позволяет читать историю сообщений, искать по тексту, отправлять новые исходящие сообщения (текст, вложения, голосовые заметки), редактировать и удалять недавние сообщения оператора, ставить реакции и отслеживать статусы доставки.

Точки входа и идемпотентность

POST /messages/send и POST /messages/send-template — самые частые точки входа: они сами находят или создают нужный тред по паре канал + получатель и сами генерируют ключ идемпотентности (клиентский body-параметр idempotency_key они не принимают). Идемпотентность по вашему ключу доступна на прямом POST /messages и на POST /messages/{message}/forward: передайте idempotency_key (до 100 символов) — повторный запрос с тем же ключом вернёт то же сообщение, а не создаст дубликат.

Отдельно от этого, POST /messages/send и POST /messages/send-template принимают необязательный HTTP-заголовок Idempotency-Key (до 128 символов) — механизм на уровне мидлвара, не связанный с body-параметром idempotency_key выше. Повторный запрос с тем же значением заголовка (скоуп — ключ + эндпоинт + пользователь) в течение 24 часов не выполняет отправку заново, а возвращает закэшированный ответ первого запроса; тот же ключ с другим телом запроса вернёт 422. Заголовок опционален — без него поведение эндпоинтов не меняется; это защита для клиентов с сетевыми автоповторами (например, мобильного приложения при потере соединения).

Блокировка контакта

Если получатель — заблокированный контакт (blocked_at заполнен; блокируется через POST /contacts/{contact}/block, см. группу «Контакты»), исходящее сообщение отклоняется до создания: POST /messages/send, прямой POST /messages и POST /messages/{message}/forward возвращают 422 {"message": "This contact is blocked; the message was not sent.", "error_code": "contact_blocked"}. Проверка идёт по conversation_id целевого треда (для /messages/send и /forward он резолвится автоматически), а не по получателю напрямую.

Список и пагинация

GET /messages фильтруется по conversation_id/thread_id/channel_id/direction/message_type (канонический список значений message_type — см. поле message.type события message.created в справочнике событий вебхуков) и пагинируется либо постранично (page/perPage), либо курсором — передайте before_message_id, чтобы идти к более старым сообщениям (направление прокрутки истории вверх), или after_message_id для более новых; приняты и легаси-псевдонимы before_id/after_id, limit (=perPage) и order (=sortDir), sortBy — одно из id|timestamp|created_at|updated_at (по умолчанию timestamp), а в курсорном режиме ответ сохраняет форму {items, pagination}, но total/lastPage возвращаются как null (тяжёлый COUNT пропускается).

GET /messages также поддерживает текстовый фильтр search (тот же ILIKE по message_content/external_message_id/external_thread_id) и его официальный алиас q (с 2026-08-20): если передан только q, его значение подставляется в search; если переданы оба — побеждает явный search. До этой даты нераспознанный q тут молча отбрасывался — запрос выглядел рабочим, но не находил ничего; для новых интеграций предпочитайте явный search.

Поиск (`GET /messages/search`)

GET /messages/search — обязательный q (строка, от 2 до 255 символов) ищет по тексту сообщения (ILIKE по message_content). conversation_id необязателен (с 2026-08-20): передан — поиск идёт внутри конкретного диалога (диалог другой компании или несуществующий — 404); не передан — поиск идёт по всей компании, в границах доступных пользователю каналов, и тогда каждый элемент результата несёт собственный conversation_id — так можно найти диалог, которого иначе не видно в списке. Результаты отсортированы от новых к старым (created_at desc). Пагинация — page/perPage (5–100, по умолчанию 20). Ответ: { "data": { items[], pagination{page,perPage,total,lastPage}, total_matches } }, где каждый items[]{message_id, conversation_id, thread_id, created_at, snippet, query, contact_name, channel_id, channel_type}; последние три поля (с 2026-08-20) — contact_name (readable-имя контакта треда, строка либо null), channel_id и channel_type (ключ типа канала, например whatsapp/telegram, либо null) — добавлены, чтобы результат из company-wide поиска можно было отрисовать отдельной строкой без дополнительного запроса за контактом и каналом. snippet (с 2026-08-22) — это окно текста вокруг НАЙДЕННОГО СОВПАДЕНИЯ (регистронезависимо, до ~240 символов, с ... по краям там, где текст обрезан), а не первые символы сообщения — до этой даты снippet всегда начинался с начала сообщения, и совпадение в длинном сообщении могло оказаться невидимым; при отсутствии текстового совпадения (например, попадание пришло по другому полю или сообщение — медиа без подписи) используется прежнее поведение (первые ~240 символов). Формат поля не менялся, изменилось только содержимое. Роут — под усиленным лимитом (throttle:api-token-heavy, 120 запросов/мин и для программных токенов, и — с 2026-08-22 — для обычных сессионных пользователей веб-клиента; раньше для сессионных клиентов лимита не было вовсе).

Цитируемые ответы

Чтобы отправить цитируемый ответ, используйте прямой POST /messages с quoted_message_id (внутренний id цитируемого сообщения) или quoted_external_message_id/messages/send этот параметр не принимает.

Атрибуция рекламы (referral)

Объект сообщения (GET /messages, /messages/{message}, /messages/{message}/around) несёт поле referral — те же данные о переходе из рекламы, что и в вебхуке message.created (объект data.referral), просто без обёртки события. null для обычных сообщений; заполняется на первом сообщении диалога, начатого из объявления, только для каналов whatsapp/whatsapp_business (Click-to-WhatsApp) и instagram_business (реклама с переходом в Direct). Набор полей зависит от канала: WhatsApp отдаёт source_url, ctwa_clid, body, media_type; Instagram — post_id и ref. Общие для обоих: source_type, source_id, headline, image_url, video_url. Полное описание каждого поля — в справочнике событий вебхуков, событие message.created.

ПолеОписание
referral.source_urlURL объявления или поста; только WhatsApp
referral.source_typead или post
referral.source_idID объявления/поста (для Instagram — ad_id)
referral.headlineЗаголовок объявления
referral.bodyТекст объявления; только WhatsApp
referral.ctwa_clidКлик-ID для точной атрибуции; только WhatsApp
referral.media_typeimage или video; только WhatsApp
referral.image_urlURL изображения объявления (если есть)
referral.video_urlURL видео объявления (если есть)
referral.post_idID поста, из которого крутилось объявление; только Instagram
referral.refМетка из диплинка (ig.me/...?ref=), если задана; только Instagram

Вложения

Вложения передаются в два шага: сначала загрузите файл через POST /uploads (до 64 МБ — image, video, audio, document), затем сошлитесь на возвращённые media_id/storage_path при создании сообщения или в POST /messages/{message}/attachments; источник проверяется и должен принадлежать вашей компании, иначе возвращается плоский 422 {error_code: attachment_source_invalid} (перезалейте файл).

Входящие вложения сначала несут рантайм/CDN-ссылку провайдера url, которая может протухнуть за часы (например, у Instagram) — вызовите POST /messages/{message}/attachments/{attachment}/fetch, чтобы догрузить байты в локальное хранилище, после чего у вложения появляется стабильный подписанный url, а GET .../attachments/{attachment}/media отдаёт эти байты потоком.

fetch работает и для вложений Instagram-каналов (meta_instagram) — ig_post/ig_reel/сторис резолвятся через Graph API/CDN так же, как это делает вебхук-обработчик при получении сообщения. Для вложений, у которых в принципе нет скачиваемого медиа (type = template, ephemeral или unsupported_type — карточка generic-шаблона, самоуничтожающееся или нераспознанное вложение), fetch сразу отвечает 422 {message: "...", error_code: "attachment_not_fetchable"}, а не пытается округлить попытку в 502 — вызывать fetch для таких типов бессмысленно, у них никогда не появится storage_path.

thumbnail_url (с 2026-08-24) заполняется автоматически для локально сохранённых image-вложений, кроме image/svg+xml (не растрируется и не отдаётся инлайном по соображениям ИБ) — это тот же подписанный роут .../media, но с параметром w=640, отдающий webp-превью вместо оригинала. Ранее поле было почти всегда null; провайдерский thumbnail_url (если он уже пришёл, например от Instagram lookaside CDN) имеет приоритет и не переопределяется. На GET .../attachments/{attachment}/media параметр w принимает только 256 или 640 (закрытый белый список) — другое значение или отсутствие w отдаёт оригинал как раньше; для не-изображений w игнорируется. Начиная с этой же даты «небезопасные для инлайна» MIME (svg, html, xml, txt и т.п.) отдаются с Content-Type: application/octet-stream и Content-Disposition: attachment вместо инлайн-рендера — как защита от XSS через сообщение с произвольным заявленным mime_type на origin с сессионной кукой. Роут — под лимитом throttle:signed-media (2000 запросов/мин на IP).

Редактирование и удаление

Редактирование (PATCH) и удаление (DELETE) ограничены окнами канала: гейтите контролы «редактировать» и «отозвать у всех» по предвычисленным per-message полям can_edit_until / can_revoke_until, а не по собственной per-channel логике — каждое поле это ISO-8601-дедлайн, пока действие доступно, либо null, когда недоступно (входящее сообщение, уже удалённое, не-текст для редактирования, канал не поддерживает действие — как whatsapp_business для отзыва — либо истёкшее окно).

DELETE по умолчанию мягко удаляет (обнуляет текст); mode=revoke дополнительно отзывает сообщение у получателя там, где канал поддерживает provider-side удаление (delete_scope='provider': QR-WhatsApp, Telegram, Instagram) — WhatsApp Cloud (whatsapp_business) отзывать не умеет и вернёт 422. Передавайте mode в JSON-теле, а не в query-строке: часть прокси срезает query-параметры на DELETE и тихо понизит revoke до локального удаления.

Реакции

POST /messages/{message}/reactions принимает {reaction: "<эмодзи>"} (обязательное, до 32 символов) и ставит реакцию от имени компании (по умолчанию автор — аутентифицированный пользователь; явные author_user_id/author_contact_account_id имеют приоритет и валидируются по компании) — канал допускает одну исходящую реакцию на сообщение, поэтому новый вызов заменяет предыдущую. DELETE /messages/{message}/reactions снимает исходящую реакцию компании с сообщения — id реакции не нужен (реакция у канала одна на сообщение), запрос идемпотентен (200 даже если снимать нечего). Оба действия рассылают вебхук message.reaction.updated (action: "added" / "removed").

Пересылка

POST /messages/{message}/forward выполняет copy-send: создаёт новое исходящее сообщение с тем же контентом и вложениями либо в существующий тред (target_thread_id), либо кросс-канально по контакту (target_contact_account_id, при необходимости с явным target_channel_id); при отказе возвращается 422 с error_code из набора message_deleted, message_not_forwardable, channel_not_textable, content_type_unsupported, wa_window_closed, channel_incompatible, channel_not_connected, no_compatible_channel, channel_required, contact_blocked (получатель заблокирован — см. «Блокировка контакта» выше).

Голосовые заметки

Голосовые заметки загружаются отдельным путём POST /uploads/voice: он принимает записанный аудио- или видеофайл (multipart-поле file, до 32 МБ, mimetypes webm/ogg/mp4/mpeg/wav/aac/m4a) и транскодирует его в voice-OGG (Opus), возвращая плоский объект с media_id/storage_path той же формы, что и POST /uploads, — сошлитесь на этот media_id при отправке, и получатель увидит именно голосовую заметку (ptt), а не обычный аудиофайл; при сбое транскодирования возвращается 422.

Отправка стикера

Стикер из библиотеки компании (см. группу «Стикеры») отправляется как обычное вложение, но по attachments[].sticker_id вместо attachments[].media_id — сошлитесь на id стикера из GET /v1/sticker-packs. Каждая строка attachments[] должна нести ровно одно из двух полей: и то, и другое сразу, и ни одного из двух — ошибка 422. Указывать attachments[].type для стикера не нужно — он выставляется автоматически (sticker); POST /v1/messages/send и прямой POST /v1/messages принимают sticker_id одинаково.

Лайк комментария Instagram

POST /messages/{message}/like ставит лайк на комментарий: он работает только для комментарийных тредов Instagram Business (thread.is_group + канал типа instagram_business) и требует, чтобы у сообщения был внешний id, иначе 422; при успехе возвращается плоский { "message": "Comment liked." }.

Эндпоинты

МетодПуть
POST/v1/messages/send

Отправить сообщение по каналу и получателю (создаёт тред при необходимости).

POST/v1/messages/send-template

Отправить одобренный WhatsApp-шаблон по каналу и получателю (или thread_id).

GET/v1/messages

Список сообщений с фильтрами (диалог/тред/канал/направление) и постраничной либо курсорной (before_message_id/after_message_id) пагинацией.

GET/v1/messages/search

Полнотекстовый поиск сообщений (`conversation_id` опционален — без него поиск идёт по всей компании).

POST/v1/messages

Создать сообщение напрямую, в т.ч. цитируемый ответ через quoted_message_id (обычно вместо этого используется /messages/send).

GET/v1/messages/{message}

Получить сообщение по id.

PATCH/v1/messages/{message}

Отредактировать текст своего исходящего сообщения (в пределах окна редактирования канала).

DELETE/v1/messages/{message}

Удалить сообщение (мягкое удаление; mode=revoke в JSON-теле — отозвать у получателя там, где канал поддерживает, кроме whatsapp_business).

GET/v1/messages/{message}/around

Окно сообщений вокруг конкретного сообщения (для прыжка к контексту); с 2026-08-22 под тем же усиленным лимитом, что и /messages/search (throttle:api-token-heavy).

GET/v1/messages/{message}/attachments

Список вложений сообщения.

POST/v1/messages/{message}/attachments

Прикрепить файл к уже созданному сообщению.

GET/v1/messages/{message}/statuses

История статусов доставки (sent/delivered/read/failed).

POST/v1/messages/{message}/statuses

Записать статус доставки сообщения.

GET/v1/messages/{message}/reactions

Список реакций (эмодзи) на сообщение.

POST/v1/messages/{message}/reactions

Поставить реакцию {reaction:"<эмодзи>"} на сообщение от имени компании (одна исходящая реакция на сообщение — новая заменяет прежнюю).

DELETE/v1/messages/{message}/reactions

Снять исходящую реакцию компании с сообщения (идемпотентно; id реакции не нужен — она одна на сообщение).

POST/v1/messages/{message}/like

Лайкнуть комментарий Instagram (только комментарийные треды Instagram Business; у сообщения должен быть внешний id).

POST/v1/messages/{message}/forward

Переслать сообщение (copy-send) в существующий тред или кросс-канально по контакту.

POST/v1/messages/{message}/attachments/{attachment}/fetch

Догрузить медиа вложения из рантайма/Instagram Graph API в локальное хранилище (lazy-load); для нескачиваемых типов (template/ephemeral/unsupported_type) — 422 attachment_not_fetchable.

GET/v1/messages/{message}/attachments/{attachment}/media

Отдать (streaming) медиа вложения из локального хранилища; необязательный подписанный w=256|640 отдаёт webp-превью изображения вместо оригинала.

POST/v1/uploads

Загрузить файл (до 64 МБ) и получить ссылку на медиа для прикрепления к сообщению.

POST/v1/uploads/voice

Загрузить голосовую заметку: транскодирует аудио/видео в voice-OGG (Opus) и возвращает media_id для прикрепления как голосовое сообщение.

Примеры

Отправка текстового сообщения

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/send" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": 3,
    "recipient": "+77001234567",
    "message_content": "Здравствуйте! Ваш заказ №1045 передан в доставку."
  }'

Ответ

json
{
  "success": true
}

Отправка стикера из библиотеки

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/send" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": 3,
    "recipient": "+77001234567",
    "attachments": [
      { "sticker_id": 87 }
    ]
  }'

Ответ

json
{
  "success": true
}

Цитируемый ответ (reply)

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": 15,
    "thread_id": 33,
    "direction": "outbound",
    "message_type": "text",
    "message_content": "Да, доставим сегодня до 18:00.",
    "quoted_message_id": 998
  }'

Ответ

json
{
  "data": {
    "message": "Message created.",
    "item": {
      "id": 1005,
      "conversation_id": 15,
      "thread_id": 33,
      "direction": "outbound",
      "message_type": "text",
      "message_content": { "text": "Да, доставим сегодня до 18:00." },
      "quoted_message_id": 998,
      "is_deleted": false,
      "created_at": "2026-02-09T18:10:00Z"
    }
  }
}

Список сообщений диалога

Запрос

bash
curl -X GET "https://api.aisar.app/v1/messages?conversation_id=15&perPage=20" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "items": [
      {
        "id": 1001,
        "conversation_id": 15,
        "thread_id": 33,
        "channel_id": 3,
        "direction": "outbound",
        "message_type": "text",
        "message_content": { "text": "Здравствуйте! Ваш заказ №1045 передан в доставку." },
        "display_content": { "text": "Здравствуйте! Ваш заказ №1045 передан в доставку." },
        "is_deleted": false,
        "timestamp": "2026-02-09T18:00:00Z",
        "created_at": "2026-02-09T18:00:00Z",
        "updated_at": "2026-02-09T18:00:00Z",
        "can_revoke_until": "2026-02-09T18:15:00Z",
        "can_edit_until": "2026-02-09T18:15:00Z"
      }
    ],
    "pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1 }
  }
}

Прокрутка истории вверх (курсор before_message_id)

Запрос

bash
curl -X GET "https://api.aisar.app/v1/messages?conversation_id=15&before_message_id=998&perPage=20&sortDir=desc" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "items": [
      { "id": 997, "direction": "inbound", "message_type": "text", "message_content": { "text": "Здравствуйте, подскажите статус заказа?" } },
      { "id": 996, "direction": "outbound", "message_type": "text", "message_content": { "text": "Добрый день! Секунду, проверяю." } }
    ],
    "pagination": { "page": 1, "perPage": 20, "total": null, "lastPage": null }
  }
}

Поиск сообщений в диалоге

Запрос

bash
curl -X GET "https://api.aisar.app/v1/messages/search?conversation_id=15&q=заказ" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "items": [
      {
        "message_id": 1001,
        "conversation_id": 15,
        "thread_id": 33,
        "created_at": "2026-02-09T18:00:00Z",
        "snippet": "...Ваш заказ №1045 передан в доставку...",
        "query": "заказ",
        "contact_name": "Aigerim K.",
        "channel_id": 3,
        "channel_type": "whatsapp"
      }
    ],
    "pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1 },
    "total_matches": 1
  }
}

Поиск по всей компании (без conversation_id)

Запрос

bash
curl -X GET "https://api.aisar.app/v1/messages/search?q=заказ" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "items": [
      {
        "message_id": 1001,
        "conversation_id": 15,
        "thread_id": 33,
        "created_at": "2026-02-09T18:00:00Z",
        "snippet": "...Ваш заказ №1045 передан в доставку...",
        "query": "заказ",
        "contact_name": "Aigerim K.",
        "channel_id": 3,
        "channel_type": "whatsapp"
      },
      {
        "message_id": 2044,
        "conversation_id": 27,
        "thread_id": 61,
        "created_at": "2026-02-08T11:20:00Z",
        "snippet": "...уточните, пожалуйста, номер заказа...",
        "query": "заказ",
        "contact_name": "Jane Roe",
        "channel_id": 9,
        "channel_type": "telegram"
      }
    ],
    "pagination": { "page": 1, "perPage": 20, "total": 2, "lastPage": 1 },
    "total_matches": 2
  }
}

Окно сообщений вокруг конкретного

Запрос

bash
curl -X GET "https://api.aisar.app/v1/messages/1001/around?before=20&after=20" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "items": [
      { "id": 998, "direction": "inbound", "message_type": "text", "message_content": { "text": "Добрый день! Где мой заказ №1045?" } },
      { "id": 1001, "direction": "outbound", "message_type": "text", "message_content": { "text": "Здравствуйте! Ваш заказ №1045 передан в доставку." } }
    ],
    "anchor_message_id": 1001,
    "has_older": true,
    "has_newer": false
  }
}

Загрузка файла для вложения

Запрос

bash
curl -X POST "https://api.aisar.app/v1/uploads" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "file=@invoice-1045.pdf"

Ответ

json
{
  "media_id": "med_9f3a2b7c",
  "type": "document",
  "storage_disk": "local",
  "storage_path": "10/attachments/8b1e2c4f-0a3d-4e57-9c21-1f2a3b4c5d6e.pdf",
  "filename": "invoice-1045.pdf",
  "mime_type": "application/pdf",
  "size": 245113,
  "width": null,
  "height": null,
  "duration": null,
  "expires_at": "2026-03-11T18:00:00Z"
}

Прикрепить загруженный файл к сообщению

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/1001/attachments" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "document",
    "media_id": "med_9f3a2b7c",
    "filename": "invoice-1045.pdf",
    "mime_type": "application/pdf",
    "size": 245113
  }'

Догрузить медиа входящего вложения (lazy-load)

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/1001/attachments/55/fetch" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "item": {
      "id": 1001,
      "direction": "inbound",
      "message_type": "image",
      "attachments": [
        {
          "id": 55,
          "type": "image",
          "url": "https://api.aisar.app/v1/messages/1001/attachments/55/media?signature=PLACEHOLDER",
          "thumbnail_url": "https://api.aisar.app/v1/messages/1001/attachments/55/media?w=640&signature=PLACEHOLDER",
          "filename": "photo.jpg",
          "mime_type": "image/jpeg"
        }
      ]
    }
  }
}

Редактирование исходящего сообщения

Запрос

bash
curl -X PATCH "https://api.aisar.app/v1/messages/1001" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "message_content": "Здравствуйте! Ваш заказ №1045 уже в пути." }'

Поставить реакцию на сообщение

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/1001/reactions" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "reaction": "👍" }'

Ответ

json
{
  "data": {
    "message": "Message reaction created.",
    "item": {
      "id": 77,
      "message_id": 1001,
      "author_user_id": 8,
      "author_name": "Азиз Каримов",
      "reaction": "👍",
      "created_at": "2026-02-09T18:12:00Z"
    }
  }
}

Снять реакцию с сообщения

Запрос

bash
curl -X DELETE "https://api.aisar.app/v1/messages/1001/reactions" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "message": "Message reaction removed."
  }
}

Удаление с отзывом у получателя (mode в теле)

Запрос

bash
curl -X DELETE "https://api.aisar.app/v1/messages/1001" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "mode": "revoke", "reason": "Отправлено по ошибке" }'

Ответ

json
{
  "data": {
    "message": "Message deleted.",
    "item": {
      "id": 1001,
      "direction": "outbound",
      "is_deleted": true,
      "can_revoke_until": null,
      "can_edit_until": null
    }
  }
}

Отзыв не поддержан каналом whatsapp_business (422)

Запрос

bash
curl -X DELETE "https://api.aisar.app/v1/messages/1001" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "mode": "revoke" }'

Ответ

json
{
  "message": "This channel does not support revoking a sent message for the recipient. Provider-side revoke (\"delete for everyone\") is not available on API-based channels such as WhatsApp Cloud (whatsapp_business). Omit \"mode\" for a local delete."
}

Форвард в существующий тред

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/1001/forward" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "target_thread_id": 45 }'

Ответ

json
{
  "data": {
    "message": "Message forwarded.",
    "item": {
      "id": 1002,
      "thread_id": 45,
      "direction": "outbound",
      "message_type": "text",
      "message_content": { "text": "Здравствуйте! Ваш заказ №1045 передан в доставку." },
      "is_forwarded": true,
      "created_at": "2026-02-09T18:05:00Z"
    }
  }
}

Кросс-канальный форвард по контакту

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/1001/forward" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "target_contact_account_id": 42,
    "target_channel_id": 5
  }'

Ответ

json
{
  "data": {
    "message": "Message forwarded.",
    "item": {
      "id": 1003,
      "thread_id": 51,
      "channel_id": 5,
      "direction": "outbound",
      "message_type": "text",
      "message_content": { "text": "Здравствуйте! Ваш заказ №1045 передан в доставку." },
      "is_forwarded": true,
      "created_at": "2026-02-09T18:06:00Z"
    }
  }
}

Форвард отклонён: нужен явный канал (422)

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/1001/forward" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "target_contact_account_id": 42 }'

Ответ

json
{
  "message": "Несколько подключённых каналов подходят этому контакту — укажите target_channel_id.",
  "error_code": "channel_required"
}

Загрузка голосовой заметки

Запрос

bash
curl -X POST "https://api.aisar.app/v1/uploads/voice" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "file=@voice-note.webm"

Ответ

json
{
  "media_id": "med_01JBQZ8VN2K3M4N5P6Q7R8S9T0",
  "type": "audio",
  "storage_disk": "local",
  "storage_path": "10/attachments/7c1e2a4f-0b3d-4e57-9c21-2f3a4b5c6d7e.ogg",
  "filename": "voice.ogg",
  "mime_type": "audio/ogg",
  "size": 18342,
  "duration": 7,
  "expires_at": "2026-03-11T18:00:00Z"
}

Лайк комментария Instagram

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/1001/like" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "message": "Comment liked."
}

Отправка с заголовком Idempotency-Key (безопасный повтор при сетевом сбое)

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/send" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f9c2b7e-8b7a-4e2d-9c3f-7a1b2c3d4e5f" \
  -d '{
    "channel_id": 3,
    "recipient": "+77001234567",
    "message_content": "Здравствуйте! Ваш заказ №1045 передан в доставку."
  }'

Ответ

json
{
  "success": true
}

Сообщение, начатое из рекламы (referral)

Запрос

bash
curl -X GET "https://api.aisar.app/v1/messages/1006" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "item": {
      "id": 1006,
      "conversation_id": 22,
      "thread_id": 48,
      "channel_id": 9,
      "direction": "inbound",
      "message_type": "text",
      "message_content": { "text": "Здравствуйте, интересует товар из рекламы" },
      "referral": {
        "source_type": "ad",
        "source_id": "120210000000000",
        "headline": "Летняя коллекция — скидка 20%",
        "image_url": "https://scontent.xx.fbcdn.net/example.jpg",
        "post_id": "17900000000000000",
        "ref": "summer_sale"
      },
      "created_at": "2026-02-09T18:00:00Z"
    }
  }
}

Отправка заблокированному контакту отклонена (422)

Запрос

bash
curl -X POST "https://api.aisar.app/v1/messages/send" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": 3,
    "recipient": "+77001234567",
    "message_content": "Здравствуйте!"
  }'

Ответ

json
{
  "message": "This contact is blocked; the message was not sent.",
  "error_code": "contact_blocked"
}