API Reference
WhatsApp Calling

Toggle transcription for a number's WhatsApp calls

PUT
/v1/numbers/{id}/whatsapp/transcription

Sets the SautiKit-side "transcribe WhatsApp calls" flag. Like recording, this is SautiKit-side speech-to-text over our own SIP media leg — Meta's native transcription is Cloud/WebRTC-only and unavailable to a BYOC SIP integration.

Authorization

bearerAuth
AuthorizationBearer <token>

Long-lived ES256 JWT minted from the dashboard (https://app.sautikit.com/developers/api-keys). Signed by the platform keyring. Carries workspace_id and scopes claims; revoked via the platform deny-list.

In: header

Path Parameters

id*string

The tenant NUMBER id.

Formatuuid

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

curl -X PUT "https://example.com/v1/numbers/497f6eca-6276-4993-bfeb-53cbbbba6f08/whatsapp/transcription" \  -H "Content-Type: application/json" \  -d '{    "enabled": true  }'
{  "transcription_enabled": true}
{  "error": {    "code": "validation.bad_request",    "message": "string",    "request_id": "string",    "details": [      "string"    ],    "resolution": "string",    "reason": "invalid_characters",    "suggested_e164": "+254727524723"  }}
{  "error": {    "code": "validation.bad_request",    "message": "string",    "request_id": "string",    "details": [      "string"    ],    "resolution": "string",    "reason": "invalid_characters",    "suggested_e164": "+254727524723"  }}

Toggle recording for a number's WhatsApp calls PUT

Sets the SautiKit-side "record WhatsApp calls" flag. This is a SautiKit setting, not a Meta one: Meta's native call recording is available only on its Cloud/WebRTC calling path, which a BYOC SIP integration cannot use, so recording is captured on our own SIP media leg instead.

Send a WhatsApp message POST

Sends a text or template message from one of your connected WhatsApp numbers. Pick the sending number with EITHER `number_id` (a Sautikit number id — the same one the rest of this API uses; we map it to Meta's `phone_number_id` for you) OR `connection_id` (a WhatsApp connection, whose WhatsApp number is used). Exactly one is required. A `number_id` that is not yours, or that no connected WhatsApp account carries, is rejected before anything reaches Meta. Returns **202** with Meta's `wamid`. Delivery is asynchronous — subscribe to the `whatsapp.event.received` workspace webhook, which relays Meta's delivery-status payloads to you verbatim. Sautikit does not store messages. The wamid is your handle for correlating the status events that follow. WhatsApp only permits free-form text inside a 24-hour customer service window opened by the contact messaging you. Outside it, use `type: template` with a Meta-approved template. ## Message types `type` names the message, and an object of the SAME NAME carries its contents. That object is forwarded to WhatsApp untouched, so its shape is WhatsApp's — including any type added after this page was written. ### Text ```json { "number_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0", "to": "254712345678", "type": "text", "text": { "body": "Habari Wanjiru — your order from Duka la Vitabu is ready for pickup on Kimathi Street until 6pm.", "preview_url": false } } ``` Set `preview_url: true` to render a preview card for the first link in the body. ### Image, video, audio, document, sticker Media takes EITHER `id` (from `POST /v1/whatsapp/media`) or `link` (a public URL WhatsApp fetches). Prefer `id`: a link is re-fetched on every send and cached for only 10 minutes. Image with a caption — an M-PESA receipt: ```json { "number_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0", "to": "254712345678", "type": "image", "image": { "id": "1234567890123456", "caption": "Receipt for KES 2,400 — Sarit Centre branch" } } ``` Document — an invoice a customer can download: ```json { "type": "document", "document": { "id": "1234567890123456", "filename": "Invoice-INV-2291.pdf", "caption": "Your invoice for July deliveries" } } ``` Audio has no caption (WhatsApp renders it as a voice note); sticker takes only `id` or `link` and must be WebP. ### Location Send a pickup point: ```json { "type": "location", "location": { "latitude": -1.2833, "longitude": 36.8172, "name": "Mama Ngina Street pickup point", "address": "Mama Ngina St, Nairobi CBD" } } ``` ### Contacts Hand over a rider's details: ```json { "type": "contacts", "contacts": [ { "name": { "formatted_name": "Otieno Odhiambo", "first_name": "Otieno" }, "phones": [{ "phone": "+254733000111", "type": "CELL", "wa_id": "254733000111" }], "org": { "company": "Nairobi Swift Deliveries", "title": "Rider" } } ] } ``` ### Interactive: reply buttons Up to three buttons. Confirming a delivery slot: ```json { "type": "interactive", "interactive": { "type": "button", "body": { "text": "Your parcel arrives in Westlands tomorrow. Which slot suits you?" }, "action": { "buttons": [ { "type": "reply", "reply": { "id": "slot_morning", "title": "8am - 12pm" } }, { "type": "reply", "reply": { "id": "slot_afternoon", "title": "12pm - 5pm" } } ] } } } ``` The tap arrives on your webhook as an inbound `interactive` message carrying the `id` you chose. ### Interactive: list Up to ten rows, grouped in sections — good for a menu or a catalogue the customer scrolls: ```json { "type": "interactive", "interactive": { "type": "list", "header": { "type": "text", "text": "Today's menu" }, "body": { "text": "Choose a dish and we'll deliver within 45 minutes." }, "action": { "button": "View menu", "sections": [ { "title": "Mains", "rows": [ { "id": "nyama_choma", "title": "Nyama choma", "description": "Half kilo, KES 750" }, { "id": "pilau", "title": "Pilau ya nyama", "description": "KES 450" } ] } ] } } } ``` ### Interactive: CTA URL button Puts a long or ugly link behind a button: ```json { "type": "interactive", "interactive": { "type": "cta_url", "body": { "text": "Track your parcel from our Mombasa Road depot." }, "action": { "name": "cta_url", "parameters": { "display_text": "Track parcel", "url": "https://track.example.co.ke/p/9F2K" } } } } ``` ### Interactive: location request Ask a customer where to deliver, instead of parsing a typed address: ```json { "type": "interactive", "interactive": { "type": "location_request_message", "body": { "text": "Where should the boda drop your order?" }, "action": { "name": "send_location" } } } ``` ### Interactive: Flow A multi-step form inside the chat — bookings, KYC, surveys. The answers arrive on your webhook as structured data: ```json { "type": "interactive", "interactive": { "type": "flow", "body": { "text": "Book your service appointment at our Thika Road garage." }, "action": { "name": "flow", "parameters": { "flow_message_version": "3", "flow_id": "1234567890", "flow_cta": "Book a slot", "flow_action": "navigate" } } } } ``` ### Reaction React to a message the customer sent you: ```json { "type": "reaction", "reaction": { "message_id": "wamid.HBgMMjU0NzEyMzQ1Njc4...", "emoji": "👍" } } ``` Send an empty `emoji` to remove a reaction. ### Replying in context Any message can quote one the customer sent, which is worth doing when a conversation has several threads running: ```json { "type": "text", "context": { "message_id": "wamid.HBgMMjU0NzEyMzQ1Njc4..." }, "text": { "body": "Yes — that size is in stock at our Nakuru branch." } } ``` ## Templates Outside the 24-hour window you may only send a template Meta has approved. Sautikit does not create templates — that happens in WhatsApp Manager or through Meta's Message Templates API — here you send one by name. `template.components` is forwarded untouched, so its shape is Meta's. Body parameters fill `{{1}}`, `{{2}}` **in order**, and the count must match the approved template exactly or Meta rejects the send. ### Utility template Transactional, tied to something the customer did — an order, a payment, a delivery: ```json { "number_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0", "to": "254712345678", "type": "template", "template": { "name": "order_dispatched", "language_code": "en", "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Wanjiru" }, { "type": "text", "text": "SK-4821" }, { "type": "text", "text": "Tuesday" } ] } ] } } ``` ### Marketing template Promotional. Subject to per-user marketing limits and the recipient's marketing opt-out, so a send can be accepted here and still not delivered: ```json { "type": "template", "template": { "name": "back_to_school_offer", "language_code": "en", "components": [ { "type": "header", "parameters": [ { "type": "image", "image": { "id": "1234567890123456" } } ] }, { "type": "body", "parameters": [{ "type": "text", "text": "20%" }] }, { "type": "button", "sub_type": "url", "index": "0", "parameters": [{ "type": "text", "text": "back-to-school" }] } ] } } ``` ### Authentication template One-time passwords. The body text is fixed by Meta — you supply only the code — and the same code repeats as the button parameter so the customer can copy or autofill it: ```json { "type": "template", "template": { "name": "verification_code", "language_code": "en", "components": [ { "type": "body", "parameters": [{ "type": "text", "text": "418902" }] }, { "type": "button", "sub_type": "url", "index": "0", "parameters": [{ "type": "text", "text": "418902" }] } ] } } ``` Authentication templates have a 10-minute time-to-live (everything else is 30 days) — an undelivered code is dropped rather than arriving long after it expired. They are also delivered only to the customer's primary WhatsApp device. ## Phone number format Include the `+` and country code. Without a `+`, WhatsApp prepends YOUR number's country code — for a Kenyan number `0712345678` becomes `+2540712345678`, which is not a real number. Send `+254712345678` or `254712345678`. ## What a 202 means A 202 means WhatsApp ACCEPTED the request, not that the message arrived. Delivery outcomes reach you on the `whatsapp.event.received` webhook; correlate them by the `wamid` returned here. **API-key scope:** `whatsapp.messages`.