{
  "info": {
    "name": "TwaBot WhatsApp API v1",
    "description": "TwaBot REST API v1 — Send WhatsApp messages, templates, media & OTPs from your CRM or application.\n\n**Plans (all endpoints available on both):**\n- Growth: 150 sends / day (admin-adjustable)\n- Pro: 5,000 sends / day (admin-adjustable)\n- Starter: no API access\n\n**Important — Meta 24-hour customer-service window:**\nFree-form text and media messages (`send-text`, `send-media`, `quick-send?message=`) are only delivered by Meta if the recipient has messaged your business in the last 24 hours. Outside this window Meta silently drops the message. Our API blocks these sends with `error_code: WINDOW_CLOSED` so you know to use a pre-approved template instead. Templates (`send-template`, `quick-send?template=`) work at any time.\n\n**Delivery status:**\nEvery successful send returns `delivery_status: \"queued\"` — Meta acceptance is not the same as actual delivery. Poll `GET /message-status/{message_id}` for the real delivery state (sent → delivered → read / failed).\n\nDocs: https://twabot.com/docs/api/",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-API-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{api_key}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://twabot.com/api/v1",
      "type": "string"
    },
    {
      "key": "api_key",
      "value": "twb_your_api_key_here",
      "type": "string"
    },
    {
      "key": "last_message_id",
      "value": "",
      "type": "string",
      "description": "Auto-populated by every successful send request via a Postman test script. The Message Status request reads this so you don't need to copy/paste the wamid manually."
    }
  ],
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Collection-level test script: after any successful send, auto-save the message_id",
          "// so the 'Message Status' request can pick it up via {{last_message_id}}.",
          "try {",
          "  if (pm.response && pm.response.code === 200) {",
          "    var json = pm.response.json();",
          "    if (json && json.message_id) {",
          "      pm.collectionVariables.set('last_message_id', json.message_id);",
          "      console.log('[TwaBot] Saved last_message_id =', json.message_id);",
          "    }",
          "  }",
          "} catch (e) {",
          "  // Response was not JSON (e.g. 404 HTML) - silently skip.",
          "}"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "Health Check",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/health",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "health"
          ]
        },
        "description": "Validate your API key and check WhatsApp connection status. Returns the plan name and feature flags so you can confirm your account tier.\n\n✅ Growth  ✅ Pro"
      },
      "response": [
        {
          "name": "Success",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"business\": \"Your Business Name\",\n  \"whatsapp_connected\": true,\n  \"api_key_name\": \"My CRM\",\n  \"plan\": \"Growth\",\n  \"daily_limit\": 150,\n  \"daily_used\": 12,\n  \"timestamp\": \"2026-04-14T10:30:00.000Z\"\n}"
        }
      ]
    },
    {
      "name": "List Templates",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/templates",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "templates"
          ],
          "query": [
            {
              "key": "verbose",
              "value": "1",
              "description": "(optional) Set to 0 for the legacy light-weight response without per-variable docs.",
              "disabled": true
            }
          ]
        },
        "description": "Get all Meta-approved WhatsApp templates for your business — with **full per-variable documentation** so your developers know exactly what each `{{n}}` placeholder is for.\n\n**This is the endpoint to hit FIRST when integrating.** It tells you:\n- How many body variables each template has (`variable_count`)\n- An inferred label + example value for each `{{1}}`, `{{2}}` etc. (`placeholders[]`)\n- A rendered body preview with realistic sample values substituted in (`body_preview_with_samples`)\n- A ready-to-POST JSON payload for `/send-template` (`example_send_template_payload`)\n- A ready-to-GET URL for `/quick-send` (`example_quick_send_url`)\n- A ready-to-paste `curl` command (`example_curl`)\n\nThat means you don't need access to Meta Business Manager to figure out what to send — just read this response and copy the example for the template you want.\n\n---\n\n### Discovery flow for a new integration\n1. `GET /templates` → pick the template you want\n2. `POST /templates/preview` with your real values → see exactly what the recipient will get (no message sent, no credit consumed)\n3. When happy, call `POST /send-template` or `GET /quick-send` with the same `body_params`\n\n✅ Growth  ✅ Pro\n\n**Query params:**\n- `verbose=0` — legacy mode, no per-variable docs. Default is `1` (docs included)."
      },
      "response": [
        {
          "name": "Success — with per-variable docs (default)",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/templates",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "templates"
              ]
            }
          },
          "status": "OK",
          "code": 200,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"success\": true,\n  \"count\": 1,\n  \"documentation\": {\n    \"preview_endpoint\": \"POST https://twabot.com/api/v1/templates/preview\",\n    \"preview_hint\": \"Test any template without sending a real message. POST { template_name, body_params } and get the rendered body back.\",\n    \"send_endpoint\": \"POST https://twabot.com/api/v1/send-template\",\n    \"quick_send_endpoint\": \"GET https://twabot.com/api/v1/quick-send\"\n  },\n  \"templates\": [\n    {\n      \"id\": 34,\n      \"name\": \"order_confirmation\",\n      \"display_name\": \"Order Confirmation\",\n      \"content\": \"Hi {{1}}, your order #{{2}} has been confirmed! Total: {{3}} Estimated delivery: {{4}}\",\n      \"category\": \"UTILITY\",\n      \"language\": \"en\",\n      \"header_type\": null,\n      \"header_text\": null,\n      \"footer_text\": \"Thanks for shopping with us.\",\n      \"variables\": [\n        \"1\",\n        \"2\",\n        \"3\",\n        \"4\"\n      ],\n      \"variable_count\": 4,\n      \"placeholders\": [\n        {\n          \"position\": 1,\n          \"placeholder\": \"{{1}}\",\n          \"inferred_label\": \"customer_name\",\n          \"example_value\": \"Rahul\",\n          \"context_before\": \"Hi\",\n          \"context_after\": \", your order\"\n        },\n        {\n          \"position\": 2,\n          \"placeholder\": \"{{2}}\",\n          \"inferred_label\": \"order_id\",\n          \"example_value\": \"ORD-12345\",\n          \"context_before\": \"your order #\",\n          \"context_after\": \"has been confirmed!\"\n        },\n        {\n          \"position\": 3,\n          \"placeholder\": \"{{3}}\",\n          \"inferred_label\": \"amount\",\n          \"example_value\": \"5000\",\n          \"context_before\": \"confirmed! Total:\",\n          \"context_after\": \"Estimated delivery:\"\n        },\n        {\n          \"position\": 4,\n          \"placeholder\": \"{{4}}\",\n          \"inferred_label\": \"delivery_date\",\n          \"example_value\": \"20-Apr-2026\",\n          \"context_before\": \"Estimated delivery:\",\n          \"context_after\": \"\"\n        }\n      ],\n      \"sample_body_params\": [\n        \"Rahul\",\n        \"ORD-12345\",\n        \"5000\",\n        \"20-Apr-2026\"\n      ],\n      \"body_preview_with_samples\": \"Hi Rahul, your order #ORD-12345 has been confirmed! Total: 5000 Estimated delivery: 20-Apr-2026\",\n      \"example_send_template_payload\": {\n        \"phone\": \"919560360921\",\n        \"template_name\": \"order_confirmation\",\n        \"language\": \"en\",\n        \"body_params\": [\n          \"Rahul\",\n          \"ORD-12345\",\n          \"5000\",\n          \"20-Apr-2026\"\n        ],\n        \"header_params\": []\n      },\n      \"example_quick_send_url\": \"https://twabot.com/api/v1/quick-send?apikey=YOUR_API_KEY&phone=919560360921&template=order_confirmation&lang=en&params=Rahul,ORD-12345,5000,20-Apr-2026\",\n      \"example_curl\": \"curl -X POST \\\"https://twabot.com/api/v1/send-template\\\" \\\\\\n  -H \\\"X-API-Key: YOUR_API_KEY\\\" \\\\\\n  -H \\\"Content-Type: application/json\\\" \\\\\\n  -d '{\\\"phone\\\":\\\"919560360921\\\",\\\"template_name\\\":\\\"order_confirmation\\\",\\\"language\\\":\\\"en\\\",\\\"body_params\\\":[\\\"Rahul\\\",\\\"ORD-12345\\\",\\\"5000\\\",\\\"20-Apr-2026\\\"],\\\"header_params\\\":[]}'\",\n      \"notes\": \"Send exactly 4 string values in body_params, in order. Labels above are inferred from the template text and may not be exact \\u2014 always verify against the template body.\"\n    }\n  ]\n}"
        }
      ]
    },
    {
      "name": "✨ Preview Template (dry-run, no send)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "X-API-Key",
            "value": "{{api_key}}",
            "type": "text"
          },
          {
            "key": "Content-Type",
            "value": "application/json",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{base_url}}/templates/preview",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "templates",
            "preview"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"template_name\": \"order_confirmation\",\n  \"language\": \"en\",\n  \"body_params\": [\n    \"Rahul\",\n    \"ORD-12345\",\n    \"5000\",\n    \"20-Apr-2026\"\n  ]\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "description": "**Dry-run a template** — see exactly what the recipient will receive, without sending a message, without hitting Meta, without consuming any credits.\n\nUse this to:\n- Confirm your `{{1}}`, `{{2}}` substitutions produce a readable message before going live\n- Catch `PARAM_COUNT_MISMATCH` / `PARAM_EMPTY` / `PARAM_FORMAT` errors during integration, not at 2 am on a weekend\n- Demo what messages will look like to a non-technical stakeholder\n\n**Body:**\n- `template_name` (required) — the Meta-approved template name from `GET /templates`\n- `language` (optional, default `en`)\n- `body_params` (optional) — if omitted, the server fills in realistic samples and returns them so you can see the shape\n\n**Response includes:**\n- `rendered_body` — the template text with your params substituted\n- `rendered_header` / `buttons` — same, for header and button placeholders\n- `placeholders[]` — per-variable docs (label, example, surrounding context)\n- `sample_body_params` — what we'd use if you didn't supply `body_params`\n\nNo `phone` needed. No rate-limit impact beyond the global 120/min. Always safe.\n\n✅ Growth  ✅ Pro"
      },
      "response": [
        {
          "name": "Success — body_params supplied",
          "status": "OK",
          "code": 200,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"success\": true,\n  \"template_name\": \"order_confirmation\",\n  \"language\": \"en\",\n  \"category\": \"UTILITY\",\n  \"variable_count\": 4,\n  \"body_params_used\": [\n    \"Rahul\",\n    \"ORD-12345\",\n    \"5000\",\n    \"20-Apr-2026\"\n  ],\n  \"rendered_body\": \"Hi Rahul, your order #ORD-12345 has been confirmed! Total: 5000 Estimated delivery: 20-Apr-2026\",\n  \"rendered_header\": null,\n  \"header_type\": null,\n  \"footer_text\": \"Thanks for shopping with us.\",\n  \"buttons\": [],\n  \"placeholders\": [\n    {\n      \"position\": 1,\n      \"placeholder\": \"{{1}}\",\n      \"inferred_label\": \"customer_name\",\n      \"example_value\": \"Rahul\",\n      \"context_before\": \"Hi\",\n      \"context_after\": \", your order\"\n    },\n    {\n      \"position\": 2,\n      \"placeholder\": \"{{2}}\",\n      \"inferred_label\": \"order_id\",\n      \"example_value\": \"ORD-12345\",\n      \"context_before\": \"your order #\",\n      \"context_after\": \"has been confirmed!\"\n    },\n    {\n      \"position\": 3,\n      \"placeholder\": \"{{3}}\",\n      \"inferred_label\": \"amount\",\n      \"example_value\": \"5000\",\n      \"context_before\": \"confirmed! Total:\",\n      \"context_after\": \"Estimated delivery:\"\n    },\n    {\n      \"position\": 4,\n      \"placeholder\": \"{{4}}\",\n      \"inferred_label\": \"delivery_date\",\n      \"example_value\": \"20-Apr-2026\",\n      \"context_before\": \"Estimated delivery:\",\n      \"context_after\": \"\"\n    }\n  ],\n  \"sample_body_params\": [\n    \"Rahul\",\n    \"ORD-12345\",\n    \"5000\",\n    \"20-Apr-2026\"\n  ],\n  \"validation_warning\": null,\n  \"note\": \"This is a preview only \\u2014 no message was sent, no credit consumed. Use POST /api/v1/send-template to send for real.\"\n}"
        },
        {
          "name": "Success — body_params omitted (server fills samples)",
          "status": "OK",
          "code": 200,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"success\": true,\n  \"template_name\": \"order_confirmation\",\n  \"language\": \"en\",\n  \"variable_count\": 4,\n  \"body_params_used\": [\n    \"Rahul\",\n    \"ORD-12345\",\n    \"5000\",\n    \"20-Apr-2026\"\n  ],\n  \"rendered_body\": \"Hi Rahul, your order #ORD-12345 has been confirmed! Total: 5000 Estimated delivery: 20-Apr-2026\",\n  \"validation_warning\": \"No body_params supplied \\u2014 rendered the template with inferred sample values. Send body_params explicitly when calling /send-template.\",\n  \"note\": \"This is a preview only \\u2014 no message was sent, no credit consumed.\"\n}"
        },
        {
          "name": "400 — PARAM_COUNT_MISMATCH (too few params)",
          "status": "Bad Request",
          "code": 400,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"success\": false,\n  \"error\": \"Template \\\"order_confirmation\\\" expects 4 body parameters but received 2.\",\n  \"error_code\": \"PARAM_COUNT_MISMATCH\",\n  \"expected_param_count\": 4,\n  \"received_param_count\": 2,\n  \"hint\": \"Send body_params as an array of exactly 4 string values, in order ({{1}}, {{2}}, ...).\"\n}"
        },
        {
          "name": "404 — TEMPLATE_NOT_FOUND",
          "status": "Not Found",
          "code": 404,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"success\": false,\n  \"error\": \"Template \\\"wrong_name\\\" not found for this business.\",\n  \"error_code\": \"TEMPLATE_NOT_FOUND\",\n  \"hint\": \"Template name is case-sensitive. GET /api/v1/templates returns all approved names.\"\n}"
        }
      ]
    },
    {
      "name": "⭐ Quick Send — Template (CRM URL)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/quick-send?apikey={{api_key}}&phone=919560360921&template=order_confirmation&lang=en&params=Rahul,ORD-12345,1499,20-Apr-2026",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "quick-send"
          ],
          "query": [
            {
              "key": "apikey",
              "value": "{{api_key}}",
              "description": "Your API key (twb_xxx...)"
            },
            {
              "key": "phone",
              "value": "919560360921",
              "description": "Phone with country code (91 = India)"
            },
            {
              "key": "template",
              "value": "order_confirmation",
              "description": "Meta-approved template name — must exist on your business (run GET /templates to list them)"
            },
            {
              "key": "lang",
              "value": "en",
              "description": "Template language code — must match how the template was approved (e.g. en, en_US, hi)"
            },
            {
              "key": "params",
              "value": "Rahul,ORD-12345,1499,20-Apr-2026",
              "description": "Comma-separated values, ONE PER TEMPLATE VARIABLE. Count must match the template definition exactly — the server validates this before calling Meta and returns 400 PARAM_COUNT_MISMATCH if wrong."
            }
          ]
        },
        "description": "Simple GET URL for CRM integrations (BhashSMS / MSG91 style). Just paste this URL in your CRM webhook field and map your form fields.\n\nThis example uses the `order_confirmation` template which has **4 variables** in its body: `Hi {{1}}, your order #{{2}} has been confirmed! Total: {{3}} Estimated delivery: {{4}}`. The `params=` value therefore has 4 comma-separated values:\n  {{1}} = Rahul\n  {{2}} = ORD-12345\n  {{3}} = 1499\n  {{4}} = 20-Apr-2026\n\n**If you use a different template**, run `GET /templates` first to see how many `{{n}}` variables it has — and make sure your `params=` string has the exact same number of comma-separated values. Mismatched counts are rejected server-side with `400 PARAM_COUNT_MISMATCH` before any Meta call is made (no credit burned).\n\n**Templates work outside the 24-hour window** — use this for order notifications, reminders, OTPs, etc. to customers who haven't messaged you recently.\n\n---\n\n### ⚠️ params formatting rules\n\n- **One value per template variable, in order.** `{{1}}` gets `params[0]`, `{{2}}` gets `params[1]`, and so on.\n- **Use real values, not positional digits.** `params=1,2,3,4` literally renders as `Hi 1, order #2, Total: 3, Delivery: 4`.\n- **URL-bearing variables must be full `https://...` URLs.** A bare value like `5` for a payment-link slot will be accepted but Meta will fail delivery asynchronously.\n- **Comma is the separator** — if a value itself contains a comma, URL-encode it as `%2C` (e.g. `Mumbai%2C India`).\n- **Avoid currency symbols** — `₹` / `$` / `€` need URL encoding in GET URLs. Pass the number without the symbol, or encode it (`%E2%82%B9`).\n- Always poll `GET /message-status/{message_id}` to confirm delivery; the immediate `200 OK` only means Meta queued the message.\n\n✅ Growth (150/day default)  ✅ Pro (5,000/day default)"
      },
      "response": [
        {
          "name": "Success",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw...\",\n  \"phone\": \"919560360921\",\n  \"template\": \"order_confirmation\",\n  \"delivery_status\": \"queued\",\n  \"note\": \"Message accepted by Meta. Actual delivery is asynchronous — poll GET /api/v1/message-status/{message_id} to track delivered/read/failed.\"\n}"
        },
        {
          "name": "Suppression Error",
          "status": "Forbidden",
          "code": 403,
          "body": "{\n  \"success\": false,\n  \"error\": \"Phone 919560360921 is suppressed (opted_out). Message not sent to protect your quality rating.\",\n  \"suppression_state\": \"opted_out\"\n}"
        },
        {
          "name": "Daily Limit Exceeded",
          "status": "Too Many Requests",
          "code": 429,
          "body": "{\n  \"success\": false,\n  \"error\": \"Daily send limit reached (150/day on Growth plan). Resets at midnight. Upgrade to Pro for higher limits.\",\n  \"daily_limit\": 150,\n  \"daily_used\": 150,\n  \"plan\": \"Growth\",\n  \"retry_after\": \"midnight\"\n}"
        }
      ]
    },
    {
      "name": "⭐ Quick Send — Text Message (24h window)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/quick-send?apikey={{api_key}}&phone=919560360921&message=Your order has been shipped",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "quick-send"
          ],
          "query": [
            {
              "key": "apikey",
              "value": "{{api_key}}",
              "description": "Your API key (twb_xxx...)"
            },
            {
              "key": "phone",
              "value": "919560360921",
              "description": "Phone with country code"
            },
            {
              "key": "message",
              "value": "Your order has been shipped",
              "description": "Plain text. Only delivers if the customer messaged you within the last 24 hours."
            }
          ]
        },
        "description": "Send a plain text message via GET URL.\n\n⚠️ **Meta 24-hour rule:** Free-form text only delivers if the recipient has messaged your business in the last 24 hours. If the window is closed we return 400 with `error_code: WINDOW_CLOSED` so you can fall back to a template — we do NOT return a fake success. For shipment notifications, OTPs and similar outbound use cases, use `template=` instead (works anytime).\n\n✅ Growth (150/day default)  ✅ Pro (5,000/day default)"
      },
      "response": [
        {
          "name": "Success (inside 24h window)",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw...\",\n  \"phone\": \"919560360921\",\n  \"delivery_status\": \"queued\",\n  \"note\": \"Message accepted by Meta. Actual delivery is asynchronous — poll GET /api/v1/message-status/{message_id} to track delivered/read/failed.\"\n}"
        },
        {
          "name": "Window Closed (use template)",
          "status": "Bad Request",
          "code": 400,
          "body": "{\n  \"success\": false,\n  \"error\": \"Recipient is outside the 24-hour customer service window. Free-form text messages can only be delivered within 24 hours of the contact messaging your business. Use a pre-approved template for re-engagement.\",\n  \"error_code\": \"WINDOW_CLOSED\",\n  \"session_window\": { \"open\": false, \"last_inbound_at\": null },\n  \"hint\": \"Call GET /api/v1/quick-send?phone=...&template=<approved_template_name>&params=... instead.\"\n}"
        }
      ]
    },
    {
      "name": "Send Template",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/send-template",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "send-template"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phone\": \"919560360921\",\n  \"template_name\": \"upcoming_loan_repayment\",\n  \"language\": \"en\",\n  \"body_params\": [\n    \"Ramesh Kumar\",\n    \"LFC12345\",\n    \"5000\",\n    \"20-Apr-2026\",\n    \"https://example.com/pay/abc123\"\n  ],\n  \"header_params\": []\n}"
        },
        "description": "Send a Meta-approved template message via POST with JSON body. Use this for server-to-server integrations.\n\nRequired: phone, template_name\nOptional: language (default: en), body_params, header_params\n\n**Templates work outside the 24-hour window.**\n\n---\n\n### ⚠️ Parameter rules — read carefully (most common cause of failed deliveries)\n\n1. **`body_params` must be JSON strings, not numbers.**\n   ✅ `[\"Ramesh\", \"5000\", \"20-Apr-2026\"]`\n   ❌ `[1, 2, 3]` — WhatsApp Cloud API requires strings; numeric values are rejected by Meta.\n\n2. **Each value must be the REAL data for that variable position, not its index.**\n   Sending `[1,2,3,4,5]` literally renders as: *\"Dear 1, ... Loan Number 2, ... Rs.3, ... Date: 4, ... Link 5\"* and Meta will fail delivery.\n\n3. **Variable order must match the template definition.**\n   Call `GET /templates` first to see what `{{1}}, {{2}}, {{3}}...` mean for your template. The order in `body_params` is positional — `body_params[0]` fills `{{1}}`, `body_params[1]` fills `{{2}}`, and so on.\n\n4. **URL variables must be full `https://` URLs.**\n   For variables that hold a payment link, tracking link, etc., always pass a complete URL: `\"https://example.com/pay/abc123\"`. A bare value like `\"5\"` for a URL slot will queue successfully (200 OK) but Meta will fail delivery silently — the message will show a red X in the dashboard.\n\n5. **Empty / null / undefined values are not allowed.**\n   Every `{{n}}` in the template must have a non-empty string in `body_params`. Use `\"-\"` or `\"N/A\"` for placeholder values, not empty strings.\n\n### How to debug a failed send\n\nThe `200 OK` from this endpoint only means Meta accepted the message into its queue — **not** that it was delivered. Always call `GET /message-status/{message_id}` afterward to see the real outcome and Meta's exact error code.\n\nCommon Meta error codes:\n- `131009 parameter_invalid` — wrong type / format for a parameter\n- `132001 template_param_count_mismatch` — wrong number of body_params\n- `132012 parameter_format_mismatch` — value doesn't match expected format (e.g. non-URL in URL slot)\n\n✅ Growth (150/day default)  ✅ Pro (5,000/day default)"
      },
      "response": [
        {
          "name": "Success",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw...\",\n  \"phone\": \"919560360921\",\n  \"template\": \"upcoming_loan_repayment\",\n  \"delivery_status\": \"queued\",\n  \"note\": \"Message accepted by Meta. Actual delivery is asynchronous — poll GET /api/v1/message-status/{message_id} to track delivered/read/failed.\"\n}"
        },
        {
          "name": "Template Not Found",
          "status": "Bad Request",
          "code": 400,
          "body": "{\n  \"success\": false,\n  \"error\": \"Template 'wrong_name' not found or not approved.\"\n}"
        },
        {
          "name": "Failed Delivery — Bad Parameters (visible only in /message-status)",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw...\",\n  \"api_status\": \"success\",\n  \"delivery_status\": \"failed\",\n  \"sent_at\": \"2026-04-16T11:32:00.000Z\",\n  \"to\": \"919871073899\",\n  \"failure_reason\": \"Meta error 131009: Parameter value is not valid. Check that body_params are strings, in correct order, and that URL variables contain full https:// URLs.\"\n}"
        }
      ]
    },
    {
      "name": "Send Text",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/send-text",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "send-text"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phone\": \"919560360921\",\n  \"message\": \"Hi Rahul, your order has been shipped! Track here: https://example.com/track/ORD-12345\"\n}"
        },
        "description": "Send a free-form text message.\n\n⚠️ **Meta 24-hour rule:** Only delivers if the recipient has messaged your business in the last 24 hours. Outside the window we return `error_code: WINDOW_CLOSED` — use a template instead.\n\nRequired: phone, message (max 4096 chars)\n\n✅ Growth (150/day default)  ✅ Pro (5,000/day default)"
      },
      "response": [
        {
          "name": "Success (inside 24h window)",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw...\",\n  \"phone\": \"919560360921\",\n  \"delivery_status\": \"queued\",\n  \"note\": \"Message accepted by Meta. Actual delivery is asynchronous — poll GET /api/v1/message-status/{message_id} to track delivered/read/failed.\"\n}"
        },
        {
          "name": "Window Closed",
          "status": "Bad Request",
          "code": 400,
          "body": "{\n  \"success\": false,\n  \"error\": \"Recipient is outside the 24-hour customer service window. Free-form text messages can only be delivered within 24 hours of the contact messaging your business. Use a pre-approved template (send-template or quick-send with template=) for re-engagement.\",\n  \"error_code\": \"WINDOW_CLOSED\",\n  \"session_window\": { \"open\": false, \"last_inbound_at\": null },\n  \"hint\": \"Call POST /api/v1/send-template or GET /api/v1/quick-send?phone=...&template=... instead.\"\n}"
        }
      ]
    },
    {
      "name": "Send Media",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/send-media",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "send-media"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phone\": \"919560360921\",\n  \"media_type\": \"image\",\n  \"media_url\": \"https://example.com/invoice.png\",\n  \"caption\": \"Your invoice for order ORD-12345\"\n}"
        },
        "description": "Send an image, document, video, or audio file.\n\n⚠️ **Meta 24-hour rule:** Free-form media follows the same rule as text — only delivers inside the 24-hour window. Outside it, use a media-header template via send-template.\n\nRequired: phone, media_type (image/document/video/audio), media_url\nOptional: caption, filename\n\n✅ Growth (150/day default)  ✅ Pro (5,000/day default)"
      },
      "response": [
        {
          "name": "Success (inside 24h window)",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw...\",\n  \"phone\": \"919560360921\",\n  \"media_type\": \"image\",\n  \"delivery_status\": \"queued\",\n  \"note\": \"Message accepted by Meta. Actual delivery is asynchronous — poll GET /api/v1/message-status/{message_id} to track delivered/read/failed.\"\n}"
        },
        {
          "name": "Window Closed",
          "status": "Bad Request",
          "code": 400,
          "body": "{\n  \"success\": false,\n  \"error\": \"Recipient is outside the 24-hour customer service window. Free-form media messages can only be delivered within 24 hours of the contact messaging your business.\",\n  \"error_code\": \"WINDOW_CLOSED\",\n  \"session_window\": { \"open\": false, \"last_inbound_at\": null },\n  \"hint\": \"Use a media-header template via POST /api/v1/send-template instead.\"\n}"
        }
      ]
    },
    {
      "name": "Send Media — File Upload (multipart/form-data)",
      "request": {
        "method": "POST",
        "header": [],
        "url": {
          "raw": "{{base_url}}/send-media",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "send-media"
          ]
        },
        "body": {
          "mode": "formdata",
          "formdata": [
            {
              "key": "phone",
              "value": "919560360921",
              "type": "text",
              "description": "Phone with country code"
            },
            {
              "key": "media_type",
              "value": "image",
              "type": "text",
              "description": "image, document, video, or audio"
            },
            {
              "key": "caption",
              "value": "Your invoice for order ORD-12345",
              "type": "text",
              "description": "Optional caption"
            },
            {
              "key": "file",
              "value": "",
              "type": "file",
              "description": "The file to upload (image, PDF, mp4, mp3 etc.)"
            }
          ]
        },
        "description": "Upload a file directly and send it as a WhatsApp media message — no public URL required.\n\n**The /send-media endpoint accepts EITHER mode:**\n\n**Option A — public URL (JSON):** see the `Send Media` request right above. Use when your file is already hosted at a public HTTPS URL.\n\n**Option B — direct file upload (this request):**\n- `Content-Type: multipart/form-data` (Postman sets this automatically when you pick file mode)\n- Required fields: `phone`, `file` (the binary)\n- Optional fields: `media_type` (auto-detected from MIME if omitted), `caption`, `filename`\n- Server uploads the file to Meta's `/media` endpoint, gets back a `media_id`, then sends the message by media_id.\n\n### Limits enforced server-side (Meta caps shown):\n- image: ≤ 5 MB (jpeg, png)\n- document: ≤ 16 MB (Meta allows up to 100 MB, server caps at 16)\n- video: ≤ 16 MB (mp4, 3gp)\n- audio: ≤ 16 MB (mp3, mp4, amr, ogg, opus)\n\nFiles over the cap return `413 FILE_TOO_LARGE`.\n\n### Same 24-hour window rule applies\nMedia is a free-form message type. It only delivers if the recipient messaged your business in the last 24 hours, OR if you're sending it as the header of an approved media-header template via `/send-template`.\n\n✅ Growth (150/day)  ✅ Pro (5,000/day)"
      },
      "response": [
        {
          "name": "Success",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw...\",\n  \"phone\": \"919560360921\",\n  \"media_type\": \"image\",\n  \"media_source\": \"upload\",\n  \"media_id\": \"1234567890123456\",\n  \"delivery_status\": \"queued\",\n  \"note\": \"Message accepted by Meta. Actual delivery is asynchronous — poll GET /api/v1/message-status/{message_id} to track delivered/read/failed.\"\n}"
        },
        {
          "name": "File too large",
          "status": "Payload Too Large",
          "code": 413,
          "body": "{\n  \"success\": false,\n  \"error\": \"File size 7.20 MB exceeds the 5 MB limit for image.\",\n  \"error_code\": \"FILE_TOO_LARGE\"\n}"
        }
      ]
    },
    {
      "name": "Send OTP",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/send-otp",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "send-otp"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phone\": \"919560360921\",\n  \"template_name\": \"otp_verification\",\n  \"language\": \"en\"\n}"
        },
        "description": "Send a one-time password via an approved OTP template. TwaBot auto-generates a 6-digit OTP if you don't provide one. Since it uses a template, it **works outside the 24-hour window**.\n\nRequired: phone, template_name\nOptional: otp (custom code), language (default: en)\n\nResponse includes the OTP code so your app can verify it later.\n\n---\n\n### ⚠️ `template_name` must exist in YOUR Meta-approved template list\n\nThe value `otp_verification` in the example below is a **placeholder** — your account almost certainly does not have a template with this exact name. If you send it as-is you will get:\n\n```\n400 Bad Request\n{ \"success\": false, \"error\": \"(#132001) Template name does not exist in the translation\" }\n```\n\n**Before running this request:**\n1. Run the `List Templates` request first.\n2. Find an approved OTP-style template in the response (it will be in the `AUTHENTICATION` category, or contain `{{1}}` for the OTP code).\n3. Replace `\"otp_verification\"` in the body below with the actual `name` of that template.\n\nIf you don't have any OTP template approved yet, you need to create and submit one in Meta Business Manager → WhatsApp Manager → Message Templates first.\n\n✅ Growth (150/day default)  ✅ Pro (5,000/day default)"
      },
      "response": [
        {
          "name": "Success",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw...\",\n  \"phone\": \"919560360921\",\n  \"otp\": \"482916\",\n  \"delivery_status\": \"queued\",\n  \"note\": \"Message accepted by Meta. Actual delivery is asynchronous — poll GET /api/v1/message-status/{message_id} to track delivered/read/failed.\"\n}"
        }
      ]
    },
    {
      "name": "Message Status",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/message-status/{{last_message_id}}",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "message-status",
            "{{last_message_id}}"
          ]
        },
        "description": "Check the real delivery status of a sent message.\n\n**This URL uses `{{last_message_id}}`, which is auto-populated by a collection-level test script every time you run a successful send request (Quick Send, Send Template, Send Text, Send Media, or Send OTP). Just run a send first, then run this — no copy/paste needed.**\n\nIf you want to check a specific message ID, replace `{{last_message_id}}` with the wamid manually.\n\nReturns two fields:\n- `api_status`: our side — success / failed (whether we accepted and forwarded the send)\n- `delivery_status`: Meta side — sent / delivered / read / failed (updated from Meta webhooks)\n\nIf `delivery_status` is `failed`, `failure_reason` explains why (this includes Meta error codes like `131009`, `132000`, `132001`, `132012` when applicable).\n\n**Always poll this endpoint after a send** — `success: true` in the send response only means Meta accepted the API call, not that the recipient received the message.\n\n✅ Growth  ✅ Pro"
      },
      "response": [
        {
          "name": "Delivered",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw\",\n  \"api_status\": \"success\",\n  \"delivery_status\": \"delivered\",\n  \"sent_at\": \"2026-04-14T10:30:00.000Z\",\n  \"to\": \"919560360921\"\n}"
        },
        {
          "name": "Read",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw\",\n  \"api_status\": \"success\",\n  \"delivery_status\": \"read\",\n  \"sent_at\": \"2026-04-14T10:30:00.000Z\",\n  \"to\": \"919560360921\"\n}"
        },
        {
          "name": "Failed",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"success\": true,\n  \"message_id\": \"wamid.HBgMOTE5ODc2NTQzMjEw\",\n  \"api_status\": \"success\",\n  \"delivery_status\": \"failed\",\n  \"sent_at\": \"2026-04-14T10:30:00.000Z\",\n  \"to\": \"919560360921\",\n  \"failure_reason\": \"Message undeliverable — recipient's phone is not on WhatsApp.\"\n}"
        },
        {
          "name": "Not Found",
          "status": "Not Found",
          "code": 404,
          "body": "{\n  \"success\": false,\n  \"error\": \"Message not found.\",\n  \"hint\": \"Status is available within a few seconds of sending. Either {{last_message_id}} is empty (run a send request first) or the message_id is wrong / from a different account.\"\n}"
        }
      ]
    }
  ]
}