Sending messages from your system
A complete guide to sending messages from an external system (an AI assistant, a chatbot, a CRM): a step-by-step walkthrough (find a contact, pick a channel, send a text or a WhatsApp template, receive replies over a webhook) plus a detailed endpoint reference — channels, media and voice uploads, sending text and templates.
A typical integration — an AI assistant, a chatbot, a CRM, or an automation script — needs to send outbound messages through channels connected to AISAR (WhatsApp, Telegram, Instagram, etc.) and receive replies from contacts. This walkthrough takes you from a token to a full "sent → received a reply" loop, and ends with a detailed reference for each sending endpoint.
Every request uses the company's Bearer token (see the Authentication guide) and runs in that company's context:
Authorization: Bearer YOUR_API_TOKENStep 1 — pick a channel
Fetch the company's channels to find the channel_id you'll send through:
curl "https://api.aisar.app/v1/channels?status=connected" \
-H "Authorization: Bearer YOUR_API_TOKEN"{
"data": [
{
"id": 57,
"name": "WhatsApp test",
"account_name": "+77001234567",
"status": "connected",
"channel_type": { "id": 1, "key": "whatsapp", "name": "WhatsApp" },
"message_operations": { "can_create": true },
"limits": { "max_text_length": 4096 }
}
]
}Before sending, check that status === "connected" and message_operations.can_create === true. The full list of query params and response fields for /channels is in the "GET /channels" section below.
Step 2 — find or create a contact (optional)
Looking up or creating a contact ahead of time is not required: /messages/send finds an existing conversation by recipient or creates a new contact, thread and conversation on its own. Look a contact up in advance only if you need to check whether it already exists, or to attach extra fields (email, tags, etc.) before the first send.
curl "https://api.aisar.app/v1/contacts?search=%2B77001234567" \
-H "Authorization: Bearer YOUR_API_TOKEN"curl -X POST https://api.aisar.app/v1/contacts \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Иван Иванов",
"phones": [{ "phone": "+77001234567", "type": "mobile" }]
}'Step 3 — send a text message
curl -X POST https://api.aisar.app/v1/messages/send \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": 57,
"recipient": "+77001234567",
"message_content": "Здравствуйте! Чем могу помочь?"
}'{
"success": true
}recipient is the recipient identifier in the format the channel expects: an E.164 number for WhatsApp/SMS, a username or chat_id for Telegram Bot, an IGSID for Instagram. To send to a WhatsApp group, pass the group JID (<id>@g.us) — the @g.us suffix is recognized automatically.
conversation.create permission — without it the response is 403 Forbidden.To attach media, first upload the file via POST /v1/uploads (get a media_id), then pass it in attachments:
curl -X POST https://api.aisar.app/v1/messages/send \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": 57,
"recipient": "+77001234567",
"message_content": "Смотрите фото",
"attachments": [{ "media_id": "med_01HZABCDEFGHIJKLMNOPQRSTUV" }]
}'Upload details are in the "POST /uploads" section (regular files, idempotency, broadcasting one media asset to many contacts) and "POST /uploads/voice" (voice notes) below. The full field list for /messages/send (is_group, is_silent, attachment limits) is in the "POST /messages/send" section.
Step 4 — send a WhatsApp template and the 24-hour window
WhatsApp Business does not allow sending arbitrary messages to a contact who hasn't messaged you in the last 24 hours — the so-called "customer service window". To start a conversation outside that window, or to run a broadcast, use a Meta-approved template via /messages/send-template.
First, fetch the list of approved templates:
curl "https://api.aisar.app/v1/templates?channel_id=57&metaStatuses[]=approved" \
-H "Authorization: Bearer YOUR_API_TOKEN"Only a template with meta_status: "approved" can be sent. Then send it to the recipient:
curl -X POST https://api.aisar.app/v1/messages/send-template \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": 57,
"recipient": "+77001234567",
"template_id": 42,
"params": { "name": "Иван", "order_id": "A-100" }
}'{
"success": true,
"message_id": 12345
}params fills the template's variables: as a named map ({{name}}) or by position — an array or a map with numeric keys ({{1}}, {{2}}). The same conversation.create permission is required; the channel must be of type whatsapp_business. The full template parameter reference (/templates filters, the header_media_url, thread_id, is_silent fields, the display structure and quick-reply buttons) is in the "GET /templates" and "POST /messages/send-template" sections below.
Step 5 — receive replies over a webhook
To react to a contact's replies (a conversational flow, e.g. with an AI assistant), subscribe to the message.created event — see the Webhooks guide. A typical loop: receive message.created with direction: "inbound" → process the text → call /messages/send or /messages/send-template to reply.
After every send (text or template), subscribed webhook integrations receive message.created with direction: "outbound", followed by message.status.updated as the delivery status progresses (sent → delivered → read, or failed).
When a contact taps a quick-reply button on a sent template, the tap arrives as a regular inbound message with type: "text" and the button's label — handle it the same way as any other message.created event.
Full cycle
1. Внешняя система → GET /v1/channels
(узнать channel_id)
2. Внешняя система → POST /v1/messages/send
{ channel_id, recipient, message_content }
3. AISAR создаёт/находит диалог, создаёт сообщение
4. AISAR → POST {webhook_url} (event: message.created, direction: outbound)
5. Коннектор доставляет сообщение в мессенджер
6. AISAR → POST {webhook_url} (event: message.status.updated)
7. Контакт отвечает → AISAR → POST {webhook_url} (event: message.created, direction: inbound)
8. Внешняя система обрабатывает ответ → снова POST /v1/messages/sendEndpoint reference
Below is a detailed reference for each sending endpoint: request fields, response shape, and edge cases. It complements the step-by-step walkthrough above; every request uses the same company Bearer token and runs in that company's context.
GET /channels
Fetch the channels available to the current user within the company — use it to find the channel_id you'll send through.
| Query param | Description |
|---|---|
type | Filter by channel type key: whatsapp, whatsapp_business, telegram, telegram_bot, instagram, instagram_business, facebook_messenger, tiktok, live_chat, sms |
status | Filter by status: connected, disconnected, paused, etc. |
setup_state | Filter by setup state: Draft, Setup, Active, Paused |
{
"data": [
{
"id": 57,
"name": "WhatsApp test",
"account_name": "+77084252291",
"status": "connected",
"message_operations": {
"can_create": true,
"can_edit": true,
"edit_window_seconds": 900,
"revoke_window_seconds": 172800
},
"limits": {
"max_text_length": 4096,
"file_limits": { "image": { "max_size_mb": 16 }, "video": { "max_size_mb": 64 } }
},
"channel_type": { "id": 1, "key": "whatsapp", "name": "WhatsApp" },
"broadcast_rate_limits": { "daily_limit": 1500, "messages_per_minute": 15 }
}
]
}Before sending, check that status === "connected" and message_operations.can_create === true.
POST /uploads
Uploads a file to AISAR and returns a compact media_id you then pass to /messages/send. Delivery to the messenger is handled by the connector without re-downloading. Two modes are supported.
Mode A — multipart upload
POST /v1/uploads
Authorization: Bearer YOUR_API_TOKEN
Content-Type: multipart/form-data
file=@photo.jpgMaximum 64 MB. Allowed extensions: jpg, jpeg, png, gif, webp, bmp, svg, mp4, mov, avi, wmv, webm, mkv, mp3, ogg, wav, aac, flac, m4a, opus, amr, pdf, doc, docx, xls, xlsx, csv, txt, rtf, ppt, pptx, zip, rar, 7z, gz, json, xml.
Mode B — upload by URL
POST /v1/uploads
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{ "url": "https://cdn.example.com/photo.jpg" }AISAR downloads the file once, stores it, and returns a media_id. The URL must be http/https, the size ≤ 64 MB, download timeout 20 sec.
{
"media_id": "med_01HZABCDEFGHIJKLMNOPQRSTUV",
"type": "image",
"filename": "photo.jpg",
"mime_type": "image/jpeg",
"size": 123456,
"width": null,
"height": null,
"duration": null,
"expires_at": "2026-05-16T10:30:00.000000Z"
}media_id; the file isn't duplicated in storage. A broadcast of one image to 100 contacts needs only one POST /uploads, even if you accidentally call it twice.Requires the conversation.view permission.
POST /uploads/voice
Uploads an audio recording and transcodes it to OGG/Opus — the format WhatsApp and Telegram render as a voice note rather than an attached file. This is the only correct way to send a voice message via the API: a plain /uploads call stores the file as-is (type audio), and the recipient gets an audio file, not a voice note.
POST /v1/uploads/voice
Authorization: Bearer YOUR_API_TOKEN
Content-Type: multipart/form-data
file=@voice.m4aAccepts audio/webm, audio/ogg, audio/mp4, audio/mpeg, audio/wav, audio/aac, audio/x-m4a (and video/webm — the browser-recording container). The server transcodes the recording to OGG/Opus itself.
{
"media_id": "med_01HZABCDEFGHIJKLMNOPQRSTUV",
"type": "audio",
"filename": "voice.ogg",
"mime_type": "audio/ogg",
"size": 24576,
"duration": 7,
"expires_at": "2026-05-16T10:30:00.000000Z"
}type field in the response is audio (it's a media asset). To make the message send as a voice note, set attachments[].type: "voice" when sending (see below). Voice notes are supported on WhatsApp (personal), WhatsApp Business, Telegram (personal), and Telegram Bot; on other channels the attachment goes out as a regular audio file. Requires the conversation.view permission.POST /messages/send
Sends an outbound message on behalf of a channel to a specific recipient. If the conversation doesn't exist yet, it will be created.
| Field | Type | Required | Description |
|---|---|---|---|
channel_id | integer | Yes | The sending channel ID (from /channels) |
recipient | string | Yes | The recipient identifier in the format the channel expects: an E.164 number, a Telegram username, an IGSID, etc. For WhatsApp groups — the group JID <id>@g.us. Max 255 chars. |
is_group | boolean | No | true — the recipient is a WhatsApp group. Not required if recipient is already in the <id>@g.us form (the suffix is recognized automatically). Defaults to false. |
is_silent | boolean | No | true — a "silent" send: the operator's unread counter is not reset. Defaults to false. Useful for programmatic/service messages. |
message_content | string | Conditional | Message text, max 65,535 chars (the channel's actual limit is in limits.max_text_length from /channels). Required if attachments is absent. |
attachments | array | Conditional | 1–10 attachments. Required if message_content is absent. Each item: media_id (required) + optional type (image/video/audio/voice/document/file). |
{
"channel_id": 42,
"recipient": "+79001234567",
"message_content": "Здравствуйте! Чем могу помочь?"
}{
"channel_id": 42,
"recipient": "120363398017778174@g.us",
"message_content": "Всем привет в группе!"
}Where to get the group identifier: the JID <id>@g.us arrives in inbound-message webhooks and is available in GET /v1/conversations — the group thread's threads[].contact_account.external_id (threads[].is_group: true). Constraints: groups are only supported on WhatsApp channels; the channel's account must be a member of the group; a contradictory request (recipient ends with @g.us but is_group: false) is rejected with 422.
{ "success": true }Behaviour. If a conversation with the recipient already exists on this channel it is reused; otherwise a new one is created (contact account, thread, conversation). After the message is created, subscribed webhook integrations receive message.created with direction: outbound. The endpoint creates the message synchronously, but delivery to the messenger runs asynchronously through the connector queue — the final delivery status arrives as message.status.updated. Requires the conversation.create permission, otherwise 403 Forbidden.
GET /templates
Fetches the company's message templates — use it to find Meta-approved WhatsApp Business templates and their id for /messages/send-template.
| Query param | Description |
|---|---|
channel_id | Filter by a specific WhatsApp Business channel |
metaStatuses[] | Meta approval status: approved, rejected, pending. For sendable templates use metaStatuses[]=approved |
channelType | Channel type key (e.g. whatsapp_business) |
language | BCP-47, e.g. en, ru |
search | Search by name/text/slug/labels |
sortBy / sortDir | name, status, created_at, updated_at, uses_count, language; asc/desc |
{
"data": {
"items": [
{
"id": 42,
"name": "order_update",
"meta_status": "approved",
"language": "en",
"body": "Здравствуйте, {{name}}! Ваш заказ {{order_id}} готов.",
"parameters": [
{ "name": "name", "type": "text", "sample_value": "Иван", "position": 1 },
{ "name": "order_id", "type": "text", "sample_value": "A-100", "position": 2 }
],
"channel_type": { "id": 1, "key": "whatsapp_business", "name": "WhatsApp Business" },
"channel": { "id": 57, "name": "WhatsApp test", "account_name": "+77084252291" }
}
],
"pagination": { "page": 1, "perPage": 15, "total": 1, "lastPage": 1 }
}
}meta_status is the Meta review status; only `approved` can be sent. body carries the placeholders ({{name}} or {{1}}) that tell you which params /messages/send-template needs. Requires the template.view permission.
template.approved and template.rejected webhook events — they fire when meta_status changes, letting you invalidate a cache.POST /messages/send-template
Sends an approved WhatsApp Business template to a recipient (recipient) or into an existing thread (thread_id) — provide exactly one of the two — for initiating conversations outside the 24-hour window and for broadcasts (call once per recipient). If a conversation with the recipient doesn't exist yet, it will be created.
| Field | Type | Required | Description |
|---|---|---|---|
channel_id | integer | Yes | Must be of type whatsapp_business |
recipient | string | Conditional (required_without:thread_id) | E.164 number, max 255 chars |
thread_id | integer | Conditional (required_without:recipient) | ID of an existing thread; an alternative to recipient — provide exactly one of the two (min: 1) |
template_id | integer | Yes | Template ID from /templates, must belong to the company and have meta_status = approved |
params | object/array | No | Template variable values (see below). Omit for templates without variables. |
header_media_url | string (url) | No | For templates with a media header (image/video/document) |
is_silent | boolean | No | Same as /messages/send — does not reset the unread counter |
The server builds the Meta components structure from the template definition for you. params values are passed one of two ways: a named map for named placeholders {{name}} ({ "name": "Иван", "order_id": "A-100" }), or positional for numeric placeholders {{1}}, {{2}} — a map with numeric keys or a plain array (["Иван", "A-100"], values in order: header → body → buttons).
{
"channel_id": 57,
"recipient": "+79001234567",
"template_id": 42,
"params": { "name": "Иван", "order_id": "A-100" }
}{
"success": true,
"message_id": 12345
}Behaviour. Sending is only allowed for whatsapp_business channels and templates with meta_status = approved — otherwise 422. If a variable's value is missing, 422 lists the missing parameters. The sent message keeps the rendered template structure in message_content.display (visible in GET /messages): header, body, footer, buttons[] — each button is { "type": "quick_reply" | "url" | "phone", "label": "...", "value": "..." }. When a contact taps a quick-reply button, the tap arrives back as an inbound message of type: "text" carrying the button's label and content.interactive_reply_payload. Requires the conversation.create permission.
Errors and status codes
| Code | Cause | How to handle |
|---|---|---|
401 | Invalid or missing token | Check Authorization: Bearer {token} |
403 | The token owner lacks the conversation.create permission | Grant the token owner the conversation.create permission |
404 | Company not found (the token isn't bound to a company) | Verify the token is bound to a company |
422 | Validation error, the channel doesn't belong to the company, or the template isn't approved | Check the request body and template status |
429 | Rate limit exceeded | Retry with exponential backoff |
For the full error format, see the Errors guide.