Qubit API v1

Sending messages

Text, templates, the 24-hour window and delivery status.

One endpoint sends on every channel. You name the channel and the connection, the platform picks the provider, formats the payload, handles the rate budget and retries, and tells you what happened through the message's status and your webhooks.

The canonical send

POST /messages with channel, to, a type and a message. connection_id picks which of your allowed connections sends it; leave it out and the application's default connection for that channel is used. external_reference is your own id - an order, an invoice, a booking - and comes back on every webhook about this message.

Request
Try it - send it and see the response

This is a real request. A test key still acts on its workspace: a send goes out on the connection named in the body. Point it at your own number.

Response 202
{
    "data": {
        "message_id": "01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22",
        "conversation_id": "01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11",
        "request_id": "2c0d8f0e-…",
        "external_reference": "ORDER-10025",
        "status": "queued"
    }
}

The answer is 202 Accepted. The message exists, it is queued, and a worker hands it to the provider within moments. status walks queued → sent → delivered → read, or to failed with a reason, and you learn each step by webhook or by reading the message.

The 24-hour service window

On WhatsApp - and on Messenger and Instagram - a business may send free-form text only within 24 hours of the customer's last message. Outside that window the only thing that can be sent is an approved template. The platform enforces this before anything reaches the provider:

Telegram, email and web chat have no window; a text always sends.

Templates

A template is created and submitted for approval by the business in the dashboard; your integration lists the approved ones and sends them by name and language with the variable values in order.

Request
Try it - send it and see the response

Sends this request from your browser and shows what the API answered. Nothing is stored here.

Request
Try it - send it and see the response

This is a real request. A test key still acts on its workspace: a send goes out on the connection named in the body. Point it at your own number.

Response 202
{
    "data": {
        "message_id": "01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22",
        "conversation_id": "01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11",
        "external_reference": "INV-10025",
        "status": "queued"
    }
}

Send a template whose status is not APPROVED and the answer is 422 template_not_approved, with the template's actual status in details. Supply the wrong number of variables and the answer is 422 validation_failed naming how many it expects.

Buttons and lists

type: "interactive" sends a question with choices, under the same window and idempotency rules as text. message.interactive is either up to three reply buttons or a list of up to ten rows:

{ "type": "buttons", "body": "How can we help?", "buttons": [{ "id": "sales", "title": "Sales" }, { "id": "support", "title": "Support" }] }
{ "type": "list", "body": "Pick a plan", "button_label": "Plans", "sections": [{ "title": "Plans", "rows": [{ "id": "pro", "title": "Pro", "description": "Unlimited seats" }] }] }

WhatsApp shows them natively, Messenger and Instagram as quick replies, Telegram as an inline keyboard and web chat as chips. A channel that cannot show choices - email, LinkedIn - receives the same question as numbered text (1) Sales). When the customer taps an option, the inbound message's body is the option's title and interactive_reply is { "payload_id": "sales", "title": "Sales" } - payload_id is the id you gave the option.

Media

type: "image", "document", "video" or "audio" with a public url in message (and an optional caption for images and documents). The platform fetches the file, checks it against the channel's size limit - 413 payload_too_large names the limit - and stores it in its own object storage before sending, so your URL only has to live for the seconds it takes to fetch.

Reading a message and its status

Request
Try it - send it and see the response

Sends this request from your browser and shows what the API answered. Nothing is stored here.

Response 200
{
    "data": {
        "id": "01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22",
        "conversation_id": "01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11",
        "direction": "OUTBOUND",
        "status": "DELIVERED",
        "external_reference": "ORDER-10025",
        "sent_at": "2026-09-15T10:02:11+05:30",
        "delivered_at": "2026-09-15T10:02:14+05:30"
    }
}

A failed message carries error_code and error_message. Codes are the platform's, not the provider's: template_not_approved, outside_service_window, channel_unavailable, provider_error. The provider's raw reason is in the dashboard for the business; it is not something an integration should branch on.

Conversations

Every message belongs to a conversation - one per contact per channel connection - and the conversation is where the thread, the assignment and the CRM context live. Your integration can read them:

Request
Try it - send it and see the response

Sends this request from your browser and shows what the API answered. Nothing is stored here.

Request
Try it - send it and see the response

Sends this request from your browser and shows what the API answered. Nothing is stored here.

Both lists are cursor paginated; see Pagination.

What you cannot do from the API

Send to a contact who has opted out or been blocked by the business - the send is refused with 403 forbidden and the reason in details. This is deliberate: consent is the business's to manage, and the API cannot override it.

Base URL https://communication-api.artofluminaire.com Every response carries X-Request-ID; quote it when you write to support.