Сообщения
Сообщения — основная единица переписки внутри треда. 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_url | URL объявления или поста; только WhatsApp |
referral.source_type | ad или post |
referral.source_id | ID объявления/поста (для Instagram — ad_id) |
referral.headline | Заголовок объявления |
referral.body | Текст объявления; только WhatsApp |
referral.ctwa_clid | Клик-ID для точной атрибуции; только WhatsApp |
referral.media_type | image или video; только WhatsApp |
referral.image_url | URL изображения объявления (если есть) |
referral.video_url | URL видео объявления (если есть) |
referral.post_id | ID поста, из которого крутилось объявление; только 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). | Отправить одобренный WhatsApp-шаблон по каналу и получателю (или thread_id). |
| GET | /v1/messagesСписок сообщений с фильтрами (диалог/тред/канал/направление) и постраничной либо курсорной (before_message_id/after_message_id) пагинацией. | Список сообщений с фильтрами (диалог/тред/канал/направление) и постраничной либо курсорной (before_message_id/after_message_id) пагинацией. |
| GET | /v1/messages/searchПолнотекстовый поиск сообщений (`conversation_id` опционален — без него поиск идёт по всей компании). | Полнотекстовый поиск сообщений (`conversation_id` опционален — без него поиск идёт по всей компании). |
| POST | /v1/messagesСоздать сообщение напрямую, в т.ч. цитируемый ответ через quoted_message_id (обычно вместо этого используется /messages/send). | Создать сообщение напрямую, в т.ч. цитируемый ответ через quoted_message_id (обычно вместо этого используется /messages/send). |
| GET | /v1/messages/{message}Получить сообщение по id. | Получить сообщение по id. |
| PATCH | /v1/messages/{message}Отредактировать текст своего исходящего сообщения (в пределах окна редактирования канала). | Отредактировать текст своего исходящего сообщения (в пределах окна редактирования канала). |
| DELETE | /v1/messages/{message}Удалить сообщение (мягкое удаление; mode=revoke в JSON-теле — отозвать у получателя там, где канал поддерживает, кроме whatsapp_business). | Удалить сообщение (мягкое удаление; mode=revoke в JSON-теле — отозвать у получателя там, где канал поддерживает, кроме whatsapp_business). |
| GET | /v1/messages/{message}/aroundОкно сообщений вокруг конкретного сообщения (для прыжка к контексту); с 2026-08-22 под тем же усиленным лимитом, что и /messages/search (throttle:api-token-heavy). | Окно сообщений вокруг конкретного сообщения (для прыжка к контексту); с 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). | История статусов доставки (sent/delivered/read/failed). |
| POST | /v1/messages/{message}/statusesЗаписать статус доставки сообщения. | Записать статус доставки сообщения. |
| GET | /v1/messages/{message}/reactionsСписок реакций (эмодзи) на сообщение. | Список реакций (эмодзи) на сообщение. |
| POST | /v1/messages/{message}/reactionsПоставить реакцию {reaction:"<эмодзи>"} на сообщение от имени компании (одна исходящая реакция на сообщение — новая заменяет прежнюю). | Поставить реакцию {reaction:"<эмодзи>"} на сообщение от имени компании (одна исходящая реакция на сообщение — новая заменяет прежнюю). |
| DELETE | /v1/messages/{message}/reactionsСнять исходящую реакцию компании с сообщения (идемпотентно; id реакции не нужен — она одна на сообщение). | Снять исходящую реакцию компании с сообщения (идемпотентно; id реакции не нужен — она одна на сообщение). |
| POST | /v1/messages/{message}/likeЛайкнуть комментарий Instagram (только комментарийные треды Instagram Business; у сообщения должен быть внешний id). | Лайкнуть комментарий Instagram (только комментарийные треды Instagram Business; у сообщения должен быть внешний id). |
| POST | /v1/messages/{message}/forwardПереслать сообщение (copy-send) в существующий тред или кросс-канально по контакту. | Переслать сообщение (copy-send) в существующий тред или кросс-канально по контакту. |
| POST | /v1/messages/{message}/attachments/{attachment}/fetchДогрузить медиа вложения из рантайма/Instagram Graph API в локальное хранилище (lazy-load); для нескачиваемых типов (template/ephemeral/unsupported_type) — 422 attachment_not_fetchable. | Догрузить медиа вложения из рантайма/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-превью изображения вместо оригинала. | Отдать (streaming) медиа вложения из локального хранилища; необязательный подписанный w=256|640 отдаёт webp-превью изображения вместо оригинала. |
| POST | /v1/uploadsЗагрузить файл (до 64 МБ) и получить ссылку на медиа для прикрепления к сообщению. | Загрузить файл (до 64 МБ) и получить ссылку на медиа для прикрепления к сообщению. |
| POST | /v1/uploads/voiceЗагрузить голосовую заметку: транскодирует аудио/видео в voice-OGG (Opus) и возвращает media_id для прикрепления как голосовое сообщение. | Загрузить голосовую заметку: транскодирует аудио/видео в voice-OGG (Opus) и возвращает media_id для прикрепления как голосовое сообщение. |
Примеры
Отправка текстового сообщения
Запрос
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 передан в доставку."
}'Ответ
{
"success": true
}Отправка стикера из библиотеки
Запрос
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 }
]
}'Ответ
{
"success": true
}Цитируемый ответ (reply)
Запрос
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
}'Ответ
{
"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"
}
}
}Список сообщений диалога
Запрос
curl -X GET "https://api.aisar.app/v1/messages?conversation_id=15&perPage=20" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/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)
Запрос
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"Ответ
{
"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 }
}
}Поиск сообщений в диалоге
Запрос
curl -X GET "https://api.aisar.app/v1/messages/search?conversation_id=15&q=заказ" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/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)
Запрос
curl -X GET "https://api.aisar.app/v1/messages/search?q=заказ" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/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
}
}Окно сообщений вокруг конкретного
Запрос
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"Ответ
{
"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
}
}Загрузка файла для вложения
Запрос
curl -X POST "https://api.aisar.app/v1/uploads" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "file=@invoice-1045.pdf"Ответ
{
"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"
}Прикрепить загруженный файл к сообщению
Запрос
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)
Запрос
curl -X POST "https://api.aisar.app/v1/messages/1001/attachments/55/fetch" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/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"
}
]
}
}
}Редактирование исходящего сообщения
Запрос
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 уже в пути." }'Поставить реакцию на сообщение
Запрос
curl -X POST "https://api.aisar.app/v1/messages/1001/reactions" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reaction": "👍" }'Ответ
{
"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"
}
}
}Снять реакцию с сообщения
Запрос
curl -X DELETE "https://api.aisar.app/v1/messages/1001/reactions" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"message": "Message reaction removed."
}
}Удаление с отзывом у получателя (mode в теле)
Запрос
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": "Отправлено по ошибке" }'Ответ
{
"data": {
"message": "Message deleted.",
"item": {
"id": 1001,
"direction": "outbound",
"is_deleted": true,
"can_revoke_until": null,
"can_edit_until": null
}
}
}Отзыв не поддержан каналом whatsapp_business (422)
Запрос
curl -X DELETE "https://api.aisar.app/v1/messages/1001" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "mode": "revoke" }'Ответ
{
"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."
}Форвард в существующий тред
Запрос
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 }'Ответ
{
"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"
}
}
}Кросс-канальный форвард по контакту
Запрос
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
}'Ответ
{
"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)
Запрос
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 }'Ответ
{
"message": "Несколько подключённых каналов подходят этому контакту — укажите target_channel_id.",
"error_code": "channel_required"
}Загрузка голосовой заметки
Запрос
curl -X POST "https://api.aisar.app/v1/uploads/voice" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "file=@voice-note.webm"Ответ
{
"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
Запрос
curl -X POST "https://api.aisar.app/v1/messages/1001/like" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"message": "Comment liked."
}Отправка с заголовком Idempotency-Key (безопасный повтор при сетевом сбое)
Запрос
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 передан в доставку."
}'Ответ
{
"success": true
}Сообщение, начатое из рекламы (referral)
Запрос
curl -X GET "https://api.aisar.app/v1/messages/1006" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/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)
Запрос
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": "Здравствуйте!"
}'Ответ
{
"message": "This contact is blocked; the message was not sent.",
"error_code": "contact_blocked"
}