Send messages with the API
How do I send a template?
POST /messages with a template object. Templates can be sent any time, unless the contact has opted out.
curl -X POST https://app.kilichat.com/api/v1/messages \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "919876543210",
"template": {
"name": "order_shipped",
"language": "en",
"variables": ["Priya", "#1042", "Friday"]
}
}'| Field | Required | Notes |
|---|---|---|
to | Yes | Phone number with country code. |
template.name | Yes | The template's name in KiliChat, like order_shipped. |
template.language | No | Like en or ta. Leave it out if you only have one language; an approved one is picked. |
template.variables | No | Values for {{1}}, {{2}}… in order. Up to 20, each up to 1,000 characters. |
Variables can also use the contact's details: {{name}}, {{phone}} or {{attr.city}} are filled in from the contact in KiliChat. For example "variables": ["{{name}}", "#1042"].
Templates with a media header, location, changing link, coupon code or offer countdown use the template's sending values. Set those in KiliChat; the API doesn't take them per message.
How do I send a text message?
POST /messages with text (up to 4,096 characters):
curl -X POST https://app.kilichat.com/api/v1/messages \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to": "919876543210", "text": "Your order has shipped!"}'Text only goes to someone who messaged this number in the last 24 hours. Otherwise you get 422:
{ "error": "The 24-hour window is closed. Only an approved template can be sent to this contact.", "code": "window_closed" }Send either text or template, not both.
What does the response look like?
A successful send returns 201:
{
"message": {
"id": "6f1c2c1e-1b7a-4a63-9d0e-6a2f0c5b9a11",
"conversationId": "…",
"direction": "out",
"type": "template",
"text": "Good news, Priya: order #1042 is on its way. Expected delivery: Friday.",
"status": "sent",
"error": null,
"sentBy": "api",
"createdAt": "2026-11-08T10:00:00.000Z"
},
"contactId": "…"
}- If the phone number is new, the contact is added to KiliChat.
- The message appears in the KiliChat inbox, so your team sees it.
statusmovesqueued→sent→delivered→read, or ends atfailed.
If WhatsApp refuses the message, you get 502 with the same body, "status": "failed" and Meta's reason in error.
In Node.js
// Node.js 18+ (fetch is built in)
const API = "https://app.kilichat.com/api/v1";
const KEY = process.env.KILICHAT_API_KEY;
async function sendTemplate(to, name, variables) {
const res = await fetch(`${API}/messages`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ to, template: { name, variables } }),
});
const body = await res.json();
if (res.status === 429) {
const wait = Number(res.headers.get("Retry-After") ?? 60);
throw new Error(`Rate limited, retry in ${wait}s`);
}
if (!res.ok) throw new Error(`${res.status}: ${body.error ?? body.message?.error}`);
return body.message; // { id, status, ... }
}
const message = await sendTemplate("919876543210", "order_shipped", ["Priya", "#1042", "Friday"]);
console.log(message.id, message.status);Which errors can sending return?
| Status | Body | Meaning |
|---|---|---|
400 | to: Use the full number with country code, like 919876543210 | Bad phone number. |
400 | Send either text or a template | Both or neither were given. |
404 | No template called "…" | Check the name (and language) in KiliChat. |
422 | Template "…" isn't approved yet | Wait for Meta's approval. |
422 | code: "window_closed" | Use a template instead of text. |
422 | code: "opted_out" | The contact opted out. Don't send them templates. |
422 | code: "blocked" | Your team blocked this contact. |
502 | message.status: "failed" | Meta refused it. See Meta error codes. |
How do I check a message's status?
GET /messages/:id with the id from the send response:
curl https://app.kilichat.com/api/v1/messages/6f1c2c1e-1b7a-4a63-9d0e-6a2f0c5b9a11 \
-H "Authorization: Bearer YOUR_API_KEY"{ "message": { "id": "…", "status": "delivered", "error": null, "…": "…" } }Rather than checking again and again, use the message.status webhook to be told when the status changes.
How do I list my templates?
GET /templates returns the approved templates:
{
"templates": [
{ "name": "order_shipped", "language": "en", "category": "UTILITY", "body": "Good news, {{1}}: order {{2}} is on its way. Expected delivery: {{3}}.", "variables": 3 }
]
}variables is how many {{n}} placeholders appear in the body.
Which number is my key for?
GET /number returns {"id", "name", "phone"} for the number the key belongs to.
Can I send images, buttons or files through the API?
Not directly. The API sends text and templates. To send a picture or document, use an approved template with a media header and set its file in Sending values. For buttons, use a template with buttons.
Does sending through the API stop a running flow?
No. Only a reply from your team in the inbox stops a flow. API messages are sent alongside.