curl --request POST \
--url https://app.thinnest.ai/api/v1/messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": "+91 98765 43210",
"template": "order_confirmation_v1",
"language": "en",
"variables": [
"Priya",
"#10432",
"Tuesday"
],
"buttonVariables": [
"10432"
]
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
to: '+91 98765 43210',
template: 'order_confirmation_v1',
language: 'en',
variables: ['Priya', '#10432', 'Tuesday'],
buttonVariables: ['10432']
})
};
fetch('https://app.thinnest.ai/api/v1/messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://app.thinnest.ai/api/v1/messages"
payload = {
"to": "+91 98765 43210",
"template": "order_confirmation_v1",
"language": "en",
"variables": ["Priya", "#10432", "Tuesday"],
"buttonVariables": ["10432"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"ok": true,
"messageRef": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSQjM2RDVGMEU4QzA3RjE2QTYA"
}{
"error": "That number could not be read — too short to be a phone number."
}{
"error": "Send a valid API key as `Authorization: Bearer <key>`."
}{
"error": "The Free plan includes 200 WhatsApp messages, and they are used. Switch to Pay as you go — there is no monthly fee — to keep sending."
}{
"error": "This API key is read-only: it can read everything but change nothing."
}{
"error": "That template is not approved in hi.",
"available": [
"en",
"mr"
]
}{
"error": "That customer has opted out of marketing messages."
}{
"error": "Over 60 messages a minute. Slow down and retry."
}{
"error": "(#131026) Message undeliverable"
}{
"error": "WhatsApp is not configured on this deployment."
}Send Message
Sends one approved template to one number and records it in that customer’s conversation. Marketing templates are refused for a customer who opted out; utility and authentication templates are not, since an order confirmation is not something a customer opts out of. Each send is charged like any WhatsApp template from your number, and the Free plan stops at its included messages. At most 60 sends a minute per workspace; send an Idempotency-Key so a retry never sends twice. Needs a full key.
curl --request POST \
--url https://app.thinnest.ai/api/v1/messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": "+91 98765 43210",
"template": "order_confirmation_v1",
"language": "en",
"variables": [
"Priya",
"#10432",
"Tuesday"
],
"buttonVariables": [
"10432"
]
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
to: '+91 98765 43210',
template: 'order_confirmation_v1',
language: 'en',
variables: ['Priya', '#10432', 'Tuesday'],
buttonVariables: ['10432']
})
};
fetch('https://app.thinnest.ai/api/v1/messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://app.thinnest.ai/api/v1/messages"
payload = {
"to": "+91 98765 43210",
"template": "order_confirmation_v1",
"language": "en",
"variables": ["Priya", "#10432", "Tuesday"],
"buttonVariables": ["10432"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"ok": true,
"messageRef": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSQjM2RDVGMEU4QzA3RjE2QTYA"
}{
"error": "That number could not be read — too short to be a phone number."
}{
"error": "Send a valid API key as `Authorization: Bearer <key>`."
}{
"error": "The Free plan includes 200 WhatsApp messages, and they are used. Switch to Pay as you go — there is no monthly fee — to keep sending."
}{
"error": "This API key is read-only: it can read everything but change nothing."
}{
"error": "That template is not approved in hi.",
"available": [
"en",
"mr"
]
}{
"error": "That customer has opted out of marketing messages."
}{
"error": "Over 60 messages a minute. Slow down and retry."
}{
"error": "(#131026) Message undeliverable"
}{
"error": "WhatsApp is not configured on this deployment."
}Authorizations
Your API key (ta_live_…) from Settings → API keys, sent as Authorization: Bearer <key>. Keep it on a server: it can message every customer you have. A key is full, build or read-only; a request its level does not allow is refused with 403.
Headers
Any unique string. A retry with the same key within 24 hours returns the first request's answer instead of acting twice.
255Developers only: the customer workspace this request acts in — its org_… id from POST /customers. Leave it out to act in your own workspace.
"org_3fKq9TzQ1mN8vB2xR7cLpA"
Body
The customer's number. Include the + and the country code (a leading 00 works too); spaces are fine. Without a +, a number of ten digits or fewer — or one starting with 0 — is read as an Indian number and 91 is added, so a foreign number sent bare goes to the wrong country.
"+91 98765 43210"
The name of an approved template in your workspace. Templates are named, not given by id, so the name stays stable across edits.
"order_confirmation_v1"
Which approved language version to send, exactly as approved — en, hi, en_US. Leave it out for the oldest approved version, so adding a language in the console never changes what a working integration sends.
"en"
Values for the body's {{1}}, {{2}} … in order. Numbers are sent as text. For an authentication template, the code goes here once — it is copied into the copy-code button for you.
["Priya", "#10432", "Tuesday"]
The value for a link button's placeholder. Separate from variables because WhatsApp addresses the button on its own. Leave it out on an authentication template.
["10432"]
A public link to the picture, video or document for a template with a media header. WhatsApp needs one on every send; leave it out and the template's own stored file is used.
"https://kaapico.in/parcels/10432.jpg"
Pre-fills a WhatsApp form the template's button opens. Only fields the form marked pre-fillable reach the screen; other keys are ignored rather than refused. Values are sent as text.
Show child attributes
Show child attributes
{ "order_number": "10432" }
Same as the Idempotency-Key header, for clients that cannot set headers. The header wins when both are sent.
Response
WhatsApp accepted the message — not yet that it arrived; delivery and read are reported afterwards. A replay of an Idempotency-Key that already succeeded answers the same with deduplicated: true and an Idempotent-Replayed: true header.
WhatsApp accepted the message.
WhatsApp's own receipt for the message — what delivery and read reports refer to, and what to quote when asking what happened to one.
Present, and true, only on a replay of an Idempotency-Key that already succeeded: nothing was sent this time.