Integrations

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/v1
Generate 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:
PlanRequests / minute
Starter60
Growth300
Enterprise1,200
The bulk send endpoint (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: 1719220860

Response 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

Codeerror valueMeaning
200OK — request succeeded
201Created — resource created
207Multi-Status — bulk send; check each result's status field
400invalid_requestMissing or malformed parameter
400invalid_templateTemplate is not APPROVED or not found
400invalid_stateResource is in the wrong state for this action
400no_whatsapp_accountNo connected WhatsApp account found
401unauthorizedMissing or invalid API key
404not_foundResource doesn't exist or belongs to another org
429rate_limit_exceededRate limit exceeded — retry after 60 seconds
502upstream_errorWhatsApp / 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 as skipped without aborting the rest. Returns HTTP 207 Multi-Status with a per-recipient result array.
FieldTypeRequiredDescription
templateNamestringYesTemplate name exactly as stored (must be APPROVED)
languageCodestringYesTemplate language code e.g. en_US, hi, ar
recipientsarrayYes1–10,000 recipient objects
recipients[].tostringYesPhone number matching an existing contact
recipients[].variablesobjectNoBody variable values keyed by position: { "1": "...", "2": "..." }
headerVariablesstring[]NoShared header variable values (same for all recipients)
buttonVariablesstring[]NoShared 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 statusMeaning
sentMessage accepted by the WhatsApp API
failedWhatsApp / Meta API rejected this specific message
skippedPhone 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 paramAllowed valuesDefaultDescription
statusAPPROVED, PENDING, REJECTED, DISABLED, DRAFTFilter by Meta approval status
categoryAUTHENTICATION, MARKETING, UTILITYFilter by template category
languagee.g. en_US, hi, arFilter by language code
searchany stringPartial match on template name
page≥ 11Page number
limit1–10020Results 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.
FieldTypeRequiredDescription
namestringNoCampaign name — auto-generated from template name and date if omitted
templateIdstringOne ofTemplate MongoDB ID (preferred — unambiguous)
templateNamestringOne ofTemplate name — requires languageCode too
languageCodestringCond.Required when using templateName (e.g. en_US, hi)
commonVariablesobjectNoBody variable values shared by all recipients — { "1": "...", "2": "..." }
headerMediaUrlstringNoPublic URL for IMAGE / VIDEO / DOCUMENT header (shared by all recipients)
rateLimitPerSecondnumberNoMessages per second — default 3, max 80
recipientsarrayYes1 – 100,000 recipient objects
recipients[].tostringYesPhone number with country code, no + prefix (e.g. 919876543210)
recipients[].namestringNoRecipient name stored in the campaign message log for reporting
recipients[].variablesobjectNoPer-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 fieldDescription
campaignIdUse this with GET /api/v1/campaigns/:id/stats to poll delivery progress
statusAlways running — the campaign starts processing immediately
totalNumber of unique recipients after deduplication
rateLimitPerSecondEffective rate that was applied
etaSecondsEstimated seconds to finish sending all messages
etaTextHuman-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

MethodEndpointDescription
GET/api/v1/analytics/summaryMessaging & campaign analytics
GET/api/v1/contactsList contacts
POST/api/v1/contactsCreate / upsert contact
GET/api/v1/contacts/:idGet contact by ID
PATCH/api/v1/contacts/:idUpdate contact fields
DELETE/api/v1/contacts/:idDelete contact
POST/api/v1/messagesSend single message (text or template)
POST/api/v1/messages/bulkBulk send template to up to 10,000 numbers
GET/api/v1/templatesList templates with filters
GET/api/v1/campaignsList campaigns
POST/api/v1/campaignsCreate campaign (draft or scheduled)
POST/api/v1/campaigns/sendCreate & instantly launch broadcast campaign with inline recipients
GET/api/v1/campaigns/:id/statsGet campaign delivery stats
POST/api/v1/campaigns/:id/pausePause a running / scheduled campaign
POST/api/v1/campaigns/:id/resumeResume 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.