Endpoints
Every endpoint lives under https://YOUR_DOMAIN/api/v1 and requires an API key
(Authorization: Bearer lw_live_... or X-API-Key: lw_live_...). See
Authentication for keys and scopes, and
Rate Limits for the per-key budget.
List endpoints wrap results in a data array. Errors return
{ "error": "...", "message": "..." }. Reads work even on an expired
license; writes return 402 when the license is locked in enforce mode.
Conventions
| Thing | Detail |
|---|---|
| Base URL | https://YOUR_DOMAIN/api/v1 |
| Auth | Authorization: Bearer lw_live_... or X-API-Key: lw_live_... |
| Scope check | A key with * passes everything; otherwise the exact scope must be present (403 insufficient_scope). |
| Content type | application/json for request bodies. |
System
Liveness check
GET /health — scope: any valid key (no scope needed).
Returns ok plus the API version.
curl https://YOUR_DOMAIN/api/v1/health \
-H "Authorization: Bearer lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{ "ok": true, "version": "1.0.0" }Messages
Send a WhatsApp message
POST /messages — scope: messages:write
Sends a text message from a connected, ready number. If from is omitted, the
first ready number is used. Returns 202 once the message is handed to the
WhatsApp engine.
| Body field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Phone number (digits, with country code) or a full WhatsApp jid. |
text | string | Yes | Message body. |
from | string | No | Id of the connected number to send from. Defaults to the first ready number. |
curl -X POST https://YOUR_DOMAIN/api/v1/messages \
-H "Authorization: Bearer lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"to": "919812345678",
"text": "Hi! Thanks for reaching out."
}'{ "status": "sent", "to": "919812345678@s.whatsapp.net", "from": "n1a2b3c" }Possible errors: 400 invalid_input, 401 invalid_api_key,
402 license_locked, 403 insufficient_scope, 409 no_ready_session,
429 rate_limited.
Chats
List chats
GET /chats — scope: chats:read
Returns chats for a number, newest activity first. If sessionId is omitted, the
first connected number is used.
| Query param | Type | Required | Description |
|---|---|---|---|
sessionId | string | No | Number/session id. Defaults to the first connected number. |
limit | integer | No | Max chats to return (default 200). |
curl "https://YOUR_DOMAIN/api/v1/chats?limit=50" \
-H "X-API-Key: lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"data": [
{
"sessionId": "n1a2b3c",
"chatId": "919812345678@s.whatsapp.net",
"name": "Asha Rao",
"isGroup": false,
"status": "open",
"assignee": null,
"assigneeName": null,
"unreadCount": 2,
"lastMessage": "Refund please",
"lastTimestamp": 1751193300
}
]
}List messages in a chat
GET /chats/{chatId}/messages — scope: chats:read
Returns messages for a chat, newest first.
| Param | In | Type | Required | Description |
|---|---|---|---|---|
chatId | path | string | Yes | The chat id (a WhatsApp jid, e.g. 919812345678@s.whatsapp.net). |
sessionId | query | string | No | Number/session id. |
limit | query | integer | No | Max messages to return (default 50). |
curl "https://YOUR_DOMAIN/api/v1/chats/919812345678@s.whatsapp.net/messages?limit=20" \
-H "X-API-Key: lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"data": [
{
"messageId": "ABCD1234",
"sessionId": "n1a2b3c",
"chatId": "919812345678@s.whatsapp.net",
"body": "Refund please",
"type": "chat",
"author": "919812345678@s.whatsapp.net",
"fromMe": false,
"hasMedia": false,
"timestamp": 1751193300
}
]
}Contacts
List contacts
GET /contacts — scope: contacts:read
Returns contacts, optionally filtered by a free-text query and lifecycle stage.
| Query param | Type | Required | Description |
|---|---|---|---|
q | string | No | Search across name, email, and phone. |
stage | string | No | One of lead, active, customer, vip, churned. |
curl "https://YOUR_DOMAIN/api/v1/contacts?stage=vip" \
-H "X-API-Key: lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"data": [
{
"id": "c_1a2b3c",
"name": "Asha Rao",
"phone": "919812345678",
"email": "asha@example.com",
"company": "Acme",
"location": "Bengaluru",
"owner": "agent-7",
"tags": ["website", "wholesale"],
"lifecycle": "vip",
"lifetimeValue": "₹12,000",
"notes": "Prefers WhatsApp over email."
}
]
}Create a contact
POST /contacts — scope: contacts:write
Creates a CRM contact. name and phone are required.
| Body field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Contact name. |
phone | string | Yes | Phone number (with country code). |
email | string | No | Email address. |
company | string | No | Company name. |
location | string | No | Location. |
owner | string | No | Agent/rep responsible. |
tags | string[] | No | Free-form labels. |
lifecycle | string | No | lead, active, customer, vip, or churned. |
lifetimeValue | string | No | Free-text value (e.g. ₹12,000). |
notes | string | No | Internal notes. |
curl -X POST https://YOUR_DOMAIN/api/v1/contacts \
-H "X-API-Key: lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Asha Rao",
"phone": "919812345678",
"email": "asha@example.com",
"lifecycle": "lead",
"tags": ["website"]
}'{
"data": {
"id": "c_1a2b3c",
"name": "Asha Rao",
"phone": "919812345678",
"email": "asha@example.com",
"tags": ["website"],
"lifecycle": "lead"
}
}Tickets
List tickets
GET /tickets — scope: tickets:read
Returns tickets, optionally filtered by status and assignee.
| Query param | Type | Required | Description |
|---|---|---|---|
status | string | No | One of open, in_progress, waiting, resolved, closed. |
assignee | string | No | Filter by assigned agent. |
curl "https://YOUR_DOMAIN/api/v1/tickets?status=open" \
-H "X-API-Key: lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"data": [
{
"id": "t_9f8e7d",
"ref": "TKT-2041",
"subject": "Refund not received",
"customerName": "Asha Rao",
"customerPhone": "919812345678",
"status": "open",
"priority": "high",
"assignee": "agent-7",
"tag": "chat"
}
]
}Create a ticket
POST /tickets — scope: tickets:write
Creates a support ticket. Only subject is required.
| Body field | Type | Required | Description |
|---|---|---|---|
subject | string | Yes | Short description of the issue. |
customerName | string | No | Who the ticket is for. |
customerPhone | string | No | Customer phone number. |
status | string | No | open, in_progress, waiting, resolved, closed. |
priority | string | No | urgent, high, med, low (defaults to med). |
assignee | string | No | Agent responsible. |
tag | string | No | Source/category tag. |
curl -X POST https://YOUR_DOMAIN/api/v1/tickets \
-H "X-API-Key: lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"subject": "Refund not received",
"customerName": "Asha Rao",
"customerPhone": "919812345678",
"priority": "high"
}'{
"data": {
"id": "t_9f8e7d",
"ref": "TKT-2041",
"subject": "Refund not received",
"customerName": "Asha Rao",
"customerPhone": "919812345678",
"status": "open",
"priority": "high",
"tag": "api"
}
}Broadcasts
Create and queue a broadcast
POST /broadcasts — scope: broadcasts:write
Creates a broadcast and immediately queues it. The paced worker sends one recipient per tick with randomized delays and a daily cap — see Anti-Ban Pacing. Up to 5000 recipients per broadcast.
broadcasts:write is deliberately a separate scope because broadcasts carry the
highest ban risk. Grant it only to keys that truly need to send bulk messages,
and only broadcast to opted-in recipients.
| Body field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | A name for the campaign. |
sessionId | string | Yes | Id of the connected number to send from. |
message | string | Yes | Message body. Supports the {{name}} token. |
recipients | array | Yes | 1–5000 items of { "chatId": "...", "name": "..." } (chatId required). |
curl -X POST https://YOUR_DOMAIN/api/v1/broadcasts \
-H "X-API-Key: lw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "October offer",
"sessionId": "n1a2b3c",
"message": "Hi {{name}}, here is 10% off this week.",
"recipients": [
{ "chatId": "919812345678@s.whatsapp.net", "name": "Asha" }
]
}'{
"data": {
"id": "b_5e4d3c",
"name": "October offer",
"sessionId": "n1a2b3c",
"message": "Hi {{name}}, here is 10% off this week.",
"status": "queued",
"total": 1,
"sentCount": 0,
"failedCount": 0
}
}Error responses
Every endpoint can return these shared errors:
| Status | error | Meaning |
|---|---|---|
400 | invalid_input | The request body or parameters are invalid. |
401 | invalid_api_key | Missing, malformed, or disabled key. |
402 | license_locked | License expired/revoked in enforce mode (writes only). |
403 | insufficient_scope | The key lacks the scope for this endpoint. |
409 | no_ready_session | No connected WhatsApp number is ready (send only). |
429 | rate_limited | Per-key rate limit exceeded (120/min). |
Base URL, scopes, and how the API fits together.