{
  "openapi": "3.1.0",
  "info": {
    "title": "WasapFlow Bridge API",
    "version": "2.9.2",
    "summary": "Official WhatsApp Business API (Meta Cloud API) for your own SaaS or CRM — without becoming a Meta Tech Provider.",
    "description": "Official Postman collection for WasapFlow Bridge API 2.9.2 — the official WhatsApp Business API (Meta Cloud API) for your own SaaS or CRM, without becoming a Meta Tech Provider.\n\n**Setup:** set the collection variables `partnerKey` (from the partner portal → Integration) and `wabaId` (a WABA registered to your account). `baseUrl` is already set.\n\n**Pricing:** $10 USD per month for every 3 WABAs. Free partner account — https://partner.wasapflow.com/bridge/join\n\n**Full docs (also for AI tools):** https://raw.githubusercontent.com/kobaranteguh/api/main/README.md\n\nGenerated from the API reference — do not edit by hand.",
    "contact": {
      "name": "WasapFlow Bridge",
      "url": "https://partner.wasapflow.com/bridge/docs"
    }
  },
  "externalDocs": {
    "description": "Full reference (Markdown, best for AI tools)",
    "url": "https://raw.githubusercontent.com/kobaranteguh/api/main/README.md"
  },
  "servers": [
    {
      "url": "https://officialapi.wasapflow.com/bridge/v1"
    }
  ],
  "security": [
    {
      "PartnerKey": []
    }
  ],
  "tags": [
    {
      "name": "Client (WABA) Management"
    },
    {
      "name": "Messages"
    },
    {
      "name": "Templates"
    },
    {
      "name": "Business Profile"
    },
    {
      "name": "Contacts"
    },
    {
      "name": "Media"
    },
    {
      "name": "Analytics"
    },
    {
      "name": "Broadcasts"
    },
    {
      "name": "QR Codes & Short Links"
    },
    {
      "name": "Conversational Automation"
    },
    {
      "name": "Blocked Users"
    },
    {
      "name": "Commerce Settings"
    },
    {
      "name": "Settings (calling & storage)"
    },
    {
      "name": "Phone Number Status"
    },
    {
      "name": "WhatsApp Business Account"
    },
    {
      "name": "Schedules"
    },
    {
      "name": "Flows (read-only)"
    },
    {
      "name": "Phone Number Lifecycle"
    },
    {
      "name": "Calling"
    },
    {
      "name": "Groups"
    },
    {
      "name": "Support Tickets"
    },
    {
      "name": "Health Check"
    }
  ],
  "paths": {
    "/clients/register-from-code": {
      "post": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "Register from Embedded Signup Code (Recommended)",
        "description": "Use this after your client completes the Meta Embedded Signup flow. Send the `code` from `FB.login` — WasapFlow exchanges it server-side and registers the WABA. **Your app never sees the Meta access token.**\n\nFor Coexistence onboarding, include `\"connection_mode\": \"coexistence\"`. This tells WasapFlow that the client is connecting an existing WhatsApp Business App number and intends to keep the Business App usable.\n\n**Response:**\n```json\n{\n  \"success\": true,\n  \"client\": {\n    \"waba_id\": \"123456789\",\n    \"phone_number_id\": \"987654321\",\n    \"display_name\": \"My Client Business\",\n    \"connection_mode\": \"coexistence\",\n    \"status\": \"active\",\n    \"quality_rating\": \"GREEN\",\n    \"registered_at\": \"2026-05-15T10:00:00.000Z\"\n  }\n}\n```\n\n**Error responses:**\n\n| Error Code | Meaning |\n|------------|---------|\n| `MISSING_FIELDS` | `code` not provided |\n| `PLATFORM_NOT_CONFIGURED` | Meta App credentials not set up on WasapFlow platform |\n| `CODE_EXCHANGE_FAILED` | Code expired or invalid — client needs to redo Embedded Signup |\n| `DISCOVERY_FAILED` | Could not find WABA/Phone from the signup — client may not have completed the flow |\n| `WABA_LIMIT_REACHED` | Partner exceeded max WABAs on their plan |\n\n---",
        "operationId": "post_clients_register_from_code",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "code": "AQB8x...",
                "display_name": "My Client Business",
                "connection_mode": "coexistence"
              }
            }
          }
        }
      }
    },
    "/connect/session": {
      "post": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "Create a Connect Session (2.6.0)",
        "description": "Server-to-server. Exchanges your partner key for a **short-lived link** you can\nsafely open in your client's browser.\n\n**Response:**\n\nSend your client to `connect_url`. Nothing else is needed — the page handles the\nMeta popup, the Coexistence extras and the code exchange.\n\n| Field | Notes |\n|---|---|\n| `display_name` | Optional. Your client's business name, shown during onboarding |\n| `state` | Optional, max 255 chars. Opaque to us. Returned to you unchanged when the connection completes, so you can match it to your own client record |\n| `expires_in` | Seconds. 30 minutes. The link may be reloaded within that window |\n\n**Why this exists — please migrate.** The older form,\n`/bridge/connect?partner_key=wf_live_…`, puts your **full API credential** in a\nbrowser URL. From there it reaches the address bar, browser history, the\n`Referer` header sent to other hosts, and our own access logs in plain text.\nAnyone who reads any one of those gets complete control of your partner account —\nevery WABA, every send, every client.\n\nThe old form still works and we have not set a removal date, so nothing breaks\ntoday. But a `partner_key` that has been through a browser should be treated as\nexposed. **Rotation is not self-serve** — ask us and we will rotate it and send\nyou the new key. (An earlier version of this page said to rotate it in the\npartner portal. There is no such control; that was our mistake.)\n\nRotating revokes any connect session that has not been completed yet, so migrate\nand dep",
        "operationId": "post_connect_session",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "display_name": "USRAA Skin Centre",
                "state": "your-own-client-id-123"
              }
            }
          }
        }
      }
    },
    "/onboarding/events": {
      "get": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "Onboarding Session Events (2.8.0)",
        "description": "What actually happened inside Meta's dialog during your customers' onboarding\nattempts — including the ones that failed.\n\n**`meta_session_id` is the one to keep.** It is Meta's own trace id, and it is\nthe first thing Direct Support asks for. Quote it when you escalate to us.\n\nOnboarding runs in your customer's browser against Meta — our server only sees\nthe very end of it. Before 2.8.0 a failure inside that dialog left no record\nanywhere, so neither of us could say what went wrong. This is that record.\n\n`waba_id` is filled from v5's `messaging_account_ids[0]` when Meta no longer sends `waba_id`, and `event` now records Meta's finish event (`FINISH` / `FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING`) when the popup forwards it.\n\nThe same information also reaches your window live, on\n`WASAPFLOW_CONNECT_ERROR` (`meta_session_id`, `current_step`) and\n`WASAPFLOW_CONNECT_CANCEL` (`current_step`).\n\n---",
        "operationId": "get_onboarding_events",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "50"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/embedded-signup/config": {
      "get": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "Get Embedded Signup Config",
        "description": "Returns the Meta App ID and Config ID needed for your frontend `FB.init()` and `FB.login()`. No secrets are exposed.\n\nThe response also includes the recommended Coexistence `extras`. If you are self-hosting the Facebook SDK popup, pass these values into `FB.login`. If you use WasapFlow hosted popup, you do not need to handle these extras yourself.\n\n**Response (4 November 2026 — Embedded Signup v5):**\n\n`es_version` is new and tells you which Embedded Signup version `extras` is for. If we ever roll back to v4, `es_version` becomes `\"v4\"` and `extras` is again exactly `{\"setup\": {}, \"featureType\": \"whatsapp_business_app_onboarding\", \"sessionInfoVersion\": \"3\", \"version\": \"v4\"}` — so pass `extras` through unchanged and both cases work.\n\n> Also available in your **Partner Dashboard → Settings** page.\n\n---",
        "operationId": "get_embedded_signup_config",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/clients/register": {
      "post": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "Register a WABA (Manual)",
        "description": "> Use `register-from-code` instead when possible. This endpoint requires you to handle the Meta access token yourself. Performs an **upsert** — safe to call again if credentials change.\n\n**Response:**\n```json\n{\n  \"success\": true,\n  \"client\": {\n    \"waba_id\": \"123456789\",\n    \"phone_number_id\": \"987654321\",\n    \"display_name\": \"My Client Business\",\n    \"status\": \"active\",\n    \"tier\": \"STANDARD\",\n    \"quality_rating\": \"GREEN\",\n    \"registered_at\": \"2026-05-14T10:00:00.000Z\"\n  }\n}\n```\n\n---",
        "operationId": "post_clients_register",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "waba_id": "123456789",
                "phone_number_id": "987654321",
                "access_token": "EAAxxxxxxxx",
                "display_name": "My Client Business"
              }
            }
          }
        }
      }
    },
    "/clients": {
      "get": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "List WABAs",
        "description": "| Parameter | Default | Max |\n|-----------|---------|-----|\n| `limit` | `100` | `500` |\n| `offset` | `0` | — |\n\n> `phone_number` is the number Meta holds for `phone_number_id`. Map by this\n> field, never by `display_name` — that label is free text you set once, and\n> it does not follow the number if the number changes.\n\n> **A WABA with more than one number.** Each number now gets its own client\n> row, so `/clients` lists them separately under the same `waba_id`. Where a\n> WABA has two or more, `x-waba-id` alone no longer identifies a number, and\n> every endpoint will refuse the call:\n>\n> ```json\n> { \"success\": false, \"error\": { \"code\": \"META_ERROR\",\n>   \"message\": \"WABA 123 has 2 registered numbers — send x-phone-number-id to choose one: ...\" } }\n> ```\n>\n> Add the header `x-phone-number-id: <id>` to pick one. This is deliberate:\n> choosing for you would send your messages from the wrong number, and the\n> only person who would notice is the customer receiving them.\n>\n> WABAs with a single number are unaffected — no header needed.\n>\n> **Billing does not change.** Slots are counted per WABA, not per number, so\n> a second number on an existing WABA costs nothing extra.\n\n**Response:**\n\n---",
        "operationId": "get_clients",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "100"
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "0"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/clients/{wabaId}": {
      "delete": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "Remove a WABA",
        "description": "**Response:**\n> The WABA is marked as `deleted` — messaging stops immediately. This action cannot be undone via API; contact support to restore.\n\n---",
        "operationId": "delete_clients_wabaid",
        "parameters": [
          {
            "name": "wabaId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/clients/{wabaId}/refresh": {
      "post": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "Refresh WABA Quality & Tier",
        "description": "Fetches latest quality rating, messaging limit and throughput from Meta. Optionally update the stored access token in the same call.\n\n**Body (optional):**\n\n**Response:**\n```json\n{\n  \"success\": true,\n  \"quality_rating\": \"GREEN\",\n  \"throughput_level\": \"STANDARD\",\n  \"messaging_limit_tier\": \"TIER_2K\",\n  \"token_updated\": false\n}\n```\n\n> **Messaging limit values:** `TIER_250`, `TIER_1K`, `TIER_2K`, `TIER_10K`, `TIER_100K`, `UNLIMITED`\n\n---",
        "operationId": "post_clients_wabaid_refresh",
        "parameters": [
          {
            "name": "wabaId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "access_token": "EAAxxxxxxxx"
              }
            }
          }
        }
      }
    },
    "/clients/{wabaId}/resubscribe-webhook": {
      "post": {
        "tags": [
          "Client (WABA) Management"
        ],
        "summary": "Reconnect Meta Webhook",
        "description": "Re-subscribes a WABA to WasapFlow's Meta webhook endpoint. Use this when webhook events stop arriving without re-doing the Embedded Signup flow.\n\n**Response:**\n\n> Safe to call at any time — does not affect messaging or WABA configuration.\n\n---",
        "operationId": "post_clients_wabaid_resubscribe_webhook",
        "parameters": [
          {
            "name": "wabaId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/messages/send": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Send Text Message",
        "description": "",
        "operationId": "post_messages_send",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "to": "60123456789",
                "text": "Hello from my system!",
                "preview_url": false
              }
            }
          }
        }
      }
    },
    "/messages/interactive": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Authentication (OTP) messages (Direct Send)",
        "description": "```bash\ncurl -X POST https://officialapi.wasapflow.com/bridge/v1/messages/interactive \\\n  -H \"x-partner-key: wf_xxx\" -H \"x-waba-id: 123456789\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to\":\"60123456789\",\"category\":\"authentication\",\"interactive\":{\"type\":\"button\",\"body\":{\"text\":\"Your verification code is 123456.\"},\"action\":{\"buttons\":[{\"type\":\"otp\",\"otp\":{\"code\":\"123456\",\"otp_type\":\"copy_code\",\"text\":\"Copy Code\",\"code_expiration_minutes\":10}}]}}}'\n```\n\n**One-tap** — `\"otp_type\": \"one_tap\"` plus `autofill_text` and `supported_apps`:\n\n```js\n\"otp\": {\n  \"code\": \"123456\", \"otp_type\": \"one_tap\",\n  \"text\": \"Copy Code\", \"autofill_text\": \"Autofill\", \"code_expiration_minutes\": 10,\n  \"supported_apps\": [ { \"package_name\": \"com.example.app\", \"signature_hash\": \"K8a/AINcGX7\" } ]\n}\n```\n\n**Zero-tap** — as one-tap, with `\"otp_type\": \"zero_tap\"` and\n`\"zero_tap_terms_accepted\": true`.\n\nBridge checks Meta's documented rules before sending (`400 INVALID_FIELD`, with\n`field`): `interactive.type` is `button`; exactly one button of `type: \"otp\"`;\n`otp_type` is `copy_code` | `one_tap` | `zero_tap`; `code` is 4–10 letters or\ndigits and appears on its own in `body.text`; `zero_tap_terms_accepted: true`\nfor zero-tap only (and always); `supported_apps` 1–5 unique apps for\none-tap/zero-tap only (`package_name` like `com.example.app`, max 224;\n`signature_hash` exactly 11 characters); `text` / `autofill_text` max 25\ncharacters (`autofill_text` not on copy code); `code_expiration_minutes` 1–90\n(default ",
        "operationId": "post_messages_interactive",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "to": "60123456789",
                "category": "authentication",
                "ttl": 600,
                "interactive": {
                  "type": "button",
                  "body": {
                    "text": "Your verification code is 123456."
                  },
                  "action": {
                    "buttons": [
                      {
                        "type": "otp",
                        "otp": {
                          "code": "123456",
                          "otp_type": "copy_code",
                          "text": "Copy Code",
                          "code_expiration_minutes": 10
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/messages/template": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Send Template Message",
        "description": "**Simple template (body params only):**\n\n**Template with header image:**\n```json\n{\n  \"to\": \"60123456789\",\n  \"template\": {\n    \"name\": \"promo_with_image\",\n    \"language\": \"ms\",\n    \"components\": [\n      {\n        \"type\": \"header\",\n        \"parameters\": [\n          { \"type\": \"image\", \"image\": { \"link\": \"https://yourserver.com/promo.jpg\" } }\n        ]\n      },\n      {\n        \"type\": \"body\",\n        \"parameters\": [\n          { \"type\": \"text\", \"text\": \"Ahmad\" },\n          { \"type\": \"text\", \"text\": \"30%\" }\n        ]\n      }\n    ]\n  }\n}\n```\n\n**Template with call-to-action buttons:**\n```json\n{\n  \"to\": \"60123456789\",\n  \"template\": {\n    \"name\": \"order_with_tracking\",\n    \"language\": \"en_US\",\n    \"components\": [\n      {\n        \"type\": \"body\",\n        \"parameters\": [\n          { \"type\": \"text\", \"text\": \"ORD-12345\" }\n        ]\n      },\n      {\n        \"type\": \"button\",\n        \"sub_type\": \"url\",\n        \"index\": \"0\",\n        \"parameters\": [\n          { \"type\": \"text\", \"text\": \"ORD-12345\" }\n        ]\n      }\n    ]\n  }\n}\n```\n\n> **Template categories:** `MARKETING`, `UTILITY`, `AUTHENTICATION`  \n> Templates must be approved in Meta Business Manager before use.\n\n---",
        "operationId": "post_messages_template",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "to": "60123456789",
                "template": {
                  "name": "order_confirmed",
                  "language": "en_US",
                  "components": [
                    {
                      "type": "body",
                      "parameters": [
                        {
                          "type": "text",
                          "text": "John Doe"
                        },
                        {
                          "type": "text",
                          "text": "RM150.00"
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/messages/marketing": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Send Marketing Message (Marketing Messages API)",
        "description": "```bash\ncurl -X POST https://officialapi.wasapflow.com/bridge/v1/messages/marketing \\\n  -H \"x-partner-key: wf_xxx\" -H \"x-waba-id: 123456789\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to\":\"60123456789\",\"template\":{\"name\":\"raya_promo\",\"language\":{\"code\":\"ms\"}}}'\n```\n\n| Field | Required | Notes |\n|---|---|---|\n| `to` / `user_id` / `recipient` | ✅ | Phone or BSUID, same as every send endpoint |\n| `template` | ✅ | Same object as `/messages/template` — `name` and `language` required. **MARKETING** templates only; Meta rejects other categories |\n| `product_policy` | | `CLOUD_API_FALLBACK` (Meta's default): if the WABA is not onboarded, Meta delivers through Cloud API instead. `STRICT`: never fall back |\n| `message_activity_sharing` | | `true` / `false` — overrides the account default for sharing message activity (e.g. reads) with Meta |\n\nMulti-number WABAs pick the number with `x-phone-number-id`, as on every send\nendpoint. Bridge forwards the request to `POST /<PHONE_NUMBER_ID>/marketing_messages`\nas `{\"messaging_product\":\"whatsapp\",\"recipient_type\":\"individual\",\"to\":…,\"type\":\"template\",\"template\":{…}}`\nplus the optional fields.\n\n**Response** — the same shape as `/messages/template`, including\n`message_status` when Meta is pacing the template:\n\n```json\n{\n  \"success\": true,\n  \"waba_id\": \"123456789\",\n  \"message_id\": \"wamid.HBgLMTY0NjcwNDM1OTUVAgARGBI1RjQyNUE3NEYxMzAzMzQ5MkEA\",\n  \"messages\": [ { \"id\": \"wamid.HBgLMTY0NjcwNDM1OTUVAgARGBI1RjQyNUE3NEYxMzAzMzQ5MkEA\" } ]\n}\n```\n\n**Er",
        "operationId": "post_messages_marketing",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "to": "60123456789",
                "template": {
                  "name": "raya_promo",
                  "language": {
                    "code": "ms"
                  },
                  "components": [
                    {
                      "type": "body",
                      "parameters": [
                        {
                          "type": "text",
                          "text": "Ahmad"
                        }
                      ]
                    }
                  ]
                },
                "product_policy": "CLOUD_API_FALLBACK",
                "message_activity_sharing": true
              }
            }
          }
        }
      }
    },
    "/marketing-messages/status": {
      "get": {
        "tags": [
          "Messages"
        ],
        "summary": "Marketing Messages onboarding status",
        "description": "Reads Meta's `marketing_messages_onboarding_status` for the WABA:\n\n`ONBOARDED` means ready; `ELIGIBLE` means the terms still need accepting. Other\nvalues are Meta's. `disable_marketing_messages_on_cloud_api` is `null` when Meta\ndoes not return it for this token. `how_to_enable` is omitted once onboarded.\n\n---",
        "operationId": "get_marketing_messages_status",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/messages/media": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Send Media (Image / Document / Audio / Video)",
        "description": "**Send by URL:**\n\n**Send by media_id (from `/media/upload`):**\n```json\n{\n  \"to\": \"60123456789\",\n  \"type\": \"image\",\n  \"media\": {\n    \"id\": \"1234567890\",\n    \"caption\": \"Your receipt\"\n  }\n}\n```\n\n**Supported types and size limits:**\n\n| Type | Formats | Max Size |\n|------|---------|----------|\n| `image` | JPG, PNG, static WebP | 5 MB |\n| `sticker` | WebP | 100 KB static, 500 KB animated |\n| `document` | PDF, DOCX, XLSX, etc | 100 MB |\n| `audio` | MP3, OGG, AAC | 16 MB |\n| `video` | MP4, 3GP | 16 MB |\n\n---",
        "operationId": "post_messages_media",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "to": "60123456789",
                "type": "image",
                "media": {
                  "link": "https://yourserver.com/image.jpg",
                  "caption": "Your receipt"
                }
              }
            }
          }
        }
      }
    },
    "/messages/location": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Send Location",
        "description": "---",
        "operationId": "post_messages_location",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "to": "60123456789",
                "latitude": 3.139,
                "longitude": 101.6869,
                "name": "Kuala Lumpur City Centre",
                "address": "Jalan Ampang, KL"
              }
            }
          }
        }
      }
    },
    "/messages/reaction": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "React to a Message",
        "description": "---",
        "operationId": "post_messages_reaction",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "to": "60123456789",
                "message_id": "wamid.xxx",
                "emoji": "👍"
              }
            }
          }
        }
      }
    },
    "/messages/read": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Mark Message as Read",
        "description": "**Group messages too (Meta, 15 October 2026).** Send the `wamid` of a message received in a\n[group](#groups) — same request, no group id needed. Unlike a 1:1 chat, marking a group message\nread does **not** mark the earlier messages in that group, so mark each one. Messages can be\nmarked within 30 days of receipt; an invalid id returns `meta_code 131009`.\n\n---",
        "operationId": "post_messages_read",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "message_id": "wamid.xxx"
              }
            }
          }
        }
      }
    },
    "/templates": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "List Templates",
        "description": "**Response:**\n\n| Field | Type | Note |\n|---|---|---|\n| `id` | string | Meta template id (numeric string) |\n| `name` | string | Template name (unique per WABA) |\n| `language` | string | e.g. `en_US`, `ms`, `id` |\n| `category` | string | `MARKETING` / `UTILITY` / `AUTHENTICATION` |\n| `status` | string | `APPROVED` / `PENDING` / `REJECTED` / `PAUSED` / `DISABLED` / `IN_APPEAL` |\n| `components` | array | Meta component objects (BODY, HEADER, FOOTER, BUTTONS) |\n\n> **Quality + rejection reason:** Not returned by this endpoint. For rejection reason, subscribe to webhook `template.status_updated` (carries `reason`). For quality changes, subscribe to `template.quality_updated` (carries `previous_quality` + `new_quality`).\n\n> **Wrapping convention:** All list endpoints wrap items under a key named after the resource (lowercase plural) — never a bare array. `/clients` → `clients[]`, `/templates` → `templates[]`, `/broadcasts` → `broadcasts[]`, `/events` → `events[]`.\n\n---",
        "operationId": "get_templates",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "post": {
        "tags": [
          "Templates"
        ],
        "summary": "Create Template",
        "description": "**Positional variables `{{1}}`, `{{2}}` (default):**\n\n**Named variables `{{customer_name}}`:**\n```json\n{\n  \"name\": \"welcome_named\",\n  \"language\": \"en_US\",\n  \"category\": \"UTILITY\",\n  \"parameter_format\": \"NAMED\",\n  \"components\": [\n    {\n      \"type\": \"BODY\",\n      \"text\": \"Hello {{customer_name}}, order {{order_id}} ready.\",\n      \"example\": {\n        \"body_text_named_params\": [\n          { \"param_name\": \"customer_name\", \"example\": \"Ali\" },\n          { \"param_name\": \"order_id\",      \"example\": \"ORD123\" }\n        ]\n      }\n    }\n  ]\n}\n```\n\n**Document (PDF) header — requires `header_handle` from `/templates/upload-header`:**\n```json\n{\n  \"name\": \"repair_ticket\",\n  \"language\": \"en_US\",\n  \"category\": \"UTILITY\",\n  \"components\": [\n    {\n      \"type\": \"HEADER\",\n      \"format\": \"DOCUMENT\",\n      \"example\": { \"header_handle\": [\"4::aW1hZ2...\"] }\n    },\n    {\n      \"type\": \"BODY\",\n      \"text\": \"Your repair ticket {{1}} is ready.\",\n      \"example\": { \"body_text\": [[\"TKT123\"]] }\n    }\n  ]\n}\n```\n\n> **Categories:** `MARKETING`, `UTILITY`, `AUTHENTICATION`\n> **`parameter_format`** is optional (default `POSITIONAL`). Set to `NAMED` for `{{name}}` style — must be at TOP level, not inside the component.\n> Variable templates **must include `example` values** — Meta rejects approval otherwise.\n> Template approval may take up to 24 hours. Status changes will appear in Meta Business Manager.\n\n---",
        "operationId": "post_templates",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "name": "order_confirmed",
                "language": "en_US",
                "category": "UTILITY",
                "parameter_format": "POSITIONAL",
                "components": [
                  {
                    "type": "BODY",
                    "text": "Hello {{1}}, your order {{2}} has been confirmed.",
                    "example": {
                      "body_text": [
                        [
                          "Ali",
                          "ORD123"
                        ]
                      ]
                    }
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/templates/upload-header": {
      "post": {
        "tags": [
          "Templates"
        ],
        "summary": "Upload Template Header Sample (for IMAGE/VIDEO/DOCUMENT headers)",
        "description": "**Response:**\n```json\n{\n  \"success\": true,\n  \"header_handle\": \"4::aW1hZ2UvanBlZw==:...\"\n}\n```\n\n> **Why a separate endpoint?** Meta requires a sample file when creating a template with a media header (IMAGE / VIDEO / DOCUMENT). That sample is uploaded via Meta's **Resumable Upload API** against the platform `app_id`, returning a `header_handle` string — **different** from the numeric `media_id` returned by `/media/upload`.\n>\n> | Field | Endpoint | When | Bound to |\n> |---|---|---|---|\n> | `header_handle` (string) | `POST /templates/upload-header` | Template approval (one-time) | `app_id` |\n> | `media_id` (numeric) | `POST /media/upload` | Sending approved template (every send) | `phone_number_id`, expires 30 days |\n>\n> Use the returned `header_handle` in `components[].example.header_handle[]` when calling `POST /templates`.\n>\n> **Limits:** image 5 MB, video 16 MB, document 100 MB. Source `url` must be HTTPS and publicly reachable.\n\n---",
        "operationId": "post_templates_upload_header",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "url": "https://your-cdn.com/sample.pdf",
                "mime_type": "application/pdf"
              }
            }
          }
        }
      }
    },
    "/templates/{name}": {
      "delete": {
        "tags": [
          "Templates"
        ],
        "summary": "Delete Template",
        "description": "---",
        "operationId": "delete_templates_name",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/profile": {
      "get": {
        "tags": [
          "Business Profile"
        ],
        "summary": "Get Profile",
        "description": "---",
        "operationId": "get_profile",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "put": {
        "tags": [
          "Business Profile"
        ],
        "summary": "Update Profile",
        "description": "---",
        "operationId": "put_profile",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "about": "We sell quality products",
                "address": "Kuala Lumpur, Malaysia",
                "email": "hello@mybusiness.com",
                "websites": [
                  "https://mybusiness.com"
                ]
              }
            }
          }
        }
      }
    },
    "/contacts/{phone}": {
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "Check if Phone is on WhatsApp",
        "description": "**Response:**\n\n---",
        "operationId": "get_contacts_phone",
        "parameters": [
          {
            "name": "phone",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/contacts/media/{mediaId}": {
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "Download Incoming Media",
        "description": "Returns the media file as binary stream. Use the `media_id` from the `message.received` webhook payload.\n\n> **Note:** Media IDs from incoming messages expire after 30 days. Download and store them on your own server if needed long-term.\n\n---",
        "operationId": "get_contacts_media_mediaid",
        "parameters": [
          {
            "name": "mediaId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/media/upload": {
      "post": {
        "tags": [
          "Media"
        ],
        "summary": "Upload Media (Get a media_id)",
        "description": "| Field | Required | Description |\n|-------|----------|-------------|\n| `url` | ✅ | Public **HTTPS** URL of the file. Bridge fetches it server-side (30s timeout). |\n| `mime_type` | ✅ | Full MIME type, e.g. `image/png`, `image/jpeg`, `image/webp`, `application/pdf`, `video/mp4`. |\n| `type` | | Only matters for `image/webp`: `image` = static WebP for an image message (5 MB); `sticker` or omitted = sticker limits (100 KB static / 500 KB animated). |\n\n**Response:**\n```json\n{ \"success\": true, \"media_id\": \"1234567890\", \"waba_id\": \"123456789\" }\n```\nUse the returned **`media_id`** when sending: pass it to `POST /messages/media` as `media.id`.\n\n**Limits:** image 5 MB (JPEG, PNG, static WebP with `\"type\": \"image\"`) · sticker 100 KB / 500 KB animated · audio/video 16 MB · document 100 MB.\n\n> **Media IDs** are bound to the WABA's phone number and **expire after ~30 days** on Meta's side — re-upload if stale.\n\n> ⚠️ Sending `multipart/form-data` to this endpoint returns `400 UNSUPPORTED_CONTENT_TYPE`. (Historical note: docs previously showed a multipart request shape — that was never how this endpoint worked.)\n\n---",
        "operationId": "post_media_upload",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "url": "https://your-server.com/files/receipt.png",
                "mime_type": "image/png"
              }
            }
          }
        }
      }
    },
    "/analytics": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Get WABA Analytics",
        "description": "| Parameter | Type | Default | Max | Description |\n|-----------|------|---------|-----|-------------|\n| `days` | integer | `7` | `90` | Number of past days to retrieve |\n\n**Response:**\n\n---",
        "operationId": "get_analytics",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "7"
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/broadcasts": {
      "post": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Create Broadcast",
        "description": "> `scheduled_at` — ISO 8601 datetime string to schedule, or `null` to start immediately.  \n> Maximum **10,000 contacts** per broadcast.\n\n**Response:**\n```json\n{\n  \"success\": true,\n  \"broadcast_id\": 42,\n  \"status\": \"pending\",\n  \"total\": 2\n}\n```\n\n---",
        "operationId": "post_broadcasts",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "name": "Raya Promo 2026",
                "template_name": "raya_promo",
                "template_language": "ms",
                "template_components": [
                  {
                    "type": "body",
                    "parameters": [
                      {
                        "type": "text",
                        "text": "Ahmad"
                      }
                    ]
                  }
                ],
                "contacts": [
                  "60123456789",
                  "60198765432"
                ],
                "scheduled_at": null
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "List Broadcasts",
        "description": "| Parameter | Default | Max |\n|-----------|---------|-----|\n| `limit` | `20` | `100` |\n| `offset` | `0` | — |\n\n**Response:**\n\n**Broadcast status values:**\n\n| Status | Meaning |\n|--------|---------|\n| `pending` | Queued, not started yet |\n| `scheduled` | Waiting for `scheduled_at` datetime |\n| `processing` | Currently sending |\n| `completed` | All messages attempted |\n| `cancelled` | Cancelled before completion |\n\n---",
        "operationId": "get_broadcasts",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "20"
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "0"
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/broadcasts/{broadcastId}": {
      "get": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Get Broadcast Details",
        "description": "**Response:**\n\n---",
        "operationId": "get_broadcasts_broadcastid",
        "parameters": [
          {
            "name": "broadcastId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/broadcasts/{broadcastId}/cancel": {
      "post": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Cancel Broadcast",
        "description": "**Response:**\n\n> Can only cancel broadcasts with status `pending` or `scheduled`. Already-started broadcasts cannot be cancelled.\n\n---",
        "operationId": "post_broadcasts_broadcastid_cancel",
        "parameters": [
          {
            "name": "broadcastId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/qr-codes": {
      "get": {
        "tags": [
          "QR Codes & Short Links"
        ],
        "summary": "List QR codes",
        "description": "---",
        "operationId": "get_qr_codes",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "post": {
        "tags": [
          "QR Codes & Short Links"
        ],
        "summary": "Create a QR code",
        "description": "| Field | Required | Notes |\n|-------|----------|-------|\n| `prefilled_message` | Yes | The text that appears already typed in the customer's chat |\n| `image_format` | No | `PNG` (default) or `SVG` |\n\nThe response contains `code`, `deep_link_url` and `qr_image_url`. **Save the `code`** — it is the only way to update or delete this QR code later.\n\n> `qr_image_url` is a Meta CDN link and it expires. Download the image and host it yourself if you are printing it.\n\n---",
        "operationId": "post_qr_codes",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "prefilled_message": "Hi, I want to order",
                "image_format": "PNG"
              }
            }
          }
        }
      }
    },
    "/qr-codes/{id}": {
      "put": {
        "tags": [
          "QR Codes & Short Links"
        ],
        "summary": "Update the message behind a QR code",
        "description": "The image and the short link stay exactly the same.\n\n---",
        "operationId": "put_qr_codes_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "3JJPQ7BQO4RJB1"
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "prefilled_message": "Hi, I want the Raya promo"
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "QR Codes & Short Links"
        ],
        "summary": "Delete a QR code",
        "description": "---",
        "operationId": "delete_qr_codes_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "3JJPQ7BQO4RJB1"
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/conversational-automation": {
      "get": {
        "tags": [
          "Conversational Automation"
        ],
        "summary": "Read current settings",
        "description": "---",
        "operationId": "get_conversational_automation",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "post": {
        "tags": [
          "Conversational Automation"
        ],
        "summary": "Update settings",
        "description": "Send only the keys you want to change. Omitted keys are left untouched.\n\n> An ice breaker is dismissed automatically if the customer arrives through a `wa.me` link that already carries pre-filled text — including one of your own QR codes. The two features do not stack.\n\n---",
        "operationId": "post_conversational_automation",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "enable_welcome_message": true,
                "prompts": [
                  "Book a flight",
                  "Plan a trip"
                ],
                "commands": [
                  {
                    "command_name": "tickets",
                    "command_description": "Book flight tickets"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/blocked": {
      "get": {
        "tags": [
          "Blocked Users"
        ],
        "summary": "List blocked users",
        "description": "Supports `limit`, `after` and `before` for cursor pagination; the cursors come back in `paging.cursors`.\n\n---",
        "operationId": "get_blocked",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "50"
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "post": {
        "tags": [
          "Blocked Users"
        ],
        "summary": "Block users",
        "description": "```json\n{\n  \"success\": true,\n  \"blocked\": [{ \"input\": \"+60123456789\", \"wa_id\": \"60123456789\" }],\n  \"failed\": [\n    {\n      \"input\": \"+60198887777\",\n      \"wa_id\": \"60198887777\",\n      \"errors\": [{ \"code\": 131047, \"message\": \"Re-engagement required\",\n                   \"error_data\": { \"details\": \"User has not messaged in the last 24 hours\" } }]\n    }\n  ]\n}\n```\n\n> ⚠️ **`success: true` does not mean every number was blocked.** Meta reports failures per number. Always read the `failed` array — a partially successful request still returns 200.\n\n---",
        "operationId": "post_blocked",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "users": [
                  "+60123456789",
                  "+60198887777"
                ]
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Blocked Users"
        ],
        "summary": "Unblock users",
        "description": "Returns `unblocked` and `failed` in the same shape.\n\n---",
        "operationId": "delete_blocked",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/commerce-settings": {
      "get": {
        "tags": [
          "Commerce Settings"
        ],
        "summary": "Read",
        "description": "",
        "operationId": "get_commerce_settings",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "post": {
        "tags": [
          "Commerce Settings"
        ],
        "summary": "Update",
        "description": "Send either key or both. Useful when you sell through a catalog but take payment on your own site — show the catalog, hide the cart.\n\n---",
        "operationId": "post_commerce_settings",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "is_catalog_visible": true,
                "is_cart_enabled": false
              }
            }
          }
        }
      }
    },
    "/settings": {
      "get": {
        "tags": [
          "Settings (calling & storage)"
        ],
        "summary": "Read",
        "description": "",
        "operationId": "get_settings",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "post": {
        "tags": [
          "Settings (calling & storage)"
        ],
        "summary": "Update",
        "description": "Calling must be `ENABLED` here before any `/calls` request will work.\n\n---",
        "operationId": "post_settings",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "calling": {
                  "status": "ENABLED",
                  "call_icon_visibility": "DEFAULT",
                  "callback_permission_status": "ENABLED"
                }
              }
            }
          }
        }
      }
    },
    "/phone-status": {
      "get": {
        "tags": [
          "Phone Number Status"
        ],
        "summary": "GET /phone-status",
        "description": "> `platform_type` reads `CLOUD_API` for Coexistence numbers too. **`is_on_biz_app` is what tells you it is Coexistence** — not `platform_type`.\n\n---",
        "operationId": "get_phone_status",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/waba": {
      "get": {
        "tags": [
          "WhatsApp Business Account"
        ],
        "summary": "Account details",
        "description": "Returns `name`, `currency`, `timezone_id`, `account_review_status`, `business_verification_status`, `owner_business_info`, `message_template_namespace`, `health_status`, `ownership_type` and `is_enabled_for_insights`.\n\n`health_status` is the useful one when sends start failing — it reports `can_send_message` and names the entity that is blocking.\n\n> `primary_funding_id` is deliberately absent. It needs a billing permission the onboarding token never holds, and Meta rejects the **entire** request with code 10 when one requested field is not permitted — one unreachable field would wipe out the other eleven.\n\n---",
        "operationId": "get_waba",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/waba/activities": {
      "get": {
        "tags": [
          "WhatsApp Business Account"
        ],
        "summary": "Activity log (audit trail)",
        "description": "Who changed what on this WABA and when. This is what to reach for when a client asks why a template disappeared or why their messaging limit changed. `limit` is capped at 200.\n\n---",
        "operationId": "get_waba_activities",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "50"
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "1750000000"
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "1755000000"
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/waba/assigned-users": {
      "get": {
        "tags": [
          "WhatsApp Business Account"
        ],
        "summary": "Assigned users",
        "description": "Which Business Manager users have access to this WABA, and their tasks. Meta requires a business id for this call — Bridge fills in the one recorded at registration, so you do not need to know it. Override with `?business_id=` if you need a different one.\n\n---",
        "operationId": "get_waba_assigned_users",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/waba/solutions": {
      "get": {
        "tags": [
          "WhatsApp Business Account"
        ],
        "summary": "Solutions",
        "description": "---",
        "operationId": "get_waba_solutions",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/schedules": {
      "get": {
        "tags": [
          "Schedules"
        ],
        "summary": "GET /schedules",
        "description": "Scheduled changes queued against this WABA — for example a pending messaging-limit change.\n\n---",
        "operationId": "get_schedules",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/flows": {
      "get": {
        "tags": [
          "Flows (read-only)"
        ],
        "summary": "GET /flows",
        "description": "",
        "operationId": "get_flows",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/flows/{id}": {
      "get": {
        "tags": [
          "Flows (read-only)"
        ],
        "summary": "GET /flows/1234567890",
        "description": "Returns `id`, `name`, `status`, `categories`, `validation_errors`, `json_version` and `health_status`.\n\n> **Read-only on purpose.** Creating a Flow needs an endpoint you host plus an encryption key pair registered with Meta — that is a separate integration, not a proxy call. Exposing half of it would leave you thinking Flows were fully supported when they are not. Talk to us if you need the write path.\n\n---",
        "operationId": "get_flows_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "1234567890"
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/phone/request-code": {
      "post": {
        "tags": [
          "Phone Number Lifecycle"
        ],
        "summary": "Request a verification code",
        "description": "`code_method` is `SMS` or `VOICE`.\n\n---",
        "operationId": "post_phone_request_code",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "code_method": "SMS",
                "language": "en"
              }
            }
          }
        }
      }
    },
    "/phone/verify-code": {
      "post": {
        "tags": [
          "Phone Number Lifecycle"
        ],
        "summary": "Verify the code",
        "description": "---",
        "operationId": "post_phone_verify_code",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "code": "123456"
              }
            }
          }
        }
      }
    },
    "/phone/two-step": {
      "post": {
        "tags": [
          "Phone Number Lifecycle"
        ],
        "summary": "Set the two-step verification PIN",
        "description": "Exactly 6 digits.\n\n> **This changed in 2.9.0 and it matters.** Bridge needs this PIN to re-register a number after a migration or a recovery. Previously we only held one platform PIN, so a partner who changed the PIN in WhatsApp Manager broke our re-registration **silently**. Setting it through this endpoint stores your PIN encrypted against the client, and `/phone/register` reads it before falling back to the platform PIN. If you have ever changed a PIN outside Bridge, set it here once.\n\n---",
        "operationId": "post_phone_two_step",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "pin": "123456"
              }
            }
          }
        }
      }
    },
    "/phone/register": {
      "post": {
        "tags": [
          "Phone Number Lifecycle"
        ],
        "summary": "Re-register the number",
        "description": "`pin` is optional — Bridge uses your stored PIN, then the platform PIN, in that order.\n\n> **Bringing a second number online.** A WABA can hold several numbers, but\n> Bridge stores one client row per WABA, so only one of them is *the* client.\n> Every provisioning endpoint below — `/phone-status`, `/phone/request-code`,\n> `/phone/verify-code`, `/phone/two-step`, `/phone/register`,\n> `/phone/deregister` — accepts an optional **`phone_number_id`** to target\n> another number under the same WABA. Omit it and you get the client's own\n> number, exactly as before.\n>\n> ```json\n> { \"phone_number_id\": \"1377823848737261\", \"pin\": \"123456\" }\n> ```\n>\n> The number must belong to your WABA; Bridge checks with Meta and returns\n> `does not belong to WABA` otherwise. Use `GET /clients` (`phone_number`) and\n> your WhatsApp Manager to find the id.\n>\n> Embedded Signup will **not** get a new number registered — Meta's dialog\n> only lists numbers already live on Cloud API, so it returns the one that\n> already works. A number sitting at `status: PENDING` with\n> `platform_type: NOT_APPLICABLE` has never been registered; the sequence is\n> `/phone/two-step` (set the PIN) then `/phone/register`, both with\n> `phone_number_id`.\n>\n> `/phone/two-step` stores the PIN against the client row only when the target\n> is the client's own number, so a sibling's PIN cannot overwrite the one\n> re-registration depends on. Keep a sibling's PIN yourself.\n\n---",
        "operationId": "post_phone_register",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "pin": "123456"
              }
            }
          }
        }
      }
    },
    "/phone/deregister": {
      "post": {
        "tags": [
          "Phone Number Lifecycle"
        ],
        "summary": "Deregister the number",
        "description": "> ⚠️ The number stops receiving messages the moment this succeeds. Re-registering needs the PIN and, depending on how the number was onboarded, a fresh verification code.\n\n---",
        "operationId": "post_phone_deregister",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "confirm": true
              }
            }
          }
        }
      }
    },
    "/calls": {
      "post": {
        "tags": [
          "Calling"
        ],
        "summary": "POST /calls",
        "description": "`action` must be one of `connect`, `pre_accept`, `accept`, `reject`, `terminate`. Terminating takes `call_id` instead of `to`.\n\n**`messaging_account_id` (Meta, 2 November 2026)** — optional on every action. It names the Messaging\naccount (WABA) the call belongs to, and Meta only needs it when the access token can message on the\nnumber through **more than one** Messaging account; send the same value on every request for one call.\nBridge's per-client token normally has one, so you can leave it out. When you send it, it must be the\naccount id as digits (string or integer — Meta's reference shows both) or Bridge returns `INVALID_PAYLOAD`;\nit is passed to Meta unchanged.\n\n```json\n{\n  \"action\": \"accept\",\n  \"call_id\": \"wacid.xxx\",\n  \"session\": { \"sdp_type\": \"answer\", \"sdp\": \"<RFC 4566 SDP>\" },\n  \"messaging_account_id\": \"123456789\"\n}\n```\n\nCounts against your `rate_limit_per_sec`.\n\n---",
        "operationId": "post_calls",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "action": "connect",
                "to": "+60123456789",
                "session": {
                  "sdp_type": "offer",
                  "sdp": "<RFC 4566 SDP>"
                }
              }
            }
          }
        }
      }
    },
    "/groups": {
      "get": {
        "tags": [
          "Groups"
        ],
        "summary": "List active groups",
        "description": "",
        "operationId": "get_groups",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "post": {
        "tags": [
          "Groups"
        ],
        "summary": "Create a group",
        "description": "`subject` max 128 characters. `join_approval_mode` is `auto_approve` (default) or `approval_required`. The invite link arrives on the `group_lifecycle_update` webhook.",
        "operationId": "post_groups",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "subject": "New Purchase Inquiry",
                "description": "Options for current year models",
                "join_approval_mode": "auto_approve"
              }
            }
          }
        }
      }
    },
    "/groups/{group_id}": {
      "get": {
        "tags": [
          "Groups"
        ],
        "summary": "Group info",
        "description": "Returns `subject`, `description`, `participants`, `join_approval_mode`.",
        "operationId": "get_groups_group_id",
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      },
      "delete": {
        "tags": [
          "Groups"
        ],
        "summary": "Delete a group",
        "description": "---",
        "operationId": "delete_groups_group_id",
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "confirm": true
              }
            }
          }
        }
      }
    },
    "/groups/{group_id}/participants": {
      "delete": {
        "tags": [
          "Groups"
        ],
        "summary": "Remove participants",
        "description": "Maximum 8 per request. A removed participant can no longer rejoin through the invite link.",
        "operationId": "delete_groups_group_id_participants",
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/tickets": {
      "post": {
        "tags": [
          "Support Tickets"
        ],
        "summary": "Open a ticket",
        "description": "| Field | Type | Notes |\n|---|---|---|\n| `subject` | string, required | Max 200 characters. |\n| `category` | string | `technical` (default), `billing`, `onboarding`, `other`. |\n| `priority` | string | `normal` (default), `high`, `urgent`. |\n| `waba_id` | string | Optional. Must be a WABA registered to your account, otherwise `403 FORBIDDEN`. The `x-waba-id` header is used when the field is omitted. |\n| `phone_number_id` | string | Optional. Filled from the WABA when omitted. |\n| `message` | string, required | Max 20,000 characters. Include what happened, when it started (with timezone), the exact error (Meta code, `fbtrace_id`), what you tried and the business impact. |\n| `diagnostics` | object | Optional raw data (error JSON, sample request/response, message IDs). Max 20 KB serialised. |\n\nReturns **201** with `data.uid` (e.g. `TKT-AB12CD`). If you already have a ticket with the same subject (case-insensitive) that is not closed and was opened in the last 24 hours, your message is added to that ticket instead and the response is **200** with `\"deduplicated\": true`.\n\n**Limit:** 20 new tickets per 24 hours per partner account — beyond that the API returns `429 QUOTA_EXCEEDED`. Reply to an existing ticket instead.\n\n```json\n{\n  \"success\": true,\n  \"deduplicated\": false,\n  \"data\": {\n    \"uid\": \"TKT-AB12CD\",\n    \"subject\": \"Outbound messages failing with 131000 since 14:05\",\n    \"category\": \"technical\",\n    \"priority\": \"urgent\",\n    \"status\": \"open\",\n    \"waba_id\": \"1234567890\",\n   ",
        "operationId": "post_tickets",
        "parameters": [
          {
            "$ref": "#/components/parameters/WabaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "subject": "Outbound messages failing with 131000 since 14:05",
                "category": "technical",
                "priority": "urgent",
                "phone_number_id": "1098765432",
                "message": "Since 2026-11-02 14:05 UTC+8 every POST /messages/send for this number returns 502 META_ERROR 131000. Retried with backoff for 30 minutes and checked the number is CONNECTED. All outbound replies for this client are failing.",
                "diagnostics": {
                  "meta_code": 131000,
                  "fbtrace_id": "AbCdEf123"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Support Tickets"
        ],
        "summary": "List your tickets",
        "description": "`status` is optional: `open` (waiting for us), `answered` (we replied), `awaiting_partner` (we need information from you), `closed`. Returns your own tickets only, newest activity first (max 200).",
        "operationId": "get_tickets",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "open"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/tickets/{uid}": {
      "get": {
        "tags": [
          "Support Tickets"
        ],
        "summary": "Read a ticket and its messages",
        "description": "Returns the ticket plus `messages[]` (`author` is `partner` or `staff`). Tickets that belong to another partner return `404 NOT_FOUND`. Poll no more often than every 15 minutes — you also receive an email when we reply.",
        "operationId": "get_tickets_uid",
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        }
      }
    },
    "/tickets/{uid}/reply": {
      "post": {
        "tags": [
          "Support Tickets"
        ],
        "summary": "Reply to a ticket",
        "description": "The ticket status becomes `open` again. Replying to a `closed` ticket reopens it (`\"reopened\": true`) — no need to open a new ticket for the same issue.\n\nScreenshots can be attached from the portal only. A ready-made prompt for your AI agent is on the portal's Support page.\n\n---",
        "operationId": "post_tickets_uid_reply",
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "message": "Still failing as of 15:30 UTC+8. New fbtrace_id attached.",
                "diagnostics": {
                  "fbtrace_id": "XyZ987"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Health Check"
        ],
        "summary": "GET /health",
        "description": "No authentication required. Use for uptime monitoring.\n\n**Response:**\n\n---",
        "operationId": "get_health",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success — `{\"success\": true, ...}`"
          },
          "4XX": {
            "description": "Error — `{\"success\": false, \"error\": {\"code\", \"message\"}}`"
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PartnerKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-partner-key",
        "description": "Partner key from the partner portal → Integration."
      }
    },
    "parameters": {
      "WabaId": {
        "name": "x-waba-id",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "WhatsApp Business Account ID registered to your partner account."
      }
    }
  }
}