Getting started
From a key to your first message in five minutes.
The public API lets your own systems - a POS, an ERP, a booking engine, a website - send and read messages on the channels your business has connected, work with contacts and leads, and start automations. You never talk to WhatsApp, Meta, Telegram or an email provider yourself: the platform normalises every channel behind one contract, and your integration does not change when the business adds a channel.
What you need
- An application in the developer portal (Settings → Developer → Applications). An administrator of the workspace creates it, chooses its scopes and the channels it may send through, and hands you a credential.
- A credential. An API key (
omni_live_…) for a single server, or an OAuth2 client id and secret if your system already manages tokens. Either is shown once; store it in a secret manager, not in code. - A channel. At least one connected channel - WhatsApp, Telegram, email or web chat - that the administrator has allow-listed for the application. You never configure a channel yourself: you send by its name (
"channel": "whatsapp") and the platform picks the allow-listed connection.
Five-minute quickstart
Every request goes to the same base URL, carries Authorization: Bearer <credential> and Accept: application/json, and answers with one envelope: { "data": … } on success, { "error": { "code", "message", "details", "request_id" } } on failure. Pick your language in the sidebar; it sticks across every page.
1. Send your first message
A free-form text can only be sent inside the customer's 24-hour service window (they wrote to you in the last day). For anything else send an approved template - see Sending messages. The Idempotency-Key header is required on every write: a retry with the same key returns the original answer instead of sending twice.
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 202 means accepted and queued, not delivered. Delivery happens on a worker, and the customer's device confirms it seconds later.
2. Follow the delivery
Either poll the message…
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
…or, better, register a webhook and be told. message.sent, message.delivered, message.read and message.failed arrive at your endpoint with the external_reference you sent, so you can reconcile against your own order or invoice id without storing our ids at all. See Webhooks.
Conventions worth knowing
- Ids are UUIDs. Nothing in a URL or a payload is a sequential number.
- Times are ISO 8601 with an offset (
2026-09-15T10:02:11+05:30). - Phone numbers are E.164 without the plus (
919876543210). - Money is in whole currency units with a three-letter currency code.
- Lists page. Contacts and templates page by number (
page,per_page); conversations and messages page by cursor (cursor,limit). See Pagination. - Every response carries
X-Request-ID. Quote it when you write to support and we can find the exact log lines.
Test and live
An application is created in a test or live environment. Test credentials (omni_test_…) work against the same API, are limited to the test channels the business connected, and are the right place to build. Swap the key and nothing else changes.
https://communication-api.artofluminaire.com
Every response carries X-Request-ID; quote it when you write to support.