Send
Endpoint
POST https://api.kirisan.com/v1/send
Authorization: Bearer YOUR_ACCOUNT_TOKEN
Content-Type: application/json Sends to one recipient per request. For large batches, use Send → Campaign in the dashboard or call this endpoint once per recipient from your app.
Request body
| Field | Required | Description |
|---|---|---|
keys | Yes | Channel tokens — whatsapp, telegram, waba, email |
content | Yes | Per-channel message — template ID or inline message |
target | Yes | Recipient address per channel + optional variables |
context | No | Scheduling and send behaviour |
keys
{
"keys": {
"whatsapp": {
"token": "DEVICE_TOKEN",
"fallback": false,
"fallback_sequence": 0
},
"email": {
"token": "SENDER_TOKEN",
"fallback": true,
"fallback_sequence": 1
}
}
} Include a key for every channel you intend to send on. See Authentication for where to copy tokens.
content
Each channel entry uses either a saved template or an inline message — not both.
Template reference (recommended — same as Send → Send):
{
"content": {
"whatsapp": { "template": 42 },
"email": { "template": 17 }
}
} Inline message (WhatsApp, Telegram, WABA, and email without a template):
{
"content": {
"whatsapp": {
"message": {
"type": "text",
"message": "Hello {{name}}, your order is ready."
}
},
"email": {
"message": {
"type": "text",
"subject": "Promo for {{name}}",
"message": "Plain text optional",
"html": "<p>Hello {{name}}</p>"
}
}
}
} Email uses either a saved template or an inline body (subject plus message and/or html) — not both. Inline email requires type: "text" and subject.
Three kinds of email
Kirisan puts every email send into one kind. You do not set a separate kind field — Kirisan decides from how you send.
| Kind | When it is used | What happens |
|---|---|---|
| Transactional | A template whose Type is transactional (OTP, receipts, account notices) | Kirisan sends now. The HTTP response waits for the result. |
| Marketing | A template whose Type is marketing (promos, newsletters) | Kirisan accepts first, then sends in order. The response includes "queued": true. |
| Raw | An inline content.email.message with no template | Same as marketing: accepted first, then sent in order. |
What you need to know
- Send → Send in the dashboard always uses a template, so the kind is transactional or marketing from the Type column. Email without a template is API-only.
- Marketing and raw: at most 2 requests per second per domain (both kinds share that limit). A further request in the same second is rejected with
rate_limitedandretry_after. - Marketing templates only go to addresses that have already received email from this domain. A brand-new address is rejected. Transactional and raw can go to a new address.
- Domain credits are deducted when the email actually sends, not when Kirisan answers
queued. - Campaigns on Send → Campaign skip the API wait-in-line — Kirisan sends them directly. If that campaign’s template kind is blocked, the campaign pauses.
When an email-only marketing or raw send is accepted:
{
"status": true,
"queued": true,
"request_id": 12345,
"channels": {
"email": { "status": true, "queued": true }
}
} queued means Kirisan accepted the request. The final result appears in Channel → Email → Logs and, if you set up a send status webhook, as a POST to your URL. context.behaviour.mode does not change this.
message.type | Channels | Notes |
|---|---|---|
text | WhatsApp, Telegram, WABA | Plain text (WABA: approved session rules apply). Telegram buttons attach here via richContext.buttons. |
image, video, document | WhatsApp, Telegram, WABA | Requires richContext.fileUrl or richContext.fileId. Telegram may also include buttons. |
location | WhatsApp, Telegram | Requires richContext.location — message text is ignored |
interactive | WABA | Session reply buttons or list — see Buttons. Not used for Telegram (use text + buttons). |
message.richContext (optional):
| Field | Purpose |
|---|---|
fileUrl | Public HTTPS URL to an attachment |
fileId | ID of a file uploaded to Resources → Files |
fileName | Display filename — Kirisan may append the detected extension |
footer | Footer text (where supported) |
location | { "lat", "lng", "name", "address" } for location messages |
buttons | Button rows — shape depends on channel (see Buttons) |
listMessage | WABA list message object (button label + sections) |
Buttons
Kirisan does not treat “buttons” as one universal type. Channel and message path decide which kinds you can send.
Telegram — inline keyboard (url / callback / copy)
Use type: "text" (or media) with richContext.buttons as an array of rows. Each button needs text and a type:
type | Extra fields | Behaviour |
|---|---|---|
url | url | Opens a link (http / https / tg://) |
callback | callbackData | Sends callback data to your bot (max 64 bytes) |
copy | copyText | Copies text to the user’s clipboard |
{
"type": "text",
"message": "Choose an option:",
"richContext": {
"buttons": [
[
{ "text": "Open site", "type": "url", "url": "https://example.com" },
{ "text": "Confirm", "type": "callback", "callbackData": "confirm" }
],
[
{ "text": "Copy code", "type": "copy", "copyText": "PROMO-10" }
]
]
}
} Same three kinds are available when you build a keyboard under Channel → Telegram → Templates.
WABA — session interactive (reply buttons or list)
Free-form WABA messages (including interactive) do not need a Meta template, but they only work inside Meta’s customer-care / session window — typically after the user messaged you (same rule as free-form text and media). Outside that window, Meta rejects free-form sends; use an approved template instead.
When the session is open, type: "interactive" accepts:
- Reply buttons —
richContext.buttonswithid+textonly (max 3 total; Metareplybuttons). No template required. - List —
richContext.listMessagewith a menubuttonlabel andsections/rows. No template required.
Not available on this free-form path: template-style quick reply, URL, or call buttons.
{
"type": "interactive",
"message": "Need help with order {{order_id}}?",
"richContext": {
"footer": "Reply anytime",
"buttons": [[{ "id": "yes", "text": "Yes" }, { "id": "no", "text": "No" }]]
}
} WABA — Meta templates (quick reply / URL / call)
Use a template when you need to message outside the session window, or when you need quick reply / URL / call buttons.
Those three button kinds belong on a Meta-approved template, not on inline interactive JSON. Create them under Channel → WABA → Templates, then send with content.waba.template: <id>.
| Template button | Meta type | Notes |
|---|---|---|
| Quick reply | quick_reply | User taps → you receive a reply payload |
| URL | url | Opens a link; may include one {{variable}} |
| Call | phone_number | Starts a phone call |
| Path | Needs approved template? | Session window? | Button kinds |
|---|---|---|---|
Free-form interactive | No | Required (open window) | Reply (id/text) or list |
content.waba.template | Yes | Not required | Quick reply / URL / call (as defined on the template) |
Dynamic media headers (image / video / document)
Templates with an IMAGE, VIDEO, or DOCUMENT header need media at send time (Meta Cloud API). When you create the template, you upload a sample for Meta review — that sample does not have to be used on every send.
On POST /v1/send with content.waba.template, pass a public URL in target.variables. The API value overrides the template’s static sample for that message:
| Template header | Key in target.variables |
|---|---|
| Image | default_image_url |
| Video | default_video_url |
| Document | default_file_url |
Body/text header placeholders ({{name}}, etc.) use the same variables map.
{
"keys": { "waba": "YOUR_WABA_DEVICE_TOKEN" },
"content": { "waba": { "template": 42 } },
"target": {
"waba": "6281234567890",
"variables": {
"customer_name": "Alex",
"default_image_url": "https://cdn.example.com/promo.jpg"
}
}
} Meta’s servers must fetch the URL — use a direct HTTPS link to the file (CDN, your host, object storage). Typical Google Drive share links, login pages, or long redirect chains often fail. File type and size must meet Meta limits.
If the template requires a media header and no URL is available (and the template provides no fallback), the send fails — see the channel message in the response or Errors.
In the Examples accordion below, open WABA template — dynamic image header.
WhatsApp (Fonnte device) does not support outbound interactive buttons on /v1/send.
target
{
"target": {
"whatsapp": "6281234567890",
"email": "customer@example.com",
"telegram": "123456789",
"waba": "6281234567890",
"variables": {
"name": "Alex",
"order_id": "A-1001"
}
}
} | Field | Format |
|---|---|
whatsapp, waba | Phone with country code (digits, no + required) |
telegram | Chat ID |
email | Valid email address |
variables | Replaces {{placeholders}} in templates and inline messages before send. For WABA media headers use default_image_url, default_video_url, or default_file_url (see above). |
Omit channel fields you are not sending on. Each active channel in content needs a matching target (unless filtered by context.behaviour.channel).
context
{
"context": {
"timing": {
"schedule": 1735689600
},
"behaviour": {
"channel": "all",
"mode": "sync"
}
}
} | Field | Meaning |
|---|---|
timing.schedule | Unix timestamp (seconds) — queue for future delivery. Omit or set null to send now. Past times are rejected. |
behaviour.channel | "all" (default) — send every channel that has a key and content. Or one of whatsapp, telegram, waba, email to restrict to a single channel. |
behaviour.mode | "sync" (default) — wait for channel results in the HTTP response. "async" — queue immediately (pending in Kirisan logs); results appear in Logs/History automatically. Optional: also receive a POST to your send_status webhook if you configure one. |
When scheduled, the response includes "scheduled": true and each channel shows "scheduled": true instead of delivering immediately. Scheduling uses the scheduled path (not mode: async).
Examples
Every example is a full POST /v1/send request. Open a row for cURL, Node.js, Python, PHP, Go, or raw JSON. Replace tokens, IDs, and targets with your own values.
Fallback cascade
When channel keys use fallback: true, Kirisan tries tiers in order:
- Primary — all keys with no fallback (sent together).
- Fallback 1 — keys with
fallback_sequence: 1(parallel within the tier). - Fallback 2 — keys with
fallback_sequence: 2, and so on.
As soon as any channel in a tier succeeds, later tiers are skipped. This matches Use as fallback and Sequence on Send → Send. See Send messages for a dashboard walkthrough.
Failed channels in an earlier tier still appear under "channels" with "reason". A successful fallback channel may include "fallback": true.
Response
Immediate send (mode: sync or omitted):
{
"status": true,
"id": 98765,
"request_id": 12345,
"channels": {
"whatsapp": {
"status": true,
"id": 98765,
"processing_time": "142ms"
}
}
} id is send.id (integer — the primary Kirisan tracking key). request_id is send_log.id for this /v1/send request. id is omitted when no send row exists yet (e.g. blocked before any channel ran).
Async (mode: async) — accepted into the queue:
{
"status": true,
"queued": true,
"request_id": 12345
} Message id is not available at queue time — the final result is stored automatically in Kirisan logs (send / send_log). If you configure a send_status webhook, the same payload is also POSTed to your URL.
Partial failure:
{
"status": false,
"channels": {
"whatsapp": {
"status": false,
"reason": "Device disconnected",
"processing_time": "89ms"
},
"email": {
"status": true,
"fallback": true,
"processing_time": "891ms"
}
}
} Scheduled:
{
"status": true,
"scheduled": true,
"channels": {
"telegram": { "status": true, "scheduled": true }
}
} Top-level status | Meaning |
|---|---|
true | At least one channel delivered or was scheduled, or the async request was queued |
false | Every attempted channel failed, or validation blocked the request |
Read "reason" on the root for validation errors. Read "channels.<name>.reason" for delivery failures. See Errors.
Kirisan also stores the request + response in the audit log for validation/auth failures (invalid JSON, bad token, bad content, etc.) — the response includes request_id when the log row was written, so support can look up your request.
Channel notes
| Channel | Inline message | Templates |
|---|---|---|
| Yes — text, media, location (no interactive buttons) | Yes — must be approved and active | |
| Telegram | Yes — text/media + inline keyboard (url / callback / copy) | Yes — same button kinds in the template editor |
| WABA | Yes — text, media, interactive reply buttons or list (session window) | Yes — Meta templates with quick reply / URL / call; manage in Channel → WABA → Templates |
Yes — subject plus message and/or html (type: "text") | Yes — create in Channel → Email → Templates |
Templates referenced by ID must be Approved and Active. List IDs via the Templates API (GET /v1/templates), copy from Channel → channel → Templates, or use inline message content instead.
Related
- Authentication — account token and channel keys
- Templates API — list and fetch templates
- Errors — HTTP codes and common reasons
- Send status webhook — async
POST /v1/sendresults - Send messages — same request shape from the UI
- Email templates — Type transactional vs marketing
- WhatsApp templates — create templates and copy IDs for send