Overview
The Botat API provides comprehensive messaging capabilities for the WhatsApp channel. Key features include:
- Versatile Messaging
- Send regular text, media, and pre-approved template messages for marketing, utility, and authentication.
- Conversation History
- Easily retrieve complete conversation histories with any contact using simple query parameters.
- Contact Management
- Create, validate, and manage your contacts within the Botat platform directly through the API.
API Details
- Base URL
-
https://app.botat.ai/ - Test URL/Endpoint
-
https://app.botat.ai/api/v1/test/ - API Version
-
1.0.0 - Content-Type
-
application/json
Authentication
API Key Authentication
All API endpoints are protected and require a valid API key to be included in the header of every request. This key authenticates your application and links it to your Botat account.
- Header
-
X-API-KEY - Value
-
your_access_token
Account Access Validation
- The API key must belong to an account owner, not an invited team member.
- Each request validates that the user has permission to access the specified inbox phone number.
-
Ensure you are using a primary account key to avoid
INSUFFICIENT_PERMISSIONSerrors.
Brute-Force Protection
To protect your account, we limit failed authentication attempts from a single IP address:
- Production: 10 failed attempts per minute.
- Test Mode: 20 failed attempts per minute.
Exceeding this triggers a
TOO_MANY_AUTH_ATTEMPTS
error.
Rate Limiting & Usage Quotas
To ensure API stability and fair usage, all requests are subject to rate limiting. We employ two types of limits: short-term rate limits to manage request frequency and long-term usage quotas based on your subscription plan.
Request Rate Limits
These limits control the number of requests you can make in short time intervals and are applied per API key.
Production Endpoints
- Per Minute
- 100 requests
- Per Hour
- 5,000 requests
- Per Day
- 100,000 requests
Test Endpoints
- Per Minute
- 20 requests
- Per Hour
- 1000 requests
- Per Day
- 5000 requests
Monitoring Usage via Headers
The API response headers provide real-time information about your current limits. Use these to build robust, adaptive integrations.
Track your short-term request frequency. When you exceed
these limits, you'll receive a
429 Too Many Requests
error.
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
API Endpoints
All endpoints are prefixed with the base URL:
https://app.botat.ai. Each endpoint is detailed
below with its purpose, request structure, and parameters.
Send Regular Message
Send a standard text or media message. This is typically used for replying to a contact within the 24-hour customer service window.
/api/v1/messages/whatsapp/send_message
Request Body
{
"inbox_phone_number": "+1234567890",
"recipient_phone_number": "+1987654321",
"message_text": "Hello, how can I help you?",
"media_url": "https://example.com/image.jpg",
"media_filename": "image.jpg"
}
Request Examples
curl -X POST "https://app.botat.ai/api/v1/messages/whatsapp/send_message" \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inbox_phone_number": "+1234567890",
"recipient_phone_number": "+1987654321",
"message_text": "Hello, how can I help you?"
}'
const response = await fetch(
"https://app.botat.ai/api/v1/messages/whatsapp/send_message",
{
method: "POST",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
inbox_phone_number: "+1234567890",
recipient_phone_number: "+1987654321",
message_text: "Hello, how can I help you?",
}),
}
);
const result = await response.json();
import requests
response = requests.post(
"https://app.botat.ai/api/v1/messages/whatsapp/send_message",
headers={"X-API-KEY": "YOUR_API_KEY"},
json={
"inbox_phone_number": "+1234567890",
"recipient_phone_number": "+1987654321",
"message_text": "Hello, how can I help you?",
},
timeout=30,
)
print(response.json())
<?php
$ch = curl_init("https://app.botat.ai/api/v1/messages/whatsapp/send_message");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-KEY: YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"inbox_phone_number" => "+1234567890",
"recipient_phone_number" => "+1987654321",
"message_text" => "Hello, how can I help you?",
]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);Parameters
| Parameter | Required | Description |
|---|---|---|
inbox_phone_number |
Yes | Your Botat business phone number. |
recipient_phone_number |
Yes | The contact's phone number. |
message_text |
Yes* | Message content (max 4096 chars). |
media_url |
No | Public URL to a media file. |
media_type |
No | `image`, `video`, `audio`, `document`. |
media_caption |
No | Caption for the media file. |
media_filename |
No | Required if `media_url` is provided. |
Send Template Message
Send a pre-approved message template. This is required to initiate conversations or message a user outside the 24-hour window.
/api/v1/messages/whatsapp/send_template
Request Body
{
"inbox_phone_number": "+1234567890",
"recipient_phone_number": "+1987654321",
"template_name": "welcome_message",
"language_code": "en",
"template_parameters": { /* ... */ }
}
Request Examples
curl -X POST "https://app.botat.ai/api/v1/messages/whatsapp/send_template" \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inbox_phone_number": "+1234567890",
"recipient_phone_number": "+1987654321",
"template_name": "welcome_message",
"language_code": "en",
"template_parameters": {
"customer_name": "John Doe"
}
}'
const response = await fetch(
"https://app.botat.ai/api/v1/messages/whatsapp/send_template",
{
method: "POST",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
inbox_phone_number: "+1234567890",
recipient_phone_number: "+1987654321",
template_name: "welcome_message",
language_code: "en",
template_parameters: { customer_name: "John Doe" },
}),
}
);
const result = await response.json();
import requests
response = requests.post(
"https://app.botat.ai/api/v1/messages/whatsapp/send_template",
headers={"X-API-KEY": "YOUR_API_KEY"},
json={
"inbox_phone_number": "+1234567890",
"recipient_phone_number": "+1987654321",
"template_name": "welcome_message",
"language_code": "en",
"template_parameters": {"customer_name": "John Doe"},
},
timeout=30,
)
print(response.json())
<?php
$ch = curl_init("https://app.botat.ai/api/v1/messages/whatsapp/send_template");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-KEY: YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"inbox_phone_number" => "+1234567890",
"recipient_phone_number" => "+1987654321",
"template_name" => "welcome_message",
"language_code" => "en",
"template_parameters" => ["customer_name" => "John Doe"],
]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);Parameters
| Parameter | Required | Description |
|---|---|---|
template_name |
Yes | Name of your approved template. |
language_code |
Yes | Language of the template (e.g., `en`). |
template_parameters |
No | Object with values for template variables. |
media_url |
No | URL for custom media header. |
OTP_code |
No | Required for Authentication templates. |
Get Conversation History
Retrieve the message history for a specific conversation between your inbox and a contact.
/api/v1/messages/whatsapp/conversation
Request Examples
curl -G "https://app.botat.ai/api/v1/messages/whatsapp/conversation" \
-H "X-API-KEY: YOUR_API_KEY" \
--data-urlencode "inbox_phone_number=+1234567890" \
--data-urlencode "contact_phone_number=+1987654321" \
--data-urlencode "limit=20"
const params = new URLSearchParams({
inbox_phone_number: "+1234567890",
contact_phone_number: "+1987654321",
limit: "20",
});
const response = await fetch(
`https://app.botat.ai/api/v1/messages/whatsapp/conversation?${params}`,
{ headers: { "X-API-KEY": "YOUR_API_KEY" } }
);
const result = await response.json();
import requests
response = requests.get(
"https://app.botat.ai/api/v1/messages/whatsapp/conversation",
headers={"X-API-KEY": "YOUR_API_KEY"},
params={
"inbox_phone_number": "+1234567890",
"contact_phone_number": "+1987654321",
"limit": 20,
},
timeout=30,
)
print(response.json())
<?php
$query = http_build_query([
"inbox_phone_number" => "+1234567890",
"contact_phone_number" => "+1987654321",
"limit" => 20,
]);
$ch = curl_init(
"https://app.botat.ai/api/v1/messages/whatsapp/conversation?" . $query
);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-KEY: YOUR_API_KEY"],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);Query Parameters
| Parameter | Required | Description |
|---|---|---|
inbox_phone_number |
Yes | Your Botat business phone number. |
contact_phone_number |
Yes | The contact's phone number. |
limit |
No | Messages per page (Default: 20, Max: 100). |
offset |
No | Messages to skip for pagination. |
after_date |
No | ISO 8601 date filter. |
before_date |
No | ISO 8601 date filter. |
List Templates
Fetch message templates for your account. The endpoint returns approved templates by default and can be filtered by status to inspect pending or rejected templates.
/api/v1/whatsapp/templates
Request Examples
curl -G "https://app.botat.ai/api/v1/whatsapp/templates" \
-H "X-API-KEY: YOUR_API_KEY" \
--data-urlencode "inbox_phone_number=+1234567890" \
--data-urlencode "status=APPROVED"
const params = new URLSearchParams({
inbox_phone_number: "+1234567890",
status: "APPROVED",
});
const response = await fetch(
`https://app.botat.ai/api/v1/whatsapp/templates?${params}`,
{ headers: { "X-API-KEY": "YOUR_API_KEY" } }
);
const result = await response.json();
import requests
response = requests.get(
"https://app.botat.ai/api/v1/whatsapp/templates",
headers={"X-API-KEY": "YOUR_API_KEY"},
params={
"inbox_phone_number": "+1234567890",
"status": "APPROVED",
},
timeout=30,
)
print(response.json())
<?php
$query = http_build_query([
"inbox_phone_number" => "+1234567890",
"status" => "APPROVED",
]);
$ch = curl_init("https://app.botat.ai/api/v1/whatsapp/templates?" . $query);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-KEY: YOUR_API_KEY"],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);Query Parameters
| Parameter | Required | Description |
|---|---|---|
inbox_phone_number |
Yes | Your Botat business phone number. |
category |
No | Filter by `MARKETING`, `UTILITY`, etc. |
language |
No | Filter by language code (e.g., `en_US`). |
status |
No | Filter by status (Default: `APPROVED`). |
Create Contact
Create or validate a contact in your Botat account. This is useful for ensuring a number is valid before sending a message.
/api/v1/contacts/whatsapp/create
Request Body
{
"phone_number": "+1987654321",
"name": "John Doe"
}
Request Examples
curl -X POST "https://app.botat.ai/api/v1/contacts/whatsapp/create" \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+1987654321",
"name": "John Doe"
}'
const response = await fetch(
"https://app.botat.ai/api/v1/contacts/whatsapp/create",
{
method: "POST",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
phone_number: "+1987654321",
name: "John Doe",
}),
}
);
const result = await response.json();
import requests
response = requests.post(
"https://app.botat.ai/api/v1/contacts/whatsapp/create",
headers={"X-API-KEY": "YOUR_API_KEY"},
json={
"phone_number": "+1987654321",
"name": "John Doe",
},
timeout=30,
)
print(response.json())
<?php
$ch = curl_init("https://app.botat.ai/api/v1/contacts/whatsapp/create");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-KEY: YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"phone_number" => "+1987654321",
"name" => "John Doe",
]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);Parameters
| Parameter | Required | Description |
|---|---|---|
phone_number |
Yes | The contact's phone number. |
name |
No | The display name for the contact. |
Guide: Creating WhatsApp Templates
Create WhatsApp message templates and submit them to Meta for review. You can create text-only templates with JSON, or upload a media header and create the template in one multipart request.
Endpoints
| Purpose | Endpoint |
|---|---|
| Create template |
POST /api/v1/whatsapp/templates
|
| Validate creation request |
POST /api/v1/test/whatsapp/templates
|
| Upload header media separately |
POST /api/v1/whatsapp/templates/media
|
| Validate a media upload |
POST /api/v1/test/whatsapp/templates/media
|
All endpoints require X-API-KEY. The requested
inbox_phone_number must belong to the
authenticated Botat account.
Create Template
Create a WhatsApp message template and submit it to Meta for review. Send plain JSON for templates that do not need an image, video, or document header.
/api/v1/whatsapp/templates
To validate the same request without submitting anything
to Meta, send it to
/api/v1/test/whatsapp/templates instead.
The template fields may also be nested under a
template object for compatibility with the
Botat dashboard payload.
Request Body
{
"inbox_phone_number": "+966500000000",
"name": "order_ready",
"category": "UTILITY",
"language": "en_US",
"components": [ /* Meta template components */ ]
}
Request Examples
curl --fail-with-body \
-X POST "https://app.botat.ai/api/v1/whatsapp/templates" \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inbox_phone_number": "+966500000000",
"name": "order_ready",
"category": "UTILITY",
"language": "en_US",
"components": [
{
"type": "BODY",
"text": "Hello {{1}}, your order {{2}} is ready for collection today.",
"example": {
"body_text": [["Ahmed", "1234"]]
}
}
]
}'
const response = await fetch(
"https://app.botat.ai/api/v1/whatsapp/templates",
{
method: "POST",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
inbox_phone_number: "+966500000000",
name: "order_ready",
category: "UTILITY",
language: "en_US",
components: [
{
type: "BODY",
text: "Hello {{1}}, your order {{2}} is ready for collection today.",
example: { body_text: [["Ahmed", "1234"]] },
},
],
}),
}
);
const result = await response.json();
import requests
response = requests.post(
"https://app.botat.ai/api/v1/whatsapp/templates",
headers={"X-API-KEY": "YOUR_API_KEY"},
json={
"inbox_phone_number": "+966500000000",
"name": "order_ready",
"category": "UTILITY",
"language": "en_US",
"components": [
{
"type": "BODY",
"text": "Hello {{1}}, your order {{2}} is ready for collection today.",
"example": {"body_text": [["Ahmed", "1234"]]},
}
],
},
timeout=30,
)
print(response.json())
<?php
$ch = curl_init("https://app.botat.ai/api/v1/whatsapp/templates");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-KEY: YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"inbox_phone_number" => "+966500000000",
"name" => "order_ready",
"category" => "UTILITY",
"language" => "en_US",
"components" => [
[
"type" => "BODY",
"text" => "Hello {{1}}, your order {{2}} is ready for collection today.",
"example" => ["body_text" => [["Ahmed", "1234"]]],
],
],
]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);Parameters
| Parameter | Required | Description |
|---|---|---|
inbox_phone_number |
Yes | A WhatsApp business number owned by the authenticated account. |
name |
Yes | Lowercase letters, numbers, and underscores. Must start with a letter. |
category |
Yes | MARKETING, UTILITY, or AUTHENTICATION. |
language |
Yes | A Meta language code such as en_US or ar. |
components |
Yes | A non-empty array of Meta template components. |
Create Template with Media
The recommended media flow, sent as a single
multipart/form-data request. Botat validates
the template and file, uploads the media to Meta, adds
the correct header component, and submits the template.
/api/v1/whatsapp/templates
@ before the local path is required. Without it,
curl sends the path as text and the API returns
FILE_NOT_UPLOADED. In browsers, use
FormData and never set the
Content-Type header manually. Do not add the
media HEADER component yourself in this
one-request flow.
Supported Header Media
| Header type | Accepted MIME types | Maximum size |
|---|---|---|
| Document | application/pdf |
10MB |
| Image |
image/jpeg, image/png
|
5MB |
| Video | video/mp4 |
12MB |
Botat verifies both the declared MIME type and the file's binary signature.
Use exactly one file field per request. In the cURL example, replace the PDF line with the matching line for your media (other languages only need the matching filename and MIME type):
# JPEG
-F "file=@./header.jpg;type=image/jpeg"
# PNG
-F "file=@./header.png;type=image/png"
# MP4
-F "file=@./header.mp4;type=video/mp4"
Request Examples
curl --fail-with-body \
-X POST "https://app.botat.ai/api/v1/whatsapp/templates" \
-H "X-API-KEY: YOUR_API_KEY" \
-F "inbox_phone_number=+966500000000" \
-F 'template={
"name": "account_document_ready",
"category": "UTILITY",
"language": "en_US",
"components": [
{
"type": "BODY",
"text": "Your requested account document is ready. Use the button below for more information."
},
{
"type": "BUTTONS",
"buttons": [
{
"type": "URL",
"text": "Visit Botat",
"url": "https://botat.ai"
}
]
}
]
}' \
-F "file=@./account-document.pdf;type=application/pdf"
// In browsers, take the file from a file input. Do not set the
// Content-Type header manually: the browser must add the
// multipart boundary itself.
const fileInput = document.querySelector('input[type="file"]');
const formData = new FormData();
formData.append("inbox_phone_number", "+966500000000");
formData.append(
"template",
JSON.stringify({
name: "account_document_ready",
category: "UTILITY",
language: "en_US",
components: [
{
type: "BODY",
text: "Your requested account document is ready. Use the button below for more information.",
},
{
type: "BUTTONS",
buttons: [
{ type: "URL", text: "Visit Botat", url: "https://botat.ai" },
],
},
],
})
);
formData.append("file", fileInput.files[0]);
const response = await fetch(
"https://app.botat.ai/api/v1/whatsapp/templates",
{
method: "POST",
headers: { "X-API-KEY": "YOUR_API_KEY" },
body: formData,
}
);
const result = await response.json();
import json
import requests
template = {
"name": "account_document_ready",
"category": "UTILITY",
"language": "en_US",
"components": [
{
"type": "BODY",
"text": "Your requested account document is ready. Use the button below for more information.",
},
{
"type": "BUTTONS",
"buttons": [
{"type": "URL", "text": "Visit Botat", "url": "https://botat.ai"}
],
},
],
}
with open("account-document.pdf", "rb") as file:
response = requests.post(
"https://app.botat.ai/api/v1/whatsapp/templates",
headers={"X-API-KEY": "YOUR_API_KEY"},
data={
"inbox_phone_number": "+966500000000",
"template": json.dumps(template),
},
files={"file": ("account-document.pdf", file, "application/pdf")},
timeout=60,
)
print(response.json())
<?php
$template = json_encode([
"name" => "account_document_ready",
"category" => "UTILITY",
"language" => "en_US",
"components" => [
[
"type" => "BODY",
"text" => "Your requested account document is ready. Use the button below for more information.",
],
[
"type" => "BUTTONS",
"buttons" => [
["type" => "URL", "text" => "Visit Botat", "url" => "https://botat.ai"],
],
],
],
]);
$ch = curl_init("https://app.botat.ai/api/v1/whatsapp/templates");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["X-API-KEY: YOUR_API_KEY"],
CURLOPT_POSTFIELDS => [
"inbox_phone_number" => "+966500000000",
"template" => $template,
"file" => new CURLFile("account-document.pdf", "application/pdf"),
],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);Parameters
| Parameter | Required | Description |
|---|---|---|
inbox_phone_number |
Yes | A WhatsApp business number owned by the authenticated account. |
template |
Yes | The template JSON as a string. Same fields as Create Template, without the media HEADER component. |
file |
Yes | The header media file. See Supported Header Media. |
Upload Header Media Separately
Use the optional two-step flow when your integration needs direct control over the Meta upload handle. Upload the file here first, then create the template with Create Template.
/api/v1/whatsapp/templates/media
To validate the file without uploading it to Meta, send
the same request to
/api/v1/test/whatsapp/templates/media
instead.
Request Examples
curl --fail-with-body \
-X POST "https://app.botat.ai/api/v1/whatsapp/templates/media" \
-H "X-API-KEY: YOUR_API_KEY" \
-F "inbox_phone_number=+966500000000" \
-F "file=@./account-document.pdf;type=application/pdf"
const fileInput = document.querySelector('input[type="file"]');
const formData = new FormData();
formData.append("inbox_phone_number", "+966500000000");
formData.append("file", fileInput.files[0]);
const response = await fetch(
"https://app.botat.ai/api/v1/whatsapp/templates/media",
{
method: "POST",
headers: { "X-API-KEY": "YOUR_API_KEY" },
body: formData,
}
);
const result = await response.json();
import requests
with open("account-document.pdf", "rb") as file:
response = requests.post(
"https://app.botat.ai/api/v1/whatsapp/templates/media",
headers={"X-API-KEY": "YOUR_API_KEY"},
data={"inbox_phone_number": "+966500000000"},
files={"file": ("account-document.pdf", file, "application/pdf")},
timeout=60,
)
print(response.json())
<?php
$ch = curl_init("https://app.botat.ai/api/v1/whatsapp/templates/media");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["X-API-KEY: YOUR_API_KEY"],
CURLOPT_POSTFIELDS => [
"inbox_phone_number" => "+966500000000",
"file" => new CURLFile("account-document.pdf", "application/pdf"),
],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);Parameters
| Parameter | Required | Description |
|---|---|---|
inbox_phone_number |
Yes | A WhatsApp business number owned by the authenticated account. |
file |
Yes | The header media file. See Supported Header Media above. |
Success Response
Copy the returned data.component into the
template's components array. The test upload
endpoint validates the file but returns
handle: null because it does not upload anything
to Meta.
{
"success": true,
"api_version": "1.0.0",
"data": {
"handle": "META_UPLOAD_HANDLE",
"h": "META_UPLOAD_HANDLE",
"header_handle": ["META_UPLOAD_HANDLE"],
"media_type": "DOCUMENT",
"mime_type": "application/pdf",
"file_name": "account-document.pdf",
"file_size": 12345,
"component": {
"type": "HEADER",
"format": "DOCUMENT",
"example": {
"header_handle": ["META_UPLOAD_HANDLE"]
}
}
},
"message": "Template media uploaded successfully"
}
Validate Before Submission
Replace /api/v1/ with
/api/v1/test/ and send the same JSON or
multipart request. Test mode checks authentication, inbox
ownership, the template, and any uploaded file, but does not
call Meta.
Validation errors identify the exact field and suggest how to fix it:
{
"success": false,
"api_version": "1.0.0",
"error": {
"code": "TEMPLATE_VALIDATION_FAILED",
"message": "The WhatsApp template payload is invalid",
"details": "1 validation error(s) found. Fix validation_errors and retry.",
"validation_errors": [
{
"field": "components[0].text",
"code": "PARAMETERS_WORD_RATIO_EXCEEDED",
"message": "Body has 5 non-variable word(s) for 3 variable(s); Meta requires at least 7.",
"suggestion": "Add at least 2 descriptive non-variable word(s), or remove variables."
}
],
"template_help": {
"description": "Use sequential numbered variables, realistic examples, and at least 2P + 1 non-variable body words for P variables."
}
}
}
- Numbered variables must be sequential and have matching, non-empty examples.
-
Named variables require
parameter_format: "NAMED"and matching named examples. -
A body with
Pvariables needs at least2P + 1non-variable words. - Header, footer, buttons, authentication, OTP, and TTL rules are checked before submission.
Success Response
Production returns HTTP 201 when Meta accepts the
template for review.
{
"success": true,
"api_version": "1.0.0",
"data": {
"template_id": "123456789",
"template_name": "order_ready",
"template_category": "UTILITY",
"language": "en_US",
"status": "PENDING",
"submitted_at": "2026-08-04T12:00:00.000Z",
"meta_response_details": {
"id": "123456789",
"status": "PENDING",
"category": "UTILITY"
}
},
"message": "WhatsApp template submitted successfully"
}
GET
/api/v1/whatsapp/templates?inbox_phone_number=...&status=PENDING
to check its current status.
Guide: Sending Template Messages
This guide will walk you through the process of sending pre-approved WhatsApp Business templates, from constructing the basic request to handling complex parameter types and errors.
1 Endpoint & Basic Request
All template messages are sent using a POST request to the following endpoint. Start with this basic structure and modify it based on your template's needs.
/api/v1/messages/whatsapp/send_template
{
"inbox_phone_number": "+1234567890",
"recipient_phone_number": "+1987654321",
"template_name": "your_template_name",
"language_code": "en",
"template_parameters": { /* ... */ }
}
2 Constructing Template Parameters
The structure of the template_parameters object
is critical and depends entirely on your template's design.
Select a tab below to see the correct structure for each use
case.
For templates with variables like
{{customer_name}}.
{
"template_parameters": {
"customer_name": "John Doe",
"platform_name": "Our Platform"
}
}
3 Troubleshooting Common Errors
If your request fails, the API will return a descriptive error. The most common issue is a parameter mismatch, for which the API provides a helpful guide.
Template Parameter Validation Error
This error includes a template_help object with a
correct example to guide you.
{
"success": false,
"error": {
"code": "TEMPLATE_VALIDATION_FAILED",
"message": "Template parameters are missing or invalid",
"template_help": {
"type": "named_parameters",
"example_request": {
"template_parameters": {
"customer_name": "your_customer_name_value"
}
}
}
}
}
4 Quick Reference & Best Practices
Parameter Rules
-
Named vars go directly in
template_parameters. -
Numbered vars are grouped by component
(
body,header). -
Button vars use indexed keys
(
"0","1") in abuttonobject.
Best Practices
- Test first: Always use the test endpoint to validate requests.
- Handle limits: Implement exponential backoff for rate limit errors.
- Check templates: Use the "List Templates" endpoint to see parameter requirements before sending.
Request/Response Structures
Standard Response Format
All API responses, whether successful or not, follow a consistent JSON structure.
{
"success": true,
"api_version": "1.0.0",
"test_mode": false,
"data": { /* Endpoint-specific data object */ },
"message": "A human-readable success message"
}
Error Response Format
When a request fails, success will be
false and an error object will be
included with specific details.
{
"success": false,
"api_version": "1.0.0",
"test_mode": false,
"error": {
"code": "ERROR_CODE_IDENTIFIER",
"message": "A human-readable error message explaining the issue.",
"details": "Additional technical details about the error.",
"validation_errors": [
"The recipient_phone_number field is required."
],
"template_help": { /* Included for template validation errors */ }
}
}
Template Parameter Types
Message templates can use different types of variables
(parameters). The Botat API supports them all, and it's
important to structure your
template_parameters object correctly.
1. Named Parameters
Used for variables like {{customer_name}}. Place
them directly in the root of the
template_parameters object.
"template_parameters": {
"customer_name": "John Doe",
"platform_name": "Our Platform"
}
2. Numbered Parameters
Used for traditional variables like {{1}}. These
must be nested inside a body object.
"template_parameters": {
"body": {
"1": "John Doe",
"2": "ORD-12345"
}
}
3. Button Parameters
Parameters for dynamic URLs in buttons are always indexed
(starting from 0) and nested inside a
button object.
"template_parameters": {
"button": {
"0": "https://example.com/website1",
"1": "https://example.com/website2"
}
}
4. Mixed Parameters
If a template uses a combination of parameter types (e.g., named in the header, numbered in the body), combine the structures.
"template_parameters": {
"customer_name": "John Doe",
"body": {
"1": "Premium Plan",
"2": "January 31, 2025"
},
"button": {
"0": "https://example.com/renew",
"1": "https://example.com/upgrade"
}
}
5. Authentication (OTP) Templates
For AUTHENTICATION templates, simply provide the
OTP_code. The API automatically formats it for the
body and button components.
"template_parameters": {
"OTP_code": "123456"
}
Template Validation Help
If you structure the parameters incorrectly, the API will return
a helpful template_help object in the error
response, guiding you on the correct structure for that specific
template.
{
"error": {
"code": "TEMPLATE_VALIDATION_FAILED",
"template_help": {
"type": "mixed_parameters",
"description": "This template uses mixed parameter types...",
"example_request": {
"template_parameters": {
"customer_name": "your_customer_name_value",
"body": { "1": "value_for_{{1}}", "2": "value_for_{{2}}" },
"button": { "0": "url_for_button_0", "1": "url_for_button_1" }
}
}
}
}
}
Error Codes Reference
Our API uses standard HTTP status codes to indicate the
success or failure of a request. Errors are returned with a
consistent JSON body containing a specific
code and a helpful message. Below is a reference
of common errors grouped by HTTP status code.
Validation Rules & Requirements
To ensure data integrity and security, all incoming requests are rigorously validated. Understanding these rules is key to successful integration.
Phone Number Format
-
Must be in international format, starting with a
+followed by the country code (e.g.,+1234567890). -
Spaces, parentheses, and dashes are automatically
removed (e.g.,
+1 (234) 567-8900is valid). - Must contain 8-15 digits after the country code.
Content Length Limits
| Field | Max Length |
|---|---|
message_text |
4096 characters |
template_name |
256 characters |
| Template parameter value | 1024 characters |
media_caption |
1024 characters |
Message Media Requirements
- Maximum File Size
- 5MB per file.
- Supported Images
- JPEG, PNG, GIF, WebP.
- Supported Videos
- MP4, 3GPP, QuickTime, AVI, MKV.
- Supported Audio
- AAC, MP3, OGG, WAV, Opus.
- Supported Documents
- PDF, ZIP, DOC/DOCX, TXT.
These limits apply to message media URLs. Template header uploads use the type-specific limits in the Create Template Guide.
Media Validation Process
When you provide a media_url, we perform a series
of checks to ensure the file is valid and secure before
sending it.
-
1
URL Validation & SSRF Protection
-
2
File Accessibility & Size Check
-
3
Magic Number Detection
-
4
Content-Type Matching
Security Measures
Security is a top priority at Botat. We've implemented a multi-layered security strategy to protect your data, our platform, and your customers. Here are the key measures in place.
Network Security
- SSRF Protection
-
We prevent Server-Side Request Forgery by blocking all
media_urlrequests to private and reserved IP networks, ensuring we only fetch from public sources. - Request Timeouts
- API requests automatically time out after 30 seconds to protect against slow loris attacks and ensure system stability.
Application Security
- Secure Headers
-
All API responses include security headers like
X-Frame-OptionsandX-Content-Type-Optionsto protect your application from common web vulnerabilities. - Payload Size Limits
- Multipart fields are limited to 1MB. Template header files are limited by type, up to 12MB, preventing resource exhaustion from oversized uploads.
- Early Authentication
- API-key authentication and rate limiting run before multipart files are buffered. Inbox ownership is verified before media is uploaded or a template is submitted.
Data Protection
- Phone Number Masking
-
To protect privacy, phone numbers in API responses and
logs are always partially masked (e.g.,
+12***890). - Input Sanitization
- All user-provided inputs are sanitized before logging to prevent log injection vulnerabilities.
- Secure File Handling
- We use magic number detection to verify file types, preventing content-type spoofing and malicious file uploads.
Testing & Development
A robust testing strategy is essential for a successful integration. We provide a complete sandbox environment that allows you to validate every aspect of your code without sending real messages or incurring charges.
Sandbox Environment
Every production endpoint has a corresponding test endpoint.
To use it, simply insert /test into the URL path.
You can also try both modes interactively with the
Try It console shown next to every endpoint
in this documentation.
/api/v1/messages/whatsapp/send_template
/api/v1/test/messages/whatsapp/send_template
/api/v1/whatsapp/templates
/api/v1/test/whatsapp/templates
For template creation, test mode performs full local template and media validation but never uploads the file or submits the template to Meta.
Validate Requests
Check your request structure, authentication, and parameters without sending a real message.
No Costs or Side-Effects
Avoid incurring charges from the underlying provider (Meta) and prevent sending accidental test messages to real customers.
Higher Rate Limits
The sandbox environment offers higher rate limits, allowing for more rapid development and integration testing.
Simulated Responses
Test mode returns a simulated success or failure, including the `test_mode: true` flag, allowing you to debug your integration safely.
Development Checklist
Follow these best practices to ensure a smooth and reliable integration.
- Use Test Endpoints First: Always validate your requests against the sandbox before deploying to production.
- Handle Rate Limits Gracefully: Implement exponential backoff when you receive a `429` error to avoid being blocked.
- Parse Error Details: Don't just check for success/failure. Use the `code` and `template_help` objects in error responses to debug effectively.
- Secure Media URLs: Ensure all `media_url` links are public, directly accessible, and use HTTPS.
- Monitor Usage Headers: Keep an eye on `X-API-Requests-Remaining` to stay within your monthly plan limits.
Support & Resources
You've reached the end of the guide! Here you'll find a summary of key information and best practices to ensure your integration is a success.
Need Assistance?
The most effective way to get help is to review the API's
error responses. They are designed to be self-explanatory and
often include a template_help object or detailed
message to guide you. If you're still stuck, our support team
is ready to help!
HTTP Status Codes
A quick reference for the primary HTTP status codes our API uses.
| 200 OK | Request successful. |
| 400 Bad Request | Your request has a validation error. |
| 401 Unauthorized | Your API key is missing or invalid. |
| 403 Forbidden | You don't have permission for this action. |
| 404 Not Found | The requested resource does not exist. |
| 429 Too Many Requests | You've hit a rate limit. |
| 500 Server Error | An unexpected error occurred on our side. |
Response Time Expectations
Typical response times for our API endpoints.
- Regular & Template Messages
- < 3 seconds
- Media Messages
- 5-10 seconds (depending on file size)
- Listing & History Endpoints
- < 2 seconds
Final Best Practices
Monitor Headers
Actively monitor the
X-RateLimit-Remaining and
X-API-Requests-Remaining headers to avoid
hitting limits unexpectedly.
Cache When Possible
Cache the response from the "List Templates" endpoint. Your available templates won't change frequently, and this will reduce redundant API calls.
You now have all the tools to build amazing things with the Botat API.
Happy Building!