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.
curl -X POST "https://communication-api.artofluminaire.com/api/v1/messages" \
-H "Authorization: Bearer omni_live_YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ORDER-10025-CONFIRMATION" \
-d '{
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "text",
"external_reference": "ORDER-10025",
"message": {
"body": "Hi Rahul, your order #10025 is confirmed and will ship today."
}
}'// Node.js 18+ (built-in fetch), saved as request.mjs for top-level await. Replace the key with your own.
const response = await fetch("https://communication-api.artofluminaire.com/api/v1/messages", {
method: "POST",
headers: {
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
"Content-Type": "application/json",
"Idempotency-Key": "ORDER-10025-CONFIRMATION",
},
body: JSON.stringify({
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "text",
"external_reference": "ORDER-10025",
"message": {
"body": "Hi Rahul, your order #10025 is confirmed and will ship today."
}
}),
});
const payload = await response.json();
if (!response.ok) {
// Every error is { error: { code, message, details, request_id } }.
throw new Error(`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`);
}
console.log(payload.data);# pip install requests
import requests
response = requests.post(
"https://communication-api.artofluminaire.com/api/v1/messages",
headers={
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
"Content-Type": "application/json",
"Idempotency-Key": "ORDER-10025-CONFIRMATION",
},
json={
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "text",
"external_reference": "ORDER-10025",
"message": {
"body": "Hi Rahul, your order #10025 is confirmed and will ship today.",
},
},
timeout=30,
)
payload = response.json()
if not response.ok:
# Every error is {"error": {"code", "message", "details", "request_id"}}.
error = payload["error"]
raise RuntimeError(f"{error['code']}: {error['message']} (request {error['request_id']})")
print(payload["data"])<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client(['http_errors' => false, 'timeout' => 30]);
$response = $client->post('https://communication-api.artofluminaire.com/api/v1/messages', [
'headers' => [
'Authorization' => 'Bearer omni_live_YOUR_API_KEY',
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Idempotency-Key' => 'ORDER-10025-CONFIRMATION',
],
'json' => [
'channel' => 'whatsapp',
'connection_id' => '01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33',
'to' => '919876543210',
'type' => 'text',
'external_reference' => 'ORDER-10025',
'message' => [
'body' => 'Hi Rahul, your order #10025 is confirmed and will ship today.',
],
],
]);
$payload = json_decode((string) $response->getBody(), true);
if ($response->getStatusCode() >= 400) {
// Every error is ['error' => ['code', 'message', 'details', 'request_id']].
throw new RuntimeException(sprintf('%s: %s (request %s)', $payload['error']['code'], $payload['error']['message'], $payload['error']['request_id']));
}
echo json_encode($payload['data'], JSON_PRETTY_PRINT), PHP_EOL;// Java 17+, no dependencies (java.net.http). Parse the JSON with your usual library.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://communication-api.artofluminaire.com/api/v1/messages"))
.header("Authorization", "Bearer omni_live_YOUR_API_KEY")
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "ORDER-10025-CONFIRMATION")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "text",
"external_reference": "ORDER-10025",
"message": {
"body": "Hi Rahul, your order #10025 is confirmed and will ship today."
}
}
"""))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new RuntimeException("Request failed: " + response.body());
}
System.out.println(response.body());
}
}// .NET 7+ (C# 11 raw string literals). Parse the JSON with System.Text.Json.
using System;
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://communication-api.artofluminaire.com/api/v1/messages");
request.Headers.TryAddWithoutValidation("Authorization", "Bearer omni_live_YOUR_API_KEY");
request.Headers.TryAddWithoutValidation("Accept", "application/json");
request.Headers.TryAddWithoutValidation("Idempotency-Key", "ORDER-10025-CONFIRMATION");
request.Content = new StringContent("""
{
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "text",
"external_reference": "ORDER-10025",
"message": {
"body": "Hi Rahul, your order #10025 is confirmed and will ship today."
}
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new Exception($"Request failed: {payload}");
}
Console.WriteLine(payload);// Go 1.20+, standard library only. Decode the JSON with encoding/json.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "text",
"external_reference": "ORDER-10025",
"message": {
"body": "Hi Rahul, your order #10025 is confirmed and will ship today."
}
}`)
req, err := http.NewRequest("POST", "https://communication-api.artofluminaire.com/api/v1/messages", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer omni_live_YOUR_API_KEY")
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "ORDER-10025-CONFIRMATION")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
panic(fmt.Sprintf("request failed: %s", payload))
}
fmt.Println(string(payload))
}Try it - send it and see the response
{
"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:
- inside the window,
type: "text"sends; - outside it,
type: "text"is refused with422 outside_service_windowand the message tells you to send a template instead; type: "template"sends at any time, provided the template is approved.
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.
curl -X GET "https://communication-api.artofluminaire.com/api/v1/templates?status=APPROVED" \
-H "Authorization: Bearer omni_live_YOUR_API_KEY" \
-H "Accept: application/json"// Node.js 18+ (built-in fetch), saved as request.mjs for top-level await. Replace the key with your own.
const response = await fetch("https://communication-api.artofluminaire.com/api/v1/templates?status=APPROVED", {
method: "GET",
headers: {
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
},
});
const payload = await response.json();
if (!response.ok) {
// Every error is { error: { code, message, details, request_id } }.
throw new Error(`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`);
}
console.log(payload.data);# pip install requests
import requests
response = requests.get(
"https://communication-api.artofluminaire.com/api/v1/templates?status=APPROVED",
headers={
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
},
timeout=30,
)
payload = response.json()
if not response.ok:
# Every error is {"error": {"code", "message", "details", "request_id"}}.
error = payload["error"]
raise RuntimeError(f"{error['code']}: {error['message']} (request {error['request_id']})")
print(payload["data"])<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client(['http_errors' => false, 'timeout' => 30]);
$response = $client->get('https://communication-api.artofluminaire.com/api/v1/templates?status=APPROVED', [
'headers' => [
'Authorization' => 'Bearer omni_live_YOUR_API_KEY',
'Accept' => 'application/json',
],
]);
$payload = json_decode((string) $response->getBody(), true);
if ($response->getStatusCode() >= 400) {
// Every error is ['error' => ['code', 'message', 'details', 'request_id']].
throw new RuntimeException(sprintf('%s: %s (request %s)', $payload['error']['code'], $payload['error']['message'], $payload['error']['request_id']));
}
echo json_encode($payload['data'], JSON_PRETTY_PRINT), PHP_EOL;// Java 17+, no dependencies (java.net.http). Parse the JSON with your usual library.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://communication-api.artofluminaire.com/api/v1/templates?status=APPROVED"))
.header("Authorization", "Bearer omni_live_YOUR_API_KEY")
.header("Accept", "application/json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new RuntimeException("Request failed: " + response.body());
}
System.out.println(response.body());
}
}// .NET 7+ (C# 11 raw string literals). Parse the JSON with System.Text.Json.
using System;
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://communication-api.artofluminaire.com/api/v1/templates?status=APPROVED");
request.Headers.TryAddWithoutValidation("Authorization", "Bearer omni_live_YOUR_API_KEY");
request.Headers.TryAddWithoutValidation("Accept", "application/json");
using var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new Exception($"Request failed: {payload}");
}
Console.WriteLine(payload);// Go 1.20+, standard library only. Decode the JSON with encoding/json.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://communication-api.artofluminaire.com/api/v1/templates?status=APPROVED", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer omni_live_YOUR_API_KEY")
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
panic(fmt.Sprintf("request failed: %s", payload))
}
fmt.Println(string(payload))
}Try it - send it and see the response
curl -X POST "https://communication-api.artofluminaire.com/api/v1/messages" \
-H "Authorization: Bearer omni_live_YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: INV-10025-WHATSAPP" \
-d '{
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "template",
"external_reference": "INV-10025",
"message": {
"template": "invoice_ready",
"language": "en",
"variables": [
"Rahul",
"INV-10025",
"₹4,850"
]
}
}'// Node.js 18+ (built-in fetch), saved as request.mjs for top-level await. Replace the key with your own.
const response = await fetch("https://communication-api.artofluminaire.com/api/v1/messages", {
method: "POST",
headers: {
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
"Content-Type": "application/json",
"Idempotency-Key": "INV-10025-WHATSAPP",
},
body: JSON.stringify({
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "template",
"external_reference": "INV-10025",
"message": {
"template": "invoice_ready",
"language": "en",
"variables": [
"Rahul",
"INV-10025",
"₹4,850"
]
}
}),
});
const payload = await response.json();
if (!response.ok) {
// Every error is { error: { code, message, details, request_id } }.
throw new Error(`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`);
}
console.log(payload.data);# pip install requests
import requests
response = requests.post(
"https://communication-api.artofluminaire.com/api/v1/messages",
headers={
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
"Content-Type": "application/json",
"Idempotency-Key": "INV-10025-WHATSAPP",
},
json={
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "template",
"external_reference": "INV-10025",
"message": {
"template": "invoice_ready",
"language": "en",
"variables": [
"Rahul",
"INV-10025",
"₹4,850",
],
},
},
timeout=30,
)
payload = response.json()
if not response.ok:
# Every error is {"error": {"code", "message", "details", "request_id"}}.
error = payload["error"]
raise RuntimeError(f"{error['code']}: {error['message']} (request {error['request_id']})")
print(payload["data"])<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client(['http_errors' => false, 'timeout' => 30]);
$response = $client->post('https://communication-api.artofluminaire.com/api/v1/messages', [
'headers' => [
'Authorization' => 'Bearer omni_live_YOUR_API_KEY',
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Idempotency-Key' => 'INV-10025-WHATSAPP',
],
'json' => [
'channel' => 'whatsapp',
'connection_id' => '01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33',
'to' => '919876543210',
'type' => 'template',
'external_reference' => 'INV-10025',
'message' => [
'template' => 'invoice_ready',
'language' => 'en',
'variables' => [
'Rahul',
'INV-10025',
'₹4,850',
],
],
],
]);
$payload = json_decode((string) $response->getBody(), true);
if ($response->getStatusCode() >= 400) {
// Every error is ['error' => ['code', 'message', 'details', 'request_id']].
throw new RuntimeException(sprintf('%s: %s (request %s)', $payload['error']['code'], $payload['error']['message'], $payload['error']['request_id']));
}
echo json_encode($payload['data'], JSON_PRETTY_PRINT), PHP_EOL;// Java 17+, no dependencies (java.net.http). Parse the JSON with your usual library.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://communication-api.artofluminaire.com/api/v1/messages"))
.header("Authorization", "Bearer omni_live_YOUR_API_KEY")
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "INV-10025-WHATSAPP")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "template",
"external_reference": "INV-10025",
"message": {
"template": "invoice_ready",
"language": "en",
"variables": [
"Rahul",
"INV-10025",
"₹4,850"
]
}
}
"""))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new RuntimeException("Request failed: " + response.body());
}
System.out.println(response.body());
}
}// .NET 7+ (C# 11 raw string literals). Parse the JSON with System.Text.Json.
using System;
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://communication-api.artofluminaire.com/api/v1/messages");
request.Headers.TryAddWithoutValidation("Authorization", "Bearer omni_live_YOUR_API_KEY");
request.Headers.TryAddWithoutValidation("Accept", "application/json");
request.Headers.TryAddWithoutValidation("Idempotency-Key", "INV-10025-WHATSAPP");
request.Content = new StringContent("""
{
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "template",
"external_reference": "INV-10025",
"message": {
"template": "invoice_ready",
"language": "en",
"variables": [
"Rahul",
"INV-10025",
"₹4,850"
]
}
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new Exception($"Request failed: {payload}");
}
Console.WriteLine(payload);// Go 1.20+, standard library only. Decode the JSON with encoding/json.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"channel": "whatsapp",
"connection_id": "01997f2a-5e8f-7e30-9d6c-4b2e1fa09c33",
"to": "919876543210",
"type": "template",
"external_reference": "INV-10025",
"message": {
"template": "invoice_ready",
"language": "en",
"variables": [
"Rahul",
"INV-10025",
"₹4,850"
]
}
}`)
req, err := http.NewRequest("POST", "https://communication-api.artofluminaire.com/api/v1/messages", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer omni_live_YOUR_API_KEY")
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "INV-10025-WHATSAPP")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
panic(fmt.Sprintf("request failed: %s", payload))
}
fmt.Println(string(payload))
}Try it - send it and see the response
{
"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
curl -X GET "https://communication-api.artofluminaire.com/api/v1/messages/01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22" \
-H "Authorization: Bearer omni_live_YOUR_API_KEY" \
-H "Accept: application/json"// Node.js 18+ (built-in fetch), saved as request.mjs for top-level await. Replace the key with your own.
const response = await fetch("https://communication-api.artofluminaire.com/api/v1/messages/01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22", {
method: "GET",
headers: {
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
},
});
const payload = await response.json();
if (!response.ok) {
// Every error is { error: { code, message, details, request_id } }.
throw new Error(`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`);
}
console.log(payload.data);# pip install requests
import requests
response = requests.get(
"https://communication-api.artofluminaire.com/api/v1/messages/01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22",
headers={
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
},
timeout=30,
)
payload = response.json()
if not response.ok:
# Every error is {"error": {"code", "message", "details", "request_id"}}.
error = payload["error"]
raise RuntimeError(f"{error['code']}: {error['message']} (request {error['request_id']})")
print(payload["data"])<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client(['http_errors' => false, 'timeout' => 30]);
$response = $client->get('https://communication-api.artofluminaire.com/api/v1/messages/01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22', [
'headers' => [
'Authorization' => 'Bearer omni_live_YOUR_API_KEY',
'Accept' => 'application/json',
],
]);
$payload = json_decode((string) $response->getBody(), true);
if ($response->getStatusCode() >= 400) {
// Every error is ['error' => ['code', 'message', 'details', 'request_id']].
throw new RuntimeException(sprintf('%s: %s (request %s)', $payload['error']['code'], $payload['error']['message'], $payload['error']['request_id']));
}
echo json_encode($payload['data'], JSON_PRETTY_PRINT), PHP_EOL;// Java 17+, no dependencies (java.net.http). Parse the JSON with your usual library.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://communication-api.artofluminaire.com/api/v1/messages/01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22"))
.header("Authorization", "Bearer omni_live_YOUR_API_KEY")
.header("Accept", "application/json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new RuntimeException("Request failed: " + response.body());
}
System.out.println(response.body());
}
}// .NET 7+ (C# 11 raw string literals). Parse the JSON with System.Text.Json.
using System;
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://communication-api.artofluminaire.com/api/v1/messages/01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22");
request.Headers.TryAddWithoutValidation("Authorization", "Bearer omni_live_YOUR_API_KEY");
request.Headers.TryAddWithoutValidation("Accept", "application/json");
using var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new Exception($"Request failed: {payload}");
}
Console.WriteLine(payload);// Go 1.20+, standard library only. Decode the JSON with encoding/json.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://communication-api.artofluminaire.com/api/v1/messages/01997f2a-4d7e-7d2f-8c5b-3a1d0e9f8b22", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer omni_live_YOUR_API_KEY")
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
panic(fmt.Sprintf("request failed: %s", payload))
}
fmt.Println(string(payload))
}Try it - send it and see the response
{
"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:
curl -X GET "https://communication-api.artofluminaire.com/api/v1/conversations?status=open&limit=25" \
-H "Authorization: Bearer omni_live_YOUR_API_KEY" \
-H "Accept: application/json"// Node.js 18+ (built-in fetch), saved as request.mjs for top-level await. Replace the key with your own.
const response = await fetch("https://communication-api.artofluminaire.com/api/v1/conversations?status=open&limit=25", {
method: "GET",
headers: {
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
},
});
const payload = await response.json();
if (!response.ok) {
// Every error is { error: { code, message, details, request_id } }.
throw new Error(`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`);
}
console.log(payload.data);# pip install requests
import requests
response = requests.get(
"https://communication-api.artofluminaire.com/api/v1/conversations?status=open&limit=25",
headers={
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
},
timeout=30,
)
payload = response.json()
if not response.ok:
# Every error is {"error": {"code", "message", "details", "request_id"}}.
error = payload["error"]
raise RuntimeError(f"{error['code']}: {error['message']} (request {error['request_id']})")
print(payload["data"])<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client(['http_errors' => false, 'timeout' => 30]);
$response = $client->get('https://communication-api.artofluminaire.com/api/v1/conversations?status=open&limit=25', [
'headers' => [
'Authorization' => 'Bearer omni_live_YOUR_API_KEY',
'Accept' => 'application/json',
],
]);
$payload = json_decode((string) $response->getBody(), true);
if ($response->getStatusCode() >= 400) {
// Every error is ['error' => ['code', 'message', 'details', 'request_id']].
throw new RuntimeException(sprintf('%s: %s (request %s)', $payload['error']['code'], $payload['error']['message'], $payload['error']['request_id']));
}
echo json_encode($payload['data'], JSON_PRETTY_PRINT), PHP_EOL;// Java 17+, no dependencies (java.net.http). Parse the JSON with your usual library.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://communication-api.artofluminaire.com/api/v1/conversations?status=open&limit=25"))
.header("Authorization", "Bearer omni_live_YOUR_API_KEY")
.header("Accept", "application/json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new RuntimeException("Request failed: " + response.body());
}
System.out.println(response.body());
}
}// .NET 7+ (C# 11 raw string literals). Parse the JSON with System.Text.Json.
using System;
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://communication-api.artofluminaire.com/api/v1/conversations?status=open&limit=25");
request.Headers.TryAddWithoutValidation("Authorization", "Bearer omni_live_YOUR_API_KEY");
request.Headers.TryAddWithoutValidation("Accept", "application/json");
using var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new Exception($"Request failed: {payload}");
}
Console.WriteLine(payload);// Go 1.20+, standard library only. Decode the JSON with encoding/json.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://communication-api.artofluminaire.com/api/v1/conversations?status=open&limit=25", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer omni_live_YOUR_API_KEY")
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
panic(fmt.Sprintf("request failed: %s", payload))
}
fmt.Println(string(payload))
}Try it - send it and see the response
curl -X GET "https://communication-api.artofluminaire.com/api/v1/conversations/01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11/messages?limit=50" \
-H "Authorization: Bearer omni_live_YOUR_API_KEY" \
-H "Accept: application/json"// Node.js 18+ (built-in fetch), saved as request.mjs for top-level await. Replace the key with your own.
const response = await fetch("https://communication-api.artofluminaire.com/api/v1/conversations/01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11/messages?limit=50", {
method: "GET",
headers: {
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
},
});
const payload = await response.json();
if (!response.ok) {
// Every error is { error: { code, message, details, request_id } }.
throw new Error(`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`);
}
console.log(payload.data);# pip install requests
import requests
response = requests.get(
"https://communication-api.artofluminaire.com/api/v1/conversations/01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11/messages?limit=50",
headers={
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
},
timeout=30,
)
payload = response.json()
if not response.ok:
# Every error is {"error": {"code", "message", "details", "request_id"}}.
error = payload["error"]
raise RuntimeError(f"{error['code']}: {error['message']} (request {error['request_id']})")
print(payload["data"])<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client(['http_errors' => false, 'timeout' => 30]);
$response = $client->get('https://communication-api.artofluminaire.com/api/v1/conversations/01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11/messages?limit=50', [
'headers' => [
'Authorization' => 'Bearer omni_live_YOUR_API_KEY',
'Accept' => 'application/json',
],
]);
$payload = json_decode((string) $response->getBody(), true);
if ($response->getStatusCode() >= 400) {
// Every error is ['error' => ['code', 'message', 'details', 'request_id']].
throw new RuntimeException(sprintf('%s: %s (request %s)', $payload['error']['code'], $payload['error']['message'], $payload['error']['request_id']));
}
echo json_encode($payload['data'], JSON_PRETTY_PRINT), PHP_EOL;// Java 17+, no dependencies (java.net.http). Parse the JSON with your usual library.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://communication-api.artofluminaire.com/api/v1/conversations/01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11/messages?limit=50"))
.header("Authorization", "Bearer omni_live_YOUR_API_KEY")
.header("Accept", "application/json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new RuntimeException("Request failed: " + response.body());
}
System.out.println(response.body());
}
}// .NET 7+ (C# 11 raw string literals). Parse the JSON with System.Text.Json.
using System;
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://communication-api.artofluminaire.com/api/v1/conversations/01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11/messages?limit=50");
request.Headers.TryAddWithoutValidation("Authorization", "Bearer omni_live_YOUR_API_KEY");
request.Headers.TryAddWithoutValidation("Accept", "application/json");
using var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new Exception($"Request failed: {payload}");
}
Console.WriteLine(payload);// Go 1.20+, standard library only. Decode the JSON with encoding/json.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://communication-api.artofluminaire.com/api/v1/conversations/01997f2a-3c6d-7c1e-9b4a-2f0c9d8e7a11/messages?limit=50", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer omni_live_YOUR_API_KEY")
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
panic(fmt.Sprintf("request failed: %s", payload))
}
fmt.Println(string(payload))
}Try it - send it and see the response
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.
https://communication-api.artofluminaire.com
Every response carries X-Request-ID; quote it when you write to support.