Рассылки
Массовые рассылки сообщений сегменту контактов по одному или нескольким каналам (WhatsApp/Telegram/Instagram). Группа покрывает весь жизненный цикл: создание черновика, публикацию и запуск(и), A/B-варианты, контроль скорости и окно отправки, отписки, отслеживание кликов, управление запусками и повторную отправку неудавшимся получателям. Все эндпоинты требуют активную подписку и фичу плана broadcasts.
Жизненный цикл и расписание
Рассылка может быть черновиком (is_draft: true) и позднее запускаться одним из способов:
- немедленно —
schedule.mode = now; - по расписанию —
later+scheduledAt; - по recurring-правилу —
recurrence.
Жизненный цикл: draft → publish → запуск(и) (run); каждый запуск отслеживает свой прогресс отдельно (sent/delivered/read/failed/skipped).
Статус рассылки (status): draft → scheduled/active → completed либо failed (cancelled при отмене). Для одноразовой рассылки (без recurring-расписания) статус failed выставляется, когда её запуск завершается с провалом или пропуском всех получателей — это же значение принимается фильтром statuses[] в GET /broadcasts. Повторная отправка неудавшимся получателям (retry-failed) возвращает такую рассылку обратно в scheduled.
A/B-варианты
Контент канала поддерживает A/B-варианты (2–3 на канал, content.channels[].variants): вариант назначается получателю детерминированно, разбивка статистики — в stats.byVariant.
Скорость отправки и окно
Скорость отправки регулируется через schedule.rateControl (messagesPerMinute, jitter, dailyLimit, skipContactedWithinHours) и окно sendWindow (режим smart шлёт каждому получателю в его исторически активный час).
Отписки (opt-out)
Контакты с opted_out_at всегда исключаются из аудитории; входящее сообщение-стоп-слово (stop/стоп/unsubscribe/отписаться) автоматически ставит opt-out. Так же безусловно исключаются заблокированные контакты (blocked_at) и — с 2026-09-08 — скрытые из рабочего inbox (hidden_from_inbox_at, см. POST /v1/contacts/{contact}/hide-from-inbox).
Отслеживание кликов
При content.settings.trackClicks = true ссылки переписываются на трекинговые /r/{code} (публичный редирект без аутентификации учитывает клик и делает 302 на оригинал).
Управление запусками
Запуски можно ставить на паузу/возобновлять/отменять (pending-получатели → skipped), а неудавшихся получателей — отправлять повторно (retry-failed).
С 2026-09-26 в списке получателей (GET /broadcasts/{broadcast}/messages) поле identifier (телефон или username) маскируется до последних четырёх символов (•••0187), если смотрящий не запускал рассылку и не имеет обзора всей компании (администратор, менеджер без ограничений channel_access); идентификаторы записей не меняются.
Валидация шаблона перед отправкой
Для записей content.channels[] с type = "template" перед стартом отправки проверяется, что на канале есть approved-версия выбранного шаблона. Проверка выполняется на трёх точках входа: POST /broadcasts (когда schedule.mode = "now"), POST /{broadcast}/run и POST /{broadcast}/publish. Если approved-шаблона нет, запрос отклоняется с 422, и errors.content[0] содержит сообщение вида Channel "..." has no approved template "..." (language ru). Approve this template for the channel or pick a channel where it is approved.
Кроме того, проверяется, что если шаблон содержит медиа-заголовок (image/video/document), то хотя бы один из источников — файл или URL — загружен. При отсутствии файла и URL запрос отклоняется с 422, и errors.content[0] содержит: Template "..." has a media header (...) but no file is uploaded. Upload the file in the template settings.
AI-помощник по тексту
AI-помощник текста (POST /broadcasts/assist, generate/improve) делит лимит 60/час с прочим AI-ассистом.
Отправка на номер без контакта (статический сегмент)
С 2026-09-09 получатель статического сегмента без привязанного контакта — просто номер телефона, audience.members[].identifier (см. группу segments) — на WhatsApp-канале (whatsapp_business или личный whatsapp) не отправляется «мимо» CRM: перед отправкой AISAR создаёт контакт, диалог и тред на этом канале, сообщение сохраняется как обычное исходящее и появляется в Чате, а провайдерские статусы (delivered/read/failed с причиной) отражаются на получателе рассылки (поле error_message в GET /{broadcast}/messages) и в статистике прогона. Имя из списка (display_name) присваивается только вновь созданному контакту — уже существующий контакт своё имя не теряет. Для отписавшихся (opted_out_at) и заблокированных (blocked_at) контактов диалог не создаётся — получатель помечается skipped. Такие диалоги не создают сделку и не запускают автоматизацию «диалог открыт» — рассылка на 500 номеров не должна породить 500 сделок или 500 срабатываний автоматизации.
Проверка WABA перед запуском (preflight)
С 2026-09-09 перед стартом каждого прогона на WhatsApp Business API канале AISAR запрашивает у Meta health_status.can_send_message. Если ответ BLOCKED (например, проблема со способом оплаты — код 141006), прогон завершается статусом failed без единой отправки: причина (описание ошибки Meta и её код) записывается в лог рассылки (logs[], событие run_failed) и в журнал канала кодом broadcast.preflight_blocked (см. группу channels, раздел «Коды сбоя отправки в журнале канала»). Если Graph API недоступен или у канала нет сохранённых WABA-учётных данных, проверка молча пропускается и прогон идёт как обычно (fail-open) — она не должна становиться новым способом потерять рассылку.
Вложения в контенте канала
С 2026-09-23 вложение рассылки (content.channels[].attachments[]) принимается только как ссылка на файл, который ваша компания загрузила через POST /v1/uploads (см. группу messages): передайте его media_id либо пару storage_disk/storage_path из ответа загрузки. Поля url и media_asset_id в запросе запрещены (422). На один канал контента допускается не больше 10 вложений, как и у одного сообщения в чате. Правила одинаковы для POST /v1/broadcasts и PATCH /v1/broadcasts/{broadcast} (включая его POST-алиас). Поля вложения:
media_id: идентификатор файла из ответаPOST /v1/uploads(видаmed_...);storage_disk+storage_path: альтернативаmedia_id, пара из того же ответа;id: идентификатор вложения внутри рассылки (строка до 64 символов), по нему вложение узнаётся при повторном сохранении;type:image,video,audio,voice,document,fileилиother;nameилиfilename,mimeTypeилиmime_type,sizeBytesилиsize: отображаемые метаданные; слишком длинное имя обрезается, а не отклоняется.
Каждый media_id/storage_path ищется среди загрузок вашей компании. Файл другой компании, несуществующий файл или путь без записи о загрузке дают 422 с ключом content.channels.N.attachments.M в errors и сообщением Attachment source is invalid or does not belong to the current company. Вложение без источника тоже отклоняется с 422 по тому же ключу (Attachment has no uploaded file: upload it first and send its media_id.), кроме повторной отправки уже сохранённого вложения по id (см. ниже).
В сохранённом content вложение хранит только media_asset_id и метаданные (id, type, name, mimeType, sizeBytes), без пути, диска и media_id; в таком виде его и возвращает GET /v1/broadcasts/{broadcast}. Загрузки, на которые ссылается рассылка, закрепляются: их expires_at сбрасывается в null, и плановая очистка файлов их больше не удаляет, поэтому черновик или рассылка по расписанию не теряют файл до отправки.
При обновлении рассылки уже сохранённое вложение можно прислать только по id, без media_id/storage_path: оно сохранит свой файл. id, которого у рассылки нет, без источника даёт 422. Сохранённое ранее вложение, у которого файла не было вовсе, при таком пересохранении удаляется из content без ошибки.
При отправке тип и MIME-тип вложения берутся из самого загруженного файла, а не из присланных type/mimeType (исключение: voice на аудиофайле остаётся голосовым); имя файла берётся из name. Если к моменту отправки файла вложения в компании уже нет, сообщение без него не уходит: получатель помечается failed с error_message = "attachment_unavailable" (видно в GET /v1/broadcasts/{broadcast}/messages), а в logs[] рассылки один раз на запуск добавляется запись с event: "attachment_unavailable". Вложения, сохранённые до 2026-09-23 с путём вместо media_asset_id, при отправке перепроверяются: путь вне файлов вашей компании отбрасывается, и сообщение уходит без этого вложения.
Вложения тестовой отправки
POST /v1/broadcasts/test принимает файлы напрямую, в multipart-поле attachments[], а не ссылками на загрузки: не больше 10 файлов, каждый до 10 МБ, с теми же расширениями, что и POST /v1/uploads (расширение сверяется с MIME-типом, определённым по содержимому файла); иначе ответ 422. Файлы тестовой отправки хранятся приватно, среди загрузок вашей компании, и живут 24 часа: после этого их удаляет плановая очистка файлов, а рассылка их не закрепляет. Файл с тем же содержимым, уже загруженный компанией, повторно не сохраняется, используется существующий. Если тот же файл потом загрузить через POST /v1/uploads, он становится обычной загрузкой с обычным сроком хранения, и на его media_id можно ссылаться в рассылке.
Каналы, которые не пишут первыми
Канал с can_start_conversation: false (сейчас это только Telegram Business, см. группу «Каналы») в рассылках не участвует: написать клиенту первым он не может. Явный выбор такого канала в channels.channelIds или в content.channels[].channelId при создании и изменении рассылки отклоняется с 422 и ошибкой валидации по полю channels.channelIds (в тексте перечислены id отвергнутых каналов). В режиме last_interacted треды таких каналов пропускаются, и для получателя выбирается его последний тред на другом канале, если такой есть. Включить такому каналу allow_broadcasts через PATCH /channels/{channel} тоже нельзя, ответ 422.
Эндпоинты
| Метод | Путь | Описание |
|---|---|---|
| GET | /v1/broadcasts/metaМетаданные конструктора рассылки (сегменты, переменные, шаблоны) | Метаданные конструктора рассылки (сегменты, переменные, шаблоны) |
| GET | /v1/broadcastsСписок рассылок (пагинация, фильтр по статусу) | Список рассылок (пагинация, фильтр по статусу) |
| GET | /v1/broadcasts/{broadcast}Получение рассылки | Получение рассылки |
| POST | /v1/broadcastsСоздание рассылки (поддерживает is_draft) | Создание рассылки (поддерживает is_draft) |
| PATCH | /v1/broadcasts/{broadcast}Обновление рассылки | Обновление рассылки |
| POST | /v1/broadcasts/{broadcast}Обновление рассылки (POST-алиас PATCH) | Обновление рассылки (POST-алиас PATCH) |
| DELETE | /v1/broadcasts/{broadcast}Удаление рассылки | Удаление рассылки |
| POST | /v1/broadcasts/estimateОценка размера аудитории рассылки | Оценка размера аудитории рассылки |
| POST | /v1/broadcasts/{broadcast}/duplicateДублирование рассылки | Дублирование рассылки |
| POST | /v1/broadcasts/{broadcast}/publishПубликация черновика (запуск/постановка на расписание) | Публикация черновика (запуск/постановка на расписание) |
| POST | /v1/broadcasts/{broadcast}/cancelОтмена разовой рассылки | Отмена разовой рассылки |
| POST | /v1/broadcasts/{broadcast}/runРучной запуск рассылки | Ручной запуск рассылки |
| GET | /v1/broadcasts/{broadcast}/messagesПолучатели запуска (пагинация, фильтр по статусу) | Получатели запуска (пагинация, фильтр по статусу) |
| GET | /v1/broadcasts/{broadcast}/runsСписок запусков рассылки | Список запусков рассылки |
| GET | /v1/broadcasts/{broadcast}/runs/{run}Получение конкретного запуска | Получение конкретного запуска |
| POST | /v1/broadcasts/{broadcast}/pause-recurringПауза recurring-расписания | Пауза recurring-расписания |
| POST | /v1/broadcasts/{broadcast}/resume-recurringВозобновление recurring-расписания | Возобновление recurring-расписания |
| POST | /v1/broadcasts/testОтправка тестового сообщения конкретному получателю | Отправка тестового сообщения конкретному получателю |
| POST | /v1/broadcasts/assistAI-помощник по тексту рассылки (generate/improve) | AI-помощник по тексту рассылки (generate/improve) |
| POST | /v1/broadcasts/{broadcast}/runs/{run}/pauseПауза запуска | Пауза запуска |
| POST | /v1/broadcasts/{broadcast}/runs/{run}/resumeВозобновление запуска | Возобновление запуска |
| POST | /v1/broadcasts/{broadcast}/runs/{run}/cancelОтмена запуска (pending-получатели → skipped) | Отмена запуска (pending-получатели → skipped) |
| POST | /v1/broadcasts/{broadcast}/runs/{run}/retry-failedПовторная отправка только failed-получателям | Повторная отправка только failed-получателям |
Примеры
Создание и немедленный запуск рассылки
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "February promo",
"is_draft": false,
"audience": { "segmentId": "12" },
"channels": { "mode": "specific", "channelIds": [7] },
"content": {
"channels": [
{ "type": "text", "channelId": 7, "accountType": "whatsapp", "text": "Hi {{first_name}}, 20% off this week!" }
]
},
"schedule": { "mode": "now" }
}'Ответ
{
"data": {
"id": 88,
"company_id": 1,
"name": "February promo",
"status": "active",
"audience": {
"conditions": [],
"group_logic": "and"
},
"channels": [{ "id": 7, "mode": "specific" }],
"content": [],
"schedule": { "mode": "now", "scheduledAt": null },
"recurring_schedule": null,
"runs": [
{ "id": 201, "broadcast_id": 88, "status": "processing", "total": 340, "sent": 0, "failed": 0, "delivered": 0, "read": 0, "started_at": "2026-03-15T10:00:00.000000Z", "completed_at": null }
],
"created_at": "2026-03-15T10:00:00.000000Z",
"updated_at": "2026-03-15T10:00:00.000000Z"
}
}Оценка аудитории рассылки
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts/estimate \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"segment_id": 12,
"channel_ids": [7]
}'Ответ
{
"data": {
"total_contacts": 340,
"reachable_contacts": 312,
"excluded_contacts": 28,
"estimated_duration_seconds": 312,
"estimated_completion": "2026-03-15T10:05:12.000Z"
}
}Черновик с A/B-вариантами, контролем скорости и окном отправки
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Spring reactivation",
"is_draft": true,
"audience": { "segmentId": "12" },
"channels": { "mode": "specific", "channelIds": [7] },
"content": {
"channels": [
{
"type": "text",
"channelId": 7,
"accountType": "whatsapp",
"text": "Hi {{first_name}}, we miss you!",
"variants": [
{ "key": "A", "text": "Hi {{first_name}}, we miss you — here is 15% off." },
{ "key": "B", "text": "{{first_name}}, come back for 15% off this week." }
]
}
],
"settings": {
"trackClicks": true,
"includeOptOut": true,
"optOutText": "Reply STOP to unsubscribe"
}
},
"schedule": {
"mode": "later",
"scheduledAt": "2026-04-01T09:00:00Z",
"rateControl": {
"enabled": true,
"messagesPerMinute": 30,
"jitterMinSeconds": 2,
"jitterMaxSeconds": 8,
"dailyLimit": 5000,
"skipContactedWithinHours": 48
},
"sendWindow": {
"enabled": true,
"mode": "smart",
"startTime": "09:00",
"endTime": "20:00",
"timezone": "recipient"
}
}
}'Ответ
{
"data": {
"id": 89,
"company_id": 1,
"name": "Spring reactivation",
"status": "draft",
"audience": { "conditions": [], "group_logic": "and" },
"channels": [{ "id": 7, "mode": "specific" }],
"content": [],
"schedule": { "mode": "later", "scheduledAt": "2026-04-01T09:00:00.000000Z" },
"recurring_schedule": null,
"runs": [],
"created_at": "2026-03-20T12:00:00.000000Z",
"updated_at": "2026-03-20T12:00:00.000000Z"
}
}Публикация черновика (draft → scheduled)
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts/89/publish \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"id": 89,
"status": "scheduled",
"schedule": { "mode": "later", "scheduledAt": "2026-04-01T09:00:00.000000Z" },
"runs": [],
"updated_at": "2026-03-20T12:05:00.000000Z"
}
}Ошибка: на канале нет approved-шаблона
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts/90/run \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"message": "Channel \"WhatsApp Main\" has no approved template \"spring_promo\" (language ru). Approve this template for the channel or pick a channel where it is approved.",
"errors": {
"content": [
"Channel \"WhatsApp Main\" has no approved template \"spring_promo\" (language ru). Approve this template for the channel or pick a channel where it is approved."
]
}
}
}Тестовое сообщение перед запуском
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts/test \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": 7,
"recipient": "+12025550102",
"content": { "type": "text", "text": "Test: 15% off this week!" }
}'Ответ
{
"data": {
"message": "Test message queued.",
"message_id": "b1f2c3d4-EXAMPLE"
}
}AI-помощник по тексту рассылки
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts/assist \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "improve",
"text": "hey buy our stuff now big sale",
"locale": "en"
}'Ответ
{
"data": {
"text": "Big news, {{first_name}} — our best sale of the season is live. Save 20% this week only.",
"action": "improve",
"tokens_used": 128
}
}Получатели запуска (фильтр по статусу failed)
Запрос
curl "https://api.aisar.app/v1/broadcasts/89/messages?status=failed&per_page=2" \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": [
{
"id": 7001,
"contact_id": 123,
"identifier": "+7700000XXXX",
"status": "failed",
"error_message": "recipient_not_on_whatsapp",
"attempts": 2,
"variant_key": "A",
"delivered_at": null,
"read_at": null,
"replied_at": null,
"clicked_at": null,
"contact": { "id": 123, "display_name": "Aylin Weber", "first_name": "Aylin", "last_name": "Weber" },
"channel": { "id": 7, "name": "WhatsApp Main" }
},
{
"id": 7002,
"contact_id": 124,
"identifier": "+7700000YYYY",
"status": "failed",
"error_message": "message_send_timeout",
"attempts": 3,
"variant_key": "B",
"delivered_at": null,
"read_at": null,
"replied_at": null,
"clicked_at": null,
"contact": { "id": 124, "display_name": "Marco Ricci", "first_name": "Marco", "last_name": "Ricci" },
"channel": { "id": 7, "name": "WhatsApp Main" }
}
],
"meta": { "current_page": 1, "last_page": 1, "per_page": 2, "total": 2 }
}Повторная отправка неудавшимся получателям
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts/89/runs/201/retry-failed \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"id": 201,
"broadcast_id": 89,
"status": "processing",
"total": 340,
"sent": 300,
"failed": 0,
"delivered": 280,
"read": 190,
"started_at": "2026-04-01T09:00:00.000000Z",
"completed_at": null
}
}Прогон остановлен preflight-проверкой (WABA заблокирована)
Запрос
curl -X GET https://api.aisar.app/v1/broadcasts/90 \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"id": 90,
"status": "failed",
"logs": [
{
"level": "warning",
"event": "run_failed",
"message": "WhatsApp Business account \"WhatsApp Main\" cannot send messages: There has been an error processing your payment method. (Meta error 131042)",
"timestamp": "2026-09-09T08:15:44.000000Z"
}
],
"runs": [
{ "id": 305, "broadcast_id": 90, "status": "failed", "total": 0, "sent": 0, "failed": 0, "delivered": 0, "read": 0, "started_at": "2026-09-09T08:15:44.000000Z", "completed_at": "2026-09-09T08:15:44.000000Z" }
],
"updated_at": "2026-09-09T08:15:44.000000Z"
}
}Рассылка с вложением из POST /v1/uploads
Запрос
curl -X POST https://api.aisar.app/v1/broadcasts \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Price list",
"is_draft": true,
"audience": { "segmentId": "12" },
"channels": { "mode": "specific", "channelIds": [7] },
"content": {
"channels": [
{
"type": "text",
"channelId": 7,
"accountType": "whatsapp",
"text": "Hi {{first_name}}, here is our new price list.",
"attachments": [
{ "id": "att_1", "type": "document", "name": "price-list.pdf", "media_id": "med_9f3a2b7c" }
]
}
]
},
"schedule": { "mode": "later", "scheduledAt": null }
}'Ответ
{
"data": {
"id": 91,
"company_id": 1,
"name": "Price list",
"status": "draft",
"content": {
"channels": [
{
"type": "text",
"channelId": 7,
"accountType": "whatsapp",
"text": "Hi {{first_name}}, here is our new price list.",
"attachments": [
{ "id": "att_1", "type": "document", "name": "price-list.pdf", "media_asset_id": 5120 }
]
}
]
},
"runs": [],
"created_at": "2026-09-23T10:00:00.000000Z",
"updated_at": "2026-09-23T10:00:00.000000Z"
}
}Метаданные конструктора рассылки
Запрос
curl "https://api.aisar.app/v1/broadcasts/meta" \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"segments": [
{ "id": "12", "name": "Active customers", "size": 340, "channels": [7] }
],
"variables": [
{ "key": "first_name", "displayName": "First name" },
{ "key": "order_number", "displayName": "Order number" }
],
"templates": [
{
"id": "15",
"name": "Appointment reminder",
"category": "utility",
"content": "Здравствуйте, {{first_name}}! Напоминаем о записи {{appointment_at}}.",
"channel_type": "whatsapp_business",
"status": "published",
"variables": ["first_name", "appointment_at"],
"header_enabled": true,
"header_type": "text",
"header_media_url": null,
"header_media": null
},
{
"id": "20",
"name": "Promotion with image",
"category": "marketing",
"content": "Здравствуйте, {{first_name}}! Ознакомьтесь с нашим новым каталогом.",
"channel_type": "whatsapp_business",
"status": "published",
"variables": ["first_name"],
"header_enabled": true,
"header_type": "image",
"header_media_url": null,
"header_media": {
"media_id": "med_01arc7z0z0000000001234567",
"filename": "catalog-march-2026.jpg",
"mime_type": "image/jpeg",
"size": 2457600,
"preview_url": "https://api.aisar.app/v1/media/template-header/01ARC7Z0Z0000000001234567?company=42&expires=1791180000&signature=…"
}
}
]
}
}Ошибка: файл вложения не принадлежит компании
Запрос
curl -X PATCH https://api.aisar.app/v1/broadcasts/91 \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"content": {
"channels": [
{
"type": "text",
"channelId": 7,
"accountType": "whatsapp",
"text": "Hi {{first_name}}, here is our new price list.",
"attachments": [
{ "id": "att_2", "type": "document", "name": "report.pdf", "storage_disk": "local", "storage_path": "99/attachments/0b7e5d1a-3c2f-4a8e-9d10-2e3f4a5b6c7d.pdf" }
]
}
]
}
}'Ответ
{
"data": {
"message": "Attachment source is invalid or does not belong to the current company.",
"errors": {
"content.channels.0.attachments.0": [
"Attachment source is invalid or does not belong to the current company."
]
}
}
}