Send a message
POST/api/v1/messages
scope
messages:sendIdempotentSend a text, template or media message into an existing conversation, or to a phone number on WhatsApp / personal WhatsApp — the contact and thread are found or created. Text and media outside WhatsApp’s 24-hour customer-service window fail with unprocessable / outside_window; send a template instead. Templates are WhatsApp-only. The message is recorded in the inbox as sent by the assistant/API (sender_type: bot).
Body
application/json| Name | Type | Description |
|---|---|---|
type: "text"optional | object | |
typerequired | string | text |
torequired | one of | Where to send |
to (option 1)optional | object | Address an existing thread |
to.conversation_idrequired | string (uuid) | An existing thread on any channel |
to (option 2)optional | object | Address a phone number; the contact and thread are found or created |
to.phonerequired | string | E.164 phone number, e.g. +971501234567 |
to.channeloptional | string (enum) | The phone-keyed channel to send onwhatsapppersonal_whatsappDefault: whatsapp |
to.connection_idoptional | string (uuid) | Required for personal_whatsapp: the linked number to send from |
textrequired | string | Message text |
type: "template"optional | object | |
typerequired | string | template |
torequired | one of | Where to send |
to (option 1)optional | object | Address an existing thread |
to.conversation_idrequired | string (uuid) | An existing thread on any channel |
to (option 2)optional | object | Address a phone number; the contact and thread are found or created |
to.phonerequired | string | E.164 phone number, e.g. +971501234567 |
to.channeloptional | string (enum) | The phone-keyed channel to send onwhatsapppersonal_whatsappDefault: whatsapp |
to.connection_idoptional | string (uuid) | Required for personal_whatsapp: the linked number to send from |
templaterequired | object | |
template.namerequired | string | Template name, as approved by Meta |
template.languageoptional | string | Template language codeDefault: en_US |
template.body_paramsoptional | string[] | Values for {{1}}, {{2}}, … in the body |
template.header_textoptional | string | Value for a text header variable |
template.header_media_urloptional | string (url) | Public URL for an image/video/document header |
template.button_paramsoptional | map<string, string> | Per-button values keyed by button index, for URL-suffix and copy-code buttons |
type: "media"optional | object | |
typerequired | string | media |
torequired | one of | Where to send |
to (option 1)optional | object | Address an existing thread |
to.conversation_idrequired | string (uuid) | An existing thread on any channel |
to (option 2)optional | object | Address a phone number; the contact and thread are found or created |
to.phonerequired | string | E.164 phone number, e.g. +971501234567 |
to.channeloptional | string (enum) | The phone-keyed channel to send onwhatsapppersonal_whatsappDefault: whatsapp |
to.connection_idoptional | string (uuid) | Required for personal_whatsapp: the linked number to send from |
mediarequired | object | |
media.kindrequired | string (enum) | imagevideodocumentaudio |
media.urlrequired | string (url) | A public URL the channel fetches at send time |
media.captionoptional | string | |
media.filenameoptional | string | Shown for documents |
Request
curl -X POST "https://your-crm.example.com/api/v1/messages" \
-H "Authorization: Bearer $RAABTA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a9e-3b4d-4c5e-8f7a-9b0c1d2e3f4a" \
-d '{
"type": "text",
"to": {
"phone": "+971501234567",
"channel": "whatsapp"
},
"text": "Your appointment is confirmed for Saturday at 11:00."
}'Response · 201 Created
{
"data": {
"id": "0c1d2e3f-4a5b-4c6d-8e7f-8a9b0c1d2e3f",
"conversation_id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8",
"direction": "outbound",
"sender_type": "bot",
"type": "text",
"text": "Your appointment is confirmed for Saturday at 11:00.",
"media_url": null,
"template_name": null,
"status": "sent",
"channel_message_id": "wamid.HBgLOTcxNTAxMjM0NTY3FQIAERgSN0Y4MjA1QzYwRjkzMDAyMzRCAA==",
"reply_to_message_id": null,
"created_at": "2026-09-02T10:00:00.000Z"
}
}Response
Wrapped in { data: … }| Name | Type | Description |
|---|---|---|
idrequired | string (uuid) | UUID |
conversation_idrequired | string (uuid) | UUID |
directionrequired | string (enum) | inbound = from the customerinboundoutbound |
sender_typerequired | string (enum) | bot = the assistant or an API key; agent = a member in the inboxcustomeragentbot |
typerequired | string (enum) | Content typetextimagedocumentaudiovideolocationtemplateinteractive |
textrequired | string | null | Body text, or the caption of a media message |
media_urlrequired | string | null | URL of the attached image, video, document or audio |
template_namerequired | string | null | The template this message was sent from, for template sends |
statusrequired | string (enum) | Delivery status as reported by the channelsendingsentdeliveredreadfailed |
channel_message_idrequired | string | null | The channel’s own id (a WhatsApp wamid, a Messenger mid) |
reply_to_message_idrequired | string (uuid) | null | The message this one replies to, when the customer quoted one |
created_atrequired | string (date-time) | ISO 8601 timestamp |
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | The request could not be parsed: malformed JSON, an invalid cursor, or a query parameter of the wrong shape. |
| 400 | validation_error | The body or query failed validation. `details` lists each failing field with a `path` and a `message`. |
| 401 | unauthorized | No usable API key: the Authorization header is missing or malformed, or the key is unknown, revoked or expired. The three are deliberately indistinguishable. |
| 402 | plan_limit_reached | The account has reached a plan limit for this resource. `details[0].limit` is the cap; ask the account owner to raise it. |
| 403 | forbidden | The key is valid but lacks the scope this endpoint requires, or the request came from an address outside the key’s IP allowlist. The message says which. |
| 403 | account_suspended | The account this key belongs to is suspended. Rotating the key will not help; contact support. |
| 404 | not_found | No such resource in this account. A resource that exists in another account also returns this. |
| 409 | conflict | The request conflicts with current state: a duplicate phone number on create, an invalid state transition, or an idempotent request that is still in flight. |
| 422 | unprocessable | The request was well-formed but cannot be carried out. `reason` is a stable string saying why (for example `outside_window` or `stage_not_in_pipeline`). |
| 422 | channel_not_connected | The channel this request needs is not connected on the account. Connect it in Settings and retry. |
| 429 | rate_limited | The per-key budget, or the per-IP budget for failed authentication, is exhausted. Honour `Retry-After` before retrying. |
| 500 | internal | Something failed on our side. Safe to retry with the same Idempotency-Key; quote `request_id` if it persists. |