API Reference
Programmatically manage contacts, send messages, bulk-broadcast templates, run campaigns, and retrieve analytics through the MotherBot REST API.
Base URL & Authentication
All external API routes are under the/api/v1 prefix:Base URL: https://motherbot.io/api/v1Generate an API key under Settings → API Keys. Pass it as a Bearer token on every request:curl https://motherbot.io/api/v1/contacts \
-H "Authorization: Bearer motherbot_xxxxxxxxxxxx"Warning
Never embed API keys in client-side code or public repositories. Rotate them immediately under Settings → API Keys if compromised.
Rate Limits
Limits are enforced per API key per minute based on your plan:| Plan | Requests / minute |
|---|---|
| Starter | 60 |
| Growth | 300 |
| Enterprise | 1,200 |
POST /messages/bulk) and the broadcast campaign endpoint (POST /campaigns/send) each use a separate bucket set to 1/10th of your plan limit per call. When exceeded the API returns 429 Too Many Requests. Rate limit headers are included in every response:X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Reset: 1719220860Response Format
All responses are JSON. Success:{ "success": true, "data": { ... } }List responses include a pagination object:{
"success": true,
"data": [...],
"pagination": { "page": 1, "limit": 20, "total": 1240, "pages": 62 }
}Error:{
"success": false,
"error": "error_code",
"message": "Human-readable description",
"details": {}
}HTTP Status Codes
| Code | error value | Meaning |
|---|---|---|
| 200 | — | OK — request succeeded |
| 201 | — | Created — resource created |
| 207 | — | Multi-Status — bulk send; check each result's status field |
| 400 | invalid_request | Missing or malformed parameter |
| 400 | invalid_template | Template is not APPROVED or not found |
| 400 | invalid_state | Resource is in the wrong state for this action |
| 400 | no_whatsapp_account | No connected WhatsApp account found |
| 401 | unauthorized | Missing or invalid API key |
| 404 | not_found | Resource doesn't exist or belongs to another org |
| 429 | rate_limit_exceeded | Rate limit exceeded — retry after 60 seconds |
| 502 | upstream_error | WhatsApp / Meta API returned an error |
Contacts
# List contacts — paginated, newest first
GET /api/v1/contacts
?page=1 # default 1
&limit=20 # default 20, max 100
&tag=vip # filter by a single tag (exact match)
&search=john # partial match on name, phone, or email
# Response 200
{
"success": true,
"data": [
{
"_id": "664abc...",
"phoneNumber": "919876543210",
"waId": "919876543210",
"name": "John Smith",
"email": "john@example.com",
"tags": ["vip", "mumbai"],
"optedIn": true,
"blocked": false,
"customFields": { "plan": "premium" },
"source": "api",
"createdAt": "2026-06-15T09:30:00.000Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 1240, "pages": 62 }
}# Create or upsert a contact (matched by phoneNumber)
POST /api/v1/contacts
Content-Type: application/json
{
"phoneNumber": "919876543210", // required — country code, no + prefix
"name": "John Smith",
"firstName": "John",
"lastName": "Smith",
"email": "john@example.com",
"tags": ["vip", "enterprise"], // merged with existing tags on upsert
"customFields": { "plan": "premium", "referral": "REF42" },
"source": "api" // default: "api"
}
# Response 201
{
"success": true,
"data": { "_id": "664abc...", "phoneNumber": "919876543210", ... }
}# Get a single contact
GET /api/v1/contacts/:id
# Response 200
{ "success": true, "data": { "_id": "664abc...", "name": "John Smith", ... } }# Update a contact — only fields you send are changed
PATCH /api/v1/contacts/:id
Content-Type: application/json
{
"name": "Jane Smith",
"email": "jane@new.com",
"tags": ["vip", "renewed"], // replaces full tag list
"optedIn": true,
"blocked": false,
"notes": "Renewed June 2026",
"country": "IN",
"city": "Mumbai",
"language": "en",
"customFields": { "plan": "enterprise" }
}
# Response 200
{ "success": true, "data": { "_id": "664abc...", ... } }# Delete a contact (irreversible)
DELETE /api/v1/contacts/:id
# Response 200
{ "success": true, "message": "Contact deleted" }Messages — Single Send
Sends a single WhatsApp message to one contact. The recipient must already exist in your contacts list.# Send a plain text message
POST /api/v1/messages
Content-Type: application/json
{
"to": "919876543210", // required — must match an existing contact
"type": "text", // "text" | "template" (default: "text")
"text": {
"body": "Hello! Your order has shipped." // max 4096 chars
}
}
# Response 201
{
"success": true,
"data": {
"messageId": "wamid.HBgNOTE5...",
"to": "919876543210",
"status": "sent"
}
}# Send an approved template message with body variables
POST /api/v1/messages
Content-Type: application/json
{
"to": "919876543210",
"type": "template",
"template": {
"name": "order_confirmation",
"language": { "code": "en_US" },
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "John" }, // {{1}}
{ "type": "text", "text": "ORDER-9912" } // {{2}}
]
}
]
}
}
# Response 201
{
"success": true,
"data": { "messageId": "wamid.xxx", "to": "919876543210", "status": "sent" }
}Note
Only APPROVED templates can be sent. Use the Templates API below to find which ones are ready.
Messages — Bulk Send
Sends a WhatsApp template message to up to 10,000 recipients in a single request. Each recipient can carry its own body variable values. Contacts not found in your list are returned asskipped without aborting the rest. Returns HTTP 207 Multi-Status with a per-recipient result array.| Field | Type | Required | Description |
|---|---|---|---|
| templateName | string | Yes | Template name exactly as stored (must be APPROVED) |
| languageCode | string | Yes | Template language code e.g. en_US, hi, ar |
| recipients | array | Yes | 1–10,000 recipient objects |
| recipients[].to | string | Yes | Phone number matching an existing contact |
| recipients[].variables | object | No | Body variable values keyed by position: { "1": "...", "2": "..." } |
| headerVariables | string[] | No | Shared header variable values (same for all recipients) |
| buttonVariables | string[] | No | Shared button URL suffix values (same for all recipients) |
POST /api/v1/messages/bulk
Content-Type: application/json
Authorization: Bearer motherbot_xxx
{
"templateName": "order_confirmation",
"languageCode": "en_US",
"headerVariables": ["https://cdn.mystore.com/banner.jpg"],
"buttonVariables": ["https://track.mystore.com/orders/"],
"recipients": [
{
"to": "919876543210",
"variables": { "1": "John", "2": "ORDER-9912", "3": "₹599" }
},
{
"to": "918765432109",
"variables": { "1": "Priya", "2": "ORDER-9913", "3": "₹299" }
}
]
}
# Response 207
{
"success": true,
"summary": { "total": 3, "sent": 2, "failed": 0, "skipped": 1 },
"results": [
{ "to": "919876543210", "status": "sent", "messageId": "wamid.AAA..." },
{ "to": "918765432109", "status": "sent", "messageId": "wamid.BBB..." },
{ "to": "910000000000", "status": "skipped", "error": "Contact not found in your contacts list" }
]
}| Result status | Meaning |
|---|---|
| sent | Message accepted by the WhatsApp API |
| failed | WhatsApp / Meta API rejected this specific message |
| skipped | Phone number has no matching contact in your org |
Tip
The bulk endpoint has its own rate-limit bucket at 1/10th of your plan limit per call (e.g. 30 bulk calls/min on Growth). Use the Templates API to fetch the exact template name and language code before sending.
Templates
List your WhatsApp message templates and filter by approval status, category, or language. Use this to discover which templates are ready to send.| Query param | Allowed values | Default | Description |
|---|---|---|---|
| status | APPROVED, PENDING, REJECTED, DISABLED, DRAFT | — | Filter by Meta approval status |
| category | AUTHENTICATION, MARKETING, UTILITY | — | Filter by template category |
| language | e.g. en_US, hi, ar | — | Filter by language code |
| search | any string | — | Partial match on template name |
| page | ≥ 1 | 1 | Page number |
| limit | 1–100 | 20 | Results per page |
# List approved marketing templates
GET /api/v1/templates?status=APPROVED&category=MARKETING&limit=50
Authorization: Bearer motherbot_xxx
# Response 200
{
"success": true,
"data": [
{
"_id": "665b2a...",
"metaTemplateId": "1234567890",
"name": "order_confirmation",
"category": "MARKETING",
"language": "en_US",
"status": "APPROVED",
"variables": ["customer_name", "order_id", "amount"],
"usageCount": 4821,
"components": [
{ "type": "HEADER", "format": "IMAGE" },
{ "type": "BODY", "text": "Hi {{1}}, your order {{2}} of {{3}} is confirmed!" },
{ "type": "FOOTER", "text": "Reply STOP to unsubscribe" },
{
"type": "BUTTONS",
"buttons": [{ "type": "URL", "text": "Track Order", "url": "https://mystore.com/track/{{1}}" }]
}
],
"createdAt": "2026-04-10T08:00:00.000Z"
}
],
"pagination": { "page": 1, "limit": 50, "total": 12, "pages": 1 }
}Tip
Use
status=APPROVED to fetch only templates that can be used in single or bulk sends. The name and language fields are what you pass to POST /messages/bulk.Campaigns
# List campaigns — paginated, newest first
GET /api/v1/campaigns
?status=scheduled # draft | scheduled | running | paused | completed | failed
&page=1
&limit=20
# Response 200
{
"success": true,
"data": [
{
"_id": "665c3b...",
"name": "June Summer Sale",
"status": "completed",
"audienceType": "all",
"audienceCount": 9800,
"template": { "_id": "665b2a...", "name": "order_confirmation", "category": "MARKETING" },
"stats": {
"total": 9800, "sent": 9750, "delivered": 9600,
"read": 7200, "failed": 50, "replied": 320
},
"startedAt": "2026-06-01T09:00:01.000Z",
"completedAt": "2026-06-01T09:14:22.000Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 5, "pages": 1 }
}# Create a campaign (draft or scheduled)
POST /api/v1/campaigns
Content-Type: application/json
{
"name": "Diwali Offer 2026", // required
"templateId": "665b2a...", // required — must be APPROVED
"audienceType": "tags", // "all" (default) | "tags"
"audienceTags": ["opted-in", "delhi"], // required when audienceType = "tags"
"description": "Festival season promo",
"scheduledAt": "2026-10-20T09:00:00Z" // omit to save as draft
}
# audienceCount is auto-calculated (optedIn: true, blocked: false contacts only)
# Response 201
{
"success": true,
"data": {
"_id": "665d4c...",
"name": "Diwali Offer 2026",
"status": "scheduled",
"audienceCount": 3200,
"scheduledAt": "2026-10-20T09:00:00.000Z"
}
}# Get live delivery stats for a campaign
GET /api/v1/campaigns/:id/stats
# Response 200
{
"success": true,
"data": {
"_id": "665c3b...",
"name": "June Summer Sale",
"status": "completed",
"audienceCount": 9800,
"stats": {
"total": 9800, "queued": 0, "sent": 9750,
"delivered": 9600, "read": 7200,
"failed": 50, "replied": 320, "clicked": 480, "optedOut": 12
},
"startedAt": "2026-06-01T09:00:01.000Z",
"completedAt": "2026-06-01T09:14:22.000Z"
}
}# Pause a running or scheduled campaign
POST /api/v1/campaigns/:id/pause
# Response: { "success": true, "data": { "id": "665c3b...", "status": "paused" } }
# Resume a paused campaign
POST /api/v1/campaigns/:id/resume
# Response: { "success": true, "data": { "id": "665c3b...", "status": "running" } }Campaigns — Broadcast Send
POST /api/v1/campaigns/send creates and immediately launches a broadcast campaign from an explicit recipient list you provide in the request body. Recipients do not need to be pre-registered in your contacts list.Tip
Use commonVariables for variable values that are the same for every recipient (e.g. your brand name, event date, promo code). Only include per-recipient overrides in
recipients[].variables — this keeps the payload compact when most variables are shared.| Field | Type | Required | Description |
|---|---|---|---|
| name | string | No | Campaign name — auto-generated from template name and date if omitted |
| templateId | string | One of | Template MongoDB ID (preferred — unambiguous) |
| templateName | string | One of | Template name — requires languageCode too |
| languageCode | string | Cond. | Required when using templateName (e.g. en_US, hi) |
| commonVariables | object | No | Body variable values shared by all recipients — { "1": "...", "2": "..." } |
| headerMediaUrl | string | No | Public URL for IMAGE / VIDEO / DOCUMENT header (shared by all recipients) |
| rateLimitPerSecond | number | No | Messages per second — default 3, max 80 |
| recipients | array | Yes | 1 – 100,000 recipient objects |
| recipients[].to | string | Yes | Phone number with country code, no + prefix (e.g. 919876543210) |
| recipients[].name | string | No | Recipient name stored in the campaign message log for reporting |
| recipients[].variables | object | No | Per-recipient variable overrides — merged over commonVariables |
# Send a template to 3 recipients — {{1}} and {{3}} are shared; {{2}} is per-recipient
POST /api/v1/campaigns/send
Content-Type: application/json
Authorization: Bearer motherbot_xxx
{
"name": "June Order Dispatch",
"templateName": "order_confirmation",
"languageCode": "en_US",
"commonVariables": {
"1": "MotherBot Store",
"3": "support@mystore.com"
},
"headerMediaUrl": "https://cdn.mystore.com/banner.jpg",
"rateLimitPerSecond": 5,
"recipients": [
{
"to": "919876543210",
"name": "John Smith",
"variables": { "2": "ORDER-9912" }
},
{
"to": "918765432109",
"name": "Priya Sharma",
"variables": { "2": "ORDER-9913" }
},
{
"to": "917654321098",
"name": "Ravi Kumar"
// no variables → all three come from commonVariables
}
]
}
# Response 201
{
"success": true,
"data": {
"campaignId": "665d4c...",
"status": "running",
"total": 3,
"rateLimitPerSecond": 5,
"etaSeconds": 1,
"etaText": "~1 second"
}
}| Response field | Description |
|---|---|
| campaignId | Use this with GET /api/v1/campaigns/:id/stats to poll delivery progress |
| status | Always running — the campaign starts processing immediately |
| total | Number of unique recipients after deduplication |
| rateLimitPerSecond | Effective rate that was applied |
| etaSeconds | Estimated seconds to finish sending all messages |
| etaText | Human-readable ETA (e.g. ~3 minutes) |
Note
Variable merge order:
{ ...commonVariables, ...recipient.variables }. A key present in both takes the recipient-level value. Duplicate phone numbers are silently deduplicated.Warning
This endpoint uses the broadcast rate-limit bucket (1/10th of your plan limit per call). For very large lists (100,000+), split into multiple calls of 50,000 each.
Analytics
# Get analytics summary
GET /api/v1/analytics/summary
?days=30 # lookback window in days (default: 30, max: 90)
# Response 200
{
"success": true,
"data": {
"period": {
"days": 30,
"from": "2026-05-25T00:00:00.000Z",
"to": "2026-06-24T00:00:00.000Z"
},
"messages": {
"total": 14200,
"sent": 9800,
"received": 4400,
"delivered": 9500,
"read": 6700,
"failed": 300,
"deliveryRate": 96,
"readRate": 70
},
"contacts": { "total": 3240 },
"campaigns": {
"total": 12,
"draft": 2,
"scheduled": 1,
"running": 0,
"paused": 0,
"completed": 8,
"failed": 1
}
}
}Endpoint Quick Reference
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/analytics/summary | Messaging & campaign analytics |
| GET | /api/v1/contacts | List contacts |
| POST | /api/v1/contacts | Create / upsert contact |
| GET | /api/v1/contacts/:id | Get contact by ID |
| PATCH | /api/v1/contacts/:id | Update contact fields |
| DELETE | /api/v1/contacts/:id | Delete contact |
| POST | /api/v1/messages | Send single message (text or template) |
| POST | /api/v1/messages/bulk | Bulk send template to up to 10,000 numbers |
| GET | /api/v1/templates | List templates with filters |
| GET | /api/v1/campaigns | List campaigns |
| POST | /api/v1/campaigns | Create campaign (draft or scheduled) |
| POST | /api/v1/campaigns/send | Create & instantly launch broadcast campaign with inline recipients |
| GET | /api/v1/campaigns/:id/stats | Get campaign delivery stats |
| POST | /api/v1/campaigns/:id/pause | Pause a running / scheduled campaign |
| POST | /api/v1/campaigns/:id/resume | Resume a paused campaign |
Generating API Keys
API keys are created from the dashboard under Settings → API Keys. The raw key is shown once at creation — copy it immediately.- Keys are prefixed with motherbot_ for easy identification.
- Each key is scoped to your organization only.
- Each key inherits the rate limit of your plan.
- Revoke a key from Settings → API Keys → Revoke. It is invalidated immediately.
Warning
The raw key is never stored — only a SHA-256 hash is kept. There is no way to retrieve the key after the creation screen is closed.