Skip to content
AISARAISAR

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:

text
Authorization: Bearer YOUR_API_TOKEN

Step 1 — pick a channel

Fetch the company's channels to find the channel_id you'll send through:

bash
curl "https://api.aisar.app/v1/channels?status=connected" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
Response (abridged)
{
  "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.

Search for a contact
curl "https://api.aisar.app/v1/contacts?search=%2B77001234567" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
Create a contact
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

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": 57,
    "recipient": "+77001234567",
    "message_content": "Здравствуйте! Чем могу помочь?"
  }'
Success response
{
  "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.

The token owner needs the 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:

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": 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:

bash
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:

bash
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 response
{
  "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 (sentdeliveredread, 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

text
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/send

Endpoint 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 paramDescription
typeFilter by channel type key: whatsapp, whatsapp_business, telegram, telegram_bot, instagram, instagram_business, facebook_messenger, tiktok, live_chat, sms
statusFilter by status: connected, disconnected, paused, etc.
setup_stateFilter by setup state: Draft, Setup, Active, Paused
Response (abridged)
{
  "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

http
POST /v1/uploads
Authorization: Bearer YOUR_API_TOKEN
Content-Type: multipart/form-data

file=@photo.jpg

Maximum 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

http
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.

Response (both modes)
{
  "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"
}
The result is idempotent by content (SHA-256 within the company) — re-uploading the same file (multipart or URL) returns the same 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.

http
POST /v1/uploads/voice
Authorization: Bearer YOUR_API_TOKEN
Content-Type: multipart/form-data

file=@voice.m4a

Accepts 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.

Response
{
  "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"
}
The 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.

FieldTypeRequiredDescription
channel_idintegerYesThe sending channel ID (from /channels)
recipientstringYesThe 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_groupbooleanNotrue — 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_silentbooleanNotrue — a "silent" send: the operator's unread counter is not reset. Defaults to false. Useful for programmatic/service messages.
message_contentstringConditionalMessage text, max 65,535 chars (the channel's actual limit is in limits.max_text_length from /channels). Required if attachments is absent.
attachmentsarrayConditional1–10 attachments. Required if message_content is absent. Each item: media_id (required) + optional type (image/video/audio/voice/document/file).
Example — text
{
  "channel_id": 42,
  "recipient": "+79001234567",
  "message_content": "Здравствуйте! Чем могу помочь?"
}
Example — WhatsApp group
{
  "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 response
{ "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 paramDescription
channel_idFilter by a specific WhatsApp Business channel
metaStatuses[]Meta approval status: approved, rejected, pending. For sendable templates use metaStatuses[]=approved
channelTypeChannel type key (e.g. whatsapp_business)
languageBCP-47, e.g. en, ru
searchSearch by name/text/slug/labels
sortBy / sortDirname, status, created_at, updated_at, uses_count, language; asc/desc
Response (abridged)
{
  "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.

To avoid polling this endpoint constantly, subscribe to the 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.

FieldTypeRequiredDescription
channel_idintegerYesMust be of type whatsapp_business
recipientstringConditional (required_without:thread_id)E.164 number, max 255 chars
thread_idintegerConditional (required_without:recipient)ID of an existing thread; an alternative to recipient — provide exactly one of the two (min: 1)
template_idintegerYesTemplate ID from /templates, must belong to the company and have meta_status = approved
paramsobject/arrayNoTemplate variable values (see below). Omit for templates without variables.
header_media_urlstring (url)NoFor templates with a media header (image/video/document)
is_silentbooleanNoSame 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).

Example request
{
  "channel_id": 57,
  "recipient": "+79001234567",
  "template_id": 42,
  "params": { "name": "Иван", "order_id": "A-100" }
}
Success response
{
  "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

CodeCauseHow to handle
401Invalid or missing tokenCheck Authorization: Bearer {token}
403The token owner lacks the conversation.create permissionGrant the token owner the conversation.create permission
404Company not found (the token isn't bound to a company)Verify the token is bound to a company
422Validation error, the channel doesn't belong to the company, or the template isn't approvedCheck the request body and template status
429Rate limit exceededRetry with exponential backoff

For the full error format, see the Errors guide.