Skip to content

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.

bash
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"]
    }
  }'
FieldRequiredNotes
toYesPhone number with country code.
template.nameYesThe template's name in KiliChat, like order_shipped.
template.languageNoLike en or ta. Leave it out if you only have one language; an approved one is picked.
template.variablesNoValues 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):

bash
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:

json
{ "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:

json
{
  "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.
  • status moves queued → sent → delivered → read, or ends at failed.

If WhatsApp refuses the message, you get 502 with the same body, "status": "failed" and Meta's reason in error.

In Node.js ​

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? ​

StatusBodyMeaning
400to: Use the full number with country code, like 919876543210Bad phone number.
400Send either text or a templateBoth or neither were given.
404No template called "…"Check the name (and language) in KiliChat.
422Template "…" isn't approved yetWait for Meta's approval.
422code: "window_closed"Use a template instead of text.
422code: "opted_out"The contact opted out. Don't send them templates.
422code: "blocked"Your team blocked this contact.
502message.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:

bash
curl https://app.kilichat.com/api/v1/messages/6f1c2c1e-1b7a-4a63-9d0e-6a2f0c5b9a11 \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{ "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:

json
{
  "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.