Send WhatsApp messages from your code — plain REST + JSON. Every endpoint returns JSON, accepts JSON, and uses standard HTTP status codes.
📖 For AI coding tools: Share /llm-api-reference.txt with ChatGPT, Claude, Cursor, or any AI — it's a clean plain-text reference with all endpoints, parameters, error codes, and ready-to-use code examples (cURL, JavaScript, Python, PHP, Google Apps Script). The AI will write integration code for you instantly.
Authentication
Every API request must include your API key in the x-api-key header. Get your key after creating an account — it appears in Settings → API Key.
x-api-key: wag_sk_YOUR_API_KEY_HERE
Base URL
All endpoints below are relative to:
https://your-domain.com/api/v1
Replace with the host where the gateway is deployed. After logging in, your exact base URL is shown on the dashboard.
Picking a phone (slots)
One API key authenticates your account. You can connect up to two WhatsApp numbers ("slots"). To choose which one sends, add a slot field to the request body:
Omit it → Phone 1 (default)
"slot": "a" → Phone 1
"slot": "b" → Phone 2 (Pro plan only)
// Send from Phone 2 (Pro)
{ "phone": "919876543210", "message": "Hi", "slot": "b" }
// Response always echoes which slot + the actual sender number
{ "success": true, "slot": "b", "from_phone": "91XXXXXXXXXX" }
Quick start
Hello world in three popular languages. Replace YOUR_API_KEY and the destination phone number.
curl -X POST "https://your-domain.com/api/v1/send-message" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"phone":"919876543210","message":"Hello from API!"}'
// Node.js (fetch is built-in from v18+; in browsers it's native too)const res = awaitfetch('https://your-domain.com/api/v1/send-message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
phone: '919876543210',
message: 'Hello from API!'
})
});
const data = await res.json();
console.log(data);
POST/send-messageText or media to contacts, groups, or multiple targets — one endpoint for everything
POST/send-bulkUp to 500 contacts with {{name}} merge variables
POST/broadcastSame message to up to 100 groups and/or saved contacts
GET/contactsList saved contacts (Basic 100 · Pro 200 · Business 500)
POST/contactsSave one contact
POST/contacts/bulkImport many contacts at once
PUT/contacts/:idEdit a saved contact
DELETE/contacts/:idRemove a saved contact
POST/check-whatsappCheck if numbers are on WhatsApp
POST/schedule-messageSend at a future timestamp
GET/statusConnection state for all slots + counts
GET/groupsList groups you belong to
GET/messagesRecent send history + stats
GET/queueCurrent bulk send queue status
GET/scheduledPending scheduled messages
GET/subscriptionYour plan + usage limits
GET/webhookYour outgoing webhook config
PUT/webhookUpdate outgoing webhook URL
POST/wh/in/<trigger_token>No-auth trigger URL for external services
POST /send-message
The single unified endpoint for sending anything — text, images, documents, videos — to any combination of contacts and groups.
Field
Type
Required
Description
phone
string | string[]
Required
Phone number (919876543210), group ID (120363xxx@g.us), or an array of both
message
string
For text
Text body. Max 4096 chars. Supports emoji, line breaks (\n), WhatsApp markdown.
type
string
For media
image, document, pdf, or video
media_url
string
For media
Public URL of the file (must be reachable from the server)
media_base64
string
For media
Base64-encoded file (alternative to media_url). Max ~1.5MB.
filename
string
Optional
Display filename (recommended for documents/PDFs)
caption
string
Optional
Caption shown under the media
slot
string
Optional
"a" (default), "b", "c", "d", "e" — based on your plan
// ─── TEXT EXAMPLES ───// Text to one contact
{
"phone": "919876543210",
"message": "Hello!"
}
// Text to a group
{
"phone": "120363025123456789@g.us",
"message": "Hi everyone!"
}
// Text to multiple targets (contacts + groups)
{
"phone": ["919876543210", "918765432109", "120363025123456789@g.us"],
"message": "Hello all!"
}
// ─── MEDIA EXAMPLES ───// Image to one contact
{
"phone": "919876543210",
"type": "image",
"media_url": "https://example.com/photo.jpg",
"caption": "Check this out!"
}
// PDF to a group
{
"phone": "120363025123456789@g.us",
"type": "pdf",
"media_url": "https://example.com/invoice.pdf",
"filename": "invoice.pdf"
}
// Image to multiple targets
{
"phone": ["919876543210", "918765432109", "120363025123456789@g.us"],
"type": "image",
"media_url": "https://example.com/photo.jpg"
}
// Response (200)
{
"success": true,
"queued": 3,
"targets": ["919876543210", "918765432109", "120363025123456789@g.us"],
"slot": "a",
"from_phone": "91XXXXXXXXXX"
}
POST /send-bulk
Send to up to 500 contacts in one call. Use {{name}} and {{phone}} as merge variables. The server enforces a 25s delay between messages plus a 2-minute rest every 20 sends to avoid bans. Trial plan blocked.
Field
Type
Required
Description
contacts
array
Required
Up to 500 objects, each with phone and optional name
message
string
Required
Template. Use {{name}} and {{phone}} for substitution.
slot
string
Optional
"a" or "b"
{
"contacts": [
{ "phone": "919876543210", "name": "Rahul" },
{ "phone": "918765432109", "name": "Priya" }
],
"message": "Hi {{name}}, your order is ready!",
"slot": "a"
}
// Response — queued for sending
{
"success": true,
"queued": 2,
"estimated_time_sec": 50
}
POST /broadcast Pro
Send the same message to a mix of groups and individual contacts, up to 100 recipients per request. Recipients can come from three sources, used together or separately. Duplicates across sources are removed automatically. Trial plan blocked.
Field
Type
Required
Description
message
string
Required
Text to broadcast. Max 4096 chars.
groups
array
One of
List of { id, name? } — group JIDs (with or without @g.us)
contacts
array
One of
List of { phone, name? } — raw numbers, 10–15 digits, no +
contact_ids
array
One of
List of saved-contact ids from /contacts (e.g. "ct_a1b2c3")
slot
string
Optional
"a" or "b"
// Mix groups + raw numbers + saved-contact ids in a single broadcast
{
"message": "Diwali sale starts tonight!",
"groups": [
{ "id": "120363025111111111@g.us", "name": "VIP Customers" }
],
"contacts": [
{ "phone": "919876543210", "name": "Rahul" }
],
"contact_ids": ["ct_a1b2c3d4e5f6", "ct_f6e5d4c3b2a1"]
}
// Response — queued for sending
{
"success": true,
"queued": 4,
"breakdown": { "groups": 1, "contacts": 3 },
"estimated_time_sec": 110
}
Tip: Pre-save your VIP list with POST /contacts/bulk, then trigger broadcasts using contact_ids — no need to re-send phone numbers each time.
POST /check-whatsapp
Check whether one or more numbers are registered on WhatsApp before sending.
Recent send history. Query parameters: type (single, group, broadcast), status (sent, failed, queued), limit (default 50, max 500). Each row includes slot and slotPhone.
GET /queue
Returns the current bulk send queue — items pending, in progress, and processing rate. Useful for status bars.
GET /scheduled
Returns all your pending scheduled messages. Delete with DELETE /scheduled/:id.
GET /subscription
Returns your active plan, expiry date, daily message limit, and whether bulk/Pro features are unlocked.
Message Templates
Save reusable message snippets with {{variable}} placeholders, and fill them in at send time. Useful for order confirmations, OTP messages, reminders, and any text you reuse with small per-recipient changes.
Pro only: Creating and editing templates requires the Pro plan. Listing, deleting, and the /use counter work on all plans.
Max 50 templates per account, 60 chars per name, 4096 chars per body.
Template variables follow the pattern {{variable_name}} — alphanumeric and underscores only. The API automatically extracts variable names from your content into a variables array. You then substitute them client-side before calling /send-message:
// Template body"Hi {{name}}, your order #{{order_id}} is on the way!"// → API extracts: variables = ["name", "order_id"]// → You substitute and send via /send-message
GET /templates
List all your saved templates, newest first. Available on every plan.
Create a new template. Variables are auto-extracted from content.
Field
Type
Required
Description
name
string
Required
Human-readable name (max 60 chars)
content
string
Required
Body with optional {{variable}} placeholders (max 4096 chars)
category
string
Optional
Tag for grouping in the dashboard (max 30 chars)
curl -X POST "https://your-domain.com/api/v1/templates" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"name": "Order Confirmation",
"content": "Hi {{name}}, your order #{{order_id}} is confirmed.",
"category": "transactional"
}'
PUT /templates/:id Pro
Update an existing template. Pass any subset of name, content, category. Variables re-extract on every content change.
curl -X PUT "https://your-domain.com/api/v1/templates/tpl_a3f9b2c1d4" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"content": "Hi {{name}}, your order is out for delivery!"}'
DELETE /templates/:id
Delete a template permanently. Returns 404 if the template ID does not exist.
Increment the usageCount for a template — optional telemetry call that lets you sort/rank templates by popularity in your own UI. Returns the updated template.
// JavaScript example — bump usage when you actually use a templateasync functionsendFromTemplate(templateId, recipient, values) {
const tpls = await (awaitfetch('/api/v1/templates', {
headers: { 'x-api-key': API_KEY }
})).json();
const tpl = tpls.templates.find(t => t.id === templateId);
// substitute {{variable}} placeholderslet body = tpl.content;
for (const [k, v] of Object.entries(values)) {
body = body.replace(newRegExp('\\{\\{\\s*' + k + '\\s*\\}\\}', 'g'), v);
}
// send and bump usage counter (fire-and-forget)awaitfetch('/api/v1/send-message', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY },
body: JSON.stringify({ phone: recipient, message: body })
});
fetch('/api/v1/templates/' + templateId + '/use', {
method: 'POST', headers: { 'x-api-key': API_KEY }
});
}
Contact book
A per-account address book of WhatsApp recipients so you don't re-type numbers each time. Saved contacts can be referenced by id in the /broadcast endpoint via contact_ids.
Plan caps:
Trial = 20 contacts ·
Basic = 100 contacts ·
Pro = 300 contacts.
Phone numbers are stored in canonical form (digits only, no +, 10–15 digits). Duplicates are rejected.
GET /contacts
List your saved contacts, newest first. Optional ?search= matches name, phone, or any tag (case-insensitive). Optional ?limit= caps the response.
Save a single contact. Returns 403 CONTACTS_CAP_REACHED when your plan's contact cap is full. Returns 409 DUPLICATE_PHONE if another contact already uses the number.
Field
Type
Required
Description
phone
string
Required
Country code + number (10–15 digits). Non-digits are stripped — "+91 98765 43210" works.
Import many contacts in one request — perfect for migrating from a CSV or spreadsheet. Up to 1000 rows per call. The endpoint inserts what it can, then returns a per-row outcome explaining why anything got skipped.
Skip reasons:
invalid_phone — number is missing or not 10–15 digits
duplicate_existing — that phone is already saved
duplicate_in_batch — appears twice in your request
Remove a single contact. To remove many at once, POST a list of ids to /contacts/delete-many:
// Singlecurl -X DELETE "https://your-domain.com/api/v1/contacts/ct_a1b2c3" \
-H "x-api-key: YOUR_API_KEY"// Many at oncecurl -X POST "https://your-domain.com/api/v1/contacts/delete-many" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"ids":["ct_a1b2c3","ct_d4e5f6"]}'
Outgoing webhooks
Configure a URL to receive delivery events (sent / failed) for every message your account sends.
// GET /webhook — view current config
{ "url": "https://yourapp.com/wa-events", "enabled": true }
// PUT /webhook — update
{ "url": "https://yourapp.com/wa-events", "enabled": true }
// POST /webhook/test — send a test ping// Payload your endpoint receives
{
"event": "message.sent",
"messageId": "msg_abc123",
"to": "919876543210",
"slot": "a",
"slotPhone": "91XXXXXXXXXX",
"timestamp": "2026-05-16T10:30:00Z"
}
Trigger token (no-auth URL)
Each account has a secret trigger token that creates a public URL services like Google Forms, Zapier, Make, or Apps Script can POST to without sending an API key. Useful when the calling service can't add custom headers.
⚠ Treat the trigger token like a password. Anyone with the URL can send messages from your account. Regenerate it from the dashboard if leaked.
// POST to your trigger URL — no x-api-key neededcurl -X POST "https://your-domain.com/wh/in/tt_YOUR_TRIGGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"phone":"919876543210","message":"Hi from form!"}'// Same body as /send-message — phone, message, optional slot
Error responses
All errors return a JSON body with an error string and (when useful) a machine-readable code.
Status
code
Meaning
400
—
Bad request body — see error for details
401
—
Missing or invalid x-api-key
402
NO_SUBSCRIPTION
No active subscription — start a trial or pay
403
PRO_REQUIRED
Used slot: "b" without Pro plan
403
TRIAL_BULK_BLOCKED
Bulk/broadcast not available on trial plan
403
SEND_WINDOW_CLOSED
Outside your configured send window — response includes nextOpenAt
400
BROADCAST_CAP_EXCEEDED
Sent more than 100 recipients in one broadcast — split into multiple calls
400
NO_RECIPIENTS
Broadcast call with no groups, contacts, or contact_ids
403
CONTACTS_CAP_REACHED
Your contact book is full for your plan (Trial 20 / Basic 100 / Pro 300)
409
DUPLICATE_PHONE
Tried to save a contact whose phone already exists
429
DAILY_LIMIT_REACHED
Hit trial-plan daily limit. Response includes limit and used.
429
—
Per-IP rate limit (60 requests/min on /api/*)
500
—
Server error — retry, then contact support if it persists
// Example error response
{
"error": "Daily message limit of 100 reached. Upgrade to a paid plan to remove the limit.",
"code": "DAILY_LIMIT_REACHED",
"limit": 100,
"used": 100
}
Rate limits & throttling
API throttle: 60 requests per minute per IP on /api/* endpoints.
Bulk & broadcast cadence: 25-second fixed base + 1–10 second random jitter between messages (26–35s window, anti-ban), plus a 2-minute rest after every 20 sends. Enforced server-side — you can't override it.
Daily limit: Trial accounts have a configurable daily ceiling (see GET /subscription). Paid plans are uncapped.
Send window: Optional per-user "only send during these hours" rule. If outside the window, you get SEND_WINDOW_CLOSED with nextOpenAt.
Need help?
Found an issue or have a question? Email support@aumfig.com or open the dashboard chat after signing in.