{
  "openapi": "3.1.0",
  "info": {
    "title": "Marsal Cloud WhatsApp Business API",
    "version": "1.0.0",
    "summary": "Send WhatsApp messages, templates and OTPs, and manage conversations and contacts.",
    "description": "The Marsal Cloud REST API lets you send WhatsApp messages and approved templates,\nrun OTP verification, read and reply inside conversations, and manage contacts —\nfrom any language that can make an HTTPS request.\n\n## Authentication\n\nEvery request carries a workspace API key as a bearer token:\n\n```\nAuthorization: Bearer msk_live_xxxxxxxxxxxxxxxxxxxx\n```\n\nCreate keys in the workspace panel under **Developers · API**. A key is scoped to one\nworkspace and grants full access to that workspace — keep it secret. Mobile device\ntokens issued by the Marsal apps are accepted on the same endpoints; those tokens are\nadditionally rate limited to 300 calls per minute and answer `429 rate_limited`.\n\n## Phone numbers\n\nRecipient numbers are always in international format, **digits only** — no `+`, spaces\nor dashes. A Saudi number is `9665xxxxxxxx`, an Egyptian number `2010xxxxxxxx`.\nNon-digit characters in `to` and `wa_id` are stripped by the server before use.\n\n## Response envelope\n\nEvery response is JSON. A success carries `\"ok\": true`; a business error carries\n`\"ok\": false` and an `error` string, with an HTTP status that matches. Some errors add\nextra fields (`detail`, `feature`, `message_id`).\n\n## The 24-hour window\n\nWhatsApp only allows free-form text within 24 hours of the customer's last inbound\nmessage. Outside that window use an approved template (`POST /messages` with\n`type: template`). `session_open` on a conversation tells you which side of the window\nyou are on.\n",
    "contact": {
      "name": "Marsal Cloud",
      "url": "https://usemarsal.cloud/api-docs.php"
    },
    "termsOfService": "https://usemarsal.cloud/terms.php",
    "license": {
      "name": "Proprietary",
      "identifier": "LicenseRef-Marsal-Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://usemarsal.cloud/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Messaging",
      "description": "Send text messages and approved templates."
    },
    {
      "name": "OTP",
      "description": "One-time passcodes delivered over WhatsApp."
    },
    {
      "name": "Conversations",
      "description": "Read, reply to and manage chats."
    },
    {
      "name": "Contacts",
      "description": "The people in your workspace."
    },
    {
      "name": "Reference",
      "description": "Templates, numbers, campaigns and workspace counters."
    },
    {
      "name": "Realtime",
      "description": "Poll for new inbound messages."
    }
  ],
  "paths": {
    "/messages": {
      "post": {
        "operationId": "sendMessage",
        "tags": [
          "Messaging"
        ],
        "summary": "Send a text message or an approved template",
        "description": "Sends a WhatsApp message to any number. The recipient is created as a contact and a\nconversation is opened on the workspace's first connected number if one does not\nalready exist.\n\n* `type: text` (the default) sends free text. WhatsApp only accepts it inside the\n  24-hour customer-service window; outside it, Meta rejects the send and the call\n  answers `502 send_failed`.\n* `type: template` sends an approved template and works at any time.\n\nFor templates, `params` fills the body variables in order. If you need header,\nbutton or non-text components, send a raw `components` array instead — it is\nforwarded to Meta as-is and is only read when `params` is absent or empty.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/SendTextRequest"
                  },
                  {
                    "$ref": "#/components/schemas/SendTemplateRequest"
                  }
                ]
              },
              "examples": {
                "text": {
                  "summary": "Free text inside the 24h window",
                  "value": {
                    "to": "966550521672",
                    "type": "text",
                    "body": "أهلاً بيك يا أحمد، طلبك اتشحن النهاردة ✅"
                  }
                },
                "template": {
                  "summary": "Approved template with body variables",
                  "value": {
                    "to": "201001234567",
                    "type": "template",
                    "template": "order_confirmation",
                    "language": "ar",
                    "params": [
                      "أحمد",
                      "#9402"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted by WhatsApp and stored in the conversation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendMessageResponse"
                },
                "example": {
                  "ok": true,
                  "message_id": 91733,
                  "wamid": "wamid.HBgMOTY2NTUwNTIxNjcy",
                  "to": "966550521672"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "409": {
            "description": "The workspace has no WhatsApp number to send from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "no_whatsapp_number"
                }
              }
            }
          },
          "422": {
            "description": "A required field is missing — `to_required`, `body_required` (text) or\n`template_required` (template).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "to_required"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "WhatsApp refused the message. `detail` carries Meta's own error text, and\n`message_id` is the row Marsal stored for the failed attempt.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendFailedError"
                },
                "example": {
                  "ok": false,
                  "error": "send_failed",
                  "detail": "(#131047) Re-engagement message",
                  "message_id": 91734
                }
              }
            }
          }
        }
      }
    },
    "/otp/send": {
      "post": {
        "operationId": "sendOtp",
        "tags": [
          "OTP"
        ],
        "summary": "Send a one-time passcode",
        "description": "Marsal generates a six-digit code (or uses the one you pass), stores only its\nSHA-256 hash with the given TTL, and delivers it over WhatsApp.\n\nPass `template` to deliver through an approved OTP template — that works at any\ntime. Omit it and the code goes out as plain Arabic text, which WhatsApp only\naccepts inside the 24-hour window.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OtpSendRequest"
              },
              "example": {
                "to": "966550521672",
                "template": "otp_code",
                "language": "ar",
                "ttl": 300
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The code was sent and stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtpSendResponse"
                },
                "example": {
                  "ok": true,
                  "to": "966550521672",
                  "expires_in": 300,
                  "message_id": 91740
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "409": {
            "description": "The workspace has no WhatsApp number to send from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "no_whatsapp_number"
                }
              }
            }
          },
          "422": {
            "description": "`to` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "to_required"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "WhatsApp refused the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendFailedError"
                },
                "example": {
                  "ok": false,
                  "error": "send_failed",
                  "detail": "(#132000) Number of parameters does not match"
                }
              }
            }
          }
        }
      }
    },
    "/otp/verify": {
      "post": {
        "operationId": "verifyOtp",
        "tags": [
          "OTP"
        ],
        "summary": "Verify a one-time passcode",
        "description": "Checks the code against the newest unverified, unexpired code stored for that\nnumber in your workspace. A correct code is marked used and cannot be replayed.\nA wrong code increments an attempt counter; after five failed attempts the code\nstops matching and `verified` stays `false`.\n\nA wrong, expired or unknown code is **not** an error — the call answers `200` with\n`\"verified\": false`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OtpVerifyRequest"
              },
              "example": {
                "to": "966550521672",
                "code": "418302"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The check ran. Read `verified`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtpVerifyResponse"
                },
                "example": {
                  "ok": true,
                  "verified": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "description": "`to` or `code` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "to_and_code_required"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/conversations": {
      "get": {
        "operationId": "listConversations",
        "tags": [
          "Conversations"
        ],
        "summary": "List conversations",
        "description": "Chats in the workspace, newest activity first. Each row carries the contact, the\nunread count, a preview of the last message and whether the 24-hour window is open.\n\nWorkspaces that set an inbox auto-hide window in their settings drop chats that\nhave had no inbound message for longer than that window; the conversation itself is\nuntouched and returns to the list on the next inbound message.\n",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by state. Any other value is treated as `all`.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "open",
                "pending",
                "closed"
              ],
              "default": "all"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum rows to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The conversation list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationListResponse"
                },
                "example": {
                  "ok": true,
                  "conversations": [
                    {
                      "id": 8421,
                      "contact_id": 5107,
                      "name": "أحمد الغامدي",
                      "wa_id": "966550521672",
                      "status": "open",
                      "unread": 2,
                      "last_message_at": "2026-09-20 14:31:08",
                      "last_inbound_at": "2026-09-20 14:31:08",
                      "session_open": true,
                      "preview": "هل المنتج متوفر؟"
                    },
                    {
                      "id": 8377,
                      "contact_id": 5022,
                      "name": "سارة عبد الرحمن",
                      "wa_id": "201001234567",
                      "status": "closed",
                      "unread": 0,
                      "last_message_at": "2026-09-18 09:12:44",
                      "last_inbound_at": "2026-09-18 09:10:02",
                      "session_open": false,
                      "preview": "شكراً ليكم 🌹"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/conversations/{id}/messages": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ConversationId"
        }
      ],
      "get": {
        "operationId": "listConversationMessages",
        "tags": [
          "Conversations"
        ],
        "summary": "Get a conversation's messages",
        "description": "The most recent messages of a conversation, oldest first.\n\nEmoji reactions are not returned as their own rows: each one is folded into the\n`reactions` array of the message it points at, with `mine` telling you which side\nsent it. A quoted message is embedded under `reply_to` with its body truncated to\n140 characters. Media that arrived without a caption comes back with an empty\n`body` and a `media_url`.\n",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many of the newest messages to load before filtering.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 200
            }
          },
          {
            "name": "after_id",
            "in": "query",
            "required": false,
            "description": "Return only messages with an id greater than this. Use it to poll a thread.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The thread.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageListResponse"
                },
                "example": {
                  "ok": true,
                  "session_open": true,
                  "ai_enabled": false,
                  "messages": [
                    {
                      "id": 91730,
                      "direction": "in",
                      "type": "text",
                      "body": "هل المنتج متوفر؟",
                      "media_url": null,
                      "status": null,
                      "created_at": "2026-09-20 14:31:08",
                      "reactions": []
                    },
                    {
                      "id": 91733,
                      "direction": "out",
                      "type": "text",
                      "body": "أهلاً بيك، أيوه متوفر ✅",
                      "media_url": null,
                      "status": "delivered",
                      "created_at": "2026-09-20 14:32:55",
                      "reply_to": {
                        "id": 91730,
                        "direction": "in",
                        "type": "text",
                        "body": "هل المنتج متوفر؟"
                      },
                      "reactions": [
                        {
                          "emoji": "👍",
                          "mine": false
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/ConversationNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "replyInConversation",
        "tags": [
          "Conversations"
        ],
        "summary": "Reply inside a chat",
        "description": "Sends a free-text reply in an existing conversation. Only works while the 24-hour\nwindow is open — use `POST /messages` with a template otherwise.\n\nReplying takes the chat off the AI bot (human takeover), exactly as the web inbox\ndoes. The message is stored immediately with `status: \"pending\"` and handed to Meta\nafter the response has been flushed, so `wamid` is always `null` here; poll the\nthread to watch the status move to `sent`, `delivered`, `read` or `failed`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplyRequest"
              },
              "example": {
                "body": "تمام، هجهزلك الطلب النهاردة 👌",
                "reply_to": 91730
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored and queued for delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReplyResponse"
                },
                "example": {
                  "ok": true,
                  "message_id": 91733,
                  "wamid": null,
                  "status": "pending"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/ConversationNotFound"
          },
          "409": {
            "description": "`session_closed` — the 24-hour window has expired, so free text is not allowed.\n`number_unavailable` — the conversation's WhatsApp number no longer belongs to\nthis workspace.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "session_closed"
                }
              }
            }
          },
          "422": {
            "description": "`body` is empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "empty_message"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/conversations/{id}/read": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ConversationId"
        }
      ],
      "post": {
        "operationId": "markConversationRead",
        "tags": [
          "Conversations"
        ],
        "summary": "Mark a conversation read",
        "description": "Clears the unread counter and, when something was actually unread, tells WhatsApp\nthe customer's latest message was read so their ticks turn blue. Takes no body.\n",
        "responses": {
          "200": {
            "description": "Marked read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/ConversationNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/conversations/{id}/ai": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ConversationId"
        }
      ],
      "post": {
        "operationId": "setConversationAi",
        "tags": [
          "Conversations"
        ],
        "summary": "Turn the AI bot on or off for one chat",
        "description": "Controls whether the AI bot answers in this conversation. Note that any agent or\nAPI reply in the thread switches it off again (human takeover).\n\n`enabled` is read loosely: it is truthy for `1`, `true`, `\"1\"`, `\"true\"`, `\"on\"`\nand `\"yes\"`, and falsy for everything else. Omitting it turns the bot **on**.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AiToggleRequest"
              },
              "example": {
                "enabled": 0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiToggleResponse"
                },
                "example": {
                  "ok": true,
                  "ai_enabled": 0
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/ConversationNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "operationId": "listContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "List contacts",
        "description": "Contacts in the workspace, newest first, each with the tags attached to it. The\nresponse also carries every tag defined in the workspace under `tags`, so a client\ncan draw the full filter list even when the current page uses none of them.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on the contact's name or WhatsApp number.",
            "schema": {
              "type": "string"
            },
            "example": "أحمد"
          },
          {
            "name": "tag_id",
            "in": "query",
            "required": false,
            "description": "Return only contacts carrying this tag.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum rows to return. Values outside the range are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The contact list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactListResponse"
                },
                "example": {
                  "ok": true,
                  "tags": [
                    {
                      "id": 12,
                      "name": "عملاء الرياض",
                      "color": "#00E5A4"
                    },
                    {
                      "id": 13,
                      "name": "VIP",
                      "color": "#0099FF"
                    }
                  ],
                  "contacts": [
                    {
                      "id": 5107,
                      "wa_id": "966550521672",
                      "name": "أحمد الغامدي",
                      "email": "ahmed@example.sa",
                      "created_at": "2026-08-02 11:05:19",
                      "opted_in": 1,
                      "last_inbound_at": "2026-09-20 14:31:08",
                      "tags": [
                        {
                          "id": 12,
                          "name": "عملاء الرياض",
                          "color": "#00E5A4"
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Add a contact",
        "description": "Creates the contact, or updates the existing one with the same WhatsApp number —\nthe number is the key, so this call is safe to repeat. Non-digit characters in\n`wa_id` are stripped before use.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContactRequest"
              },
              "example": {
                "wa_id": "966550521672",
                "name": "أحمد الغامدي",
                "email": "ahmed@example.sa"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contact id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateContactResponse"
                },
                "example": {
                  "ok": true,
                  "contact_id": 5107
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "description": "`wa_id` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "wa_id_required"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/templates": {
      "get": {
        "operationId": "listTemplates",
        "tags": [
          "Reference"
        ],
        "summary": "List message templates",
        "description": "Your WhatsApp message templates with their Meta status, parsed header, footer and\nbutton labels, and `vars` — the number of distinct `{{n}}` placeholders in the\nbody, which is how many entries `params` needs when you send it.\n\nMarsal refreshes template statuses from Meta at most once every three minutes, so\na fresh approval can take that long to appear here.\n\n`can_manage` reports whether the caller may create or delete templates. An API key\nis read-only for templates and always sees `false`.\n",
        "responses": {
          "200": {
            "description": "The template list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateListResponse"
                },
                "example": {
                  "ok": true,
                  "can_manage": false,
                  "templates": [
                    {
                      "id": 77,
                      "name": "order_confirmation",
                      "language": "ar",
                      "category": "UTILITY",
                      "status": "APPROVED",
                      "body": "أهلاً {{1}}، طلبك رقم {{2}} اتأكد ✅",
                      "header": "تأكيد الطلب",
                      "footer": "مرسال",
                      "buttons": [
                        "تتبع الطلب"
                      ],
                      "vars": 2,
                      "wa_number_id": 4,
                      "created_at": "2026-07-14 10:22:51"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/numbers": {
      "get": {
        "operationId": "listNumbers",
        "tags": [
          "Reference"
        ],
        "summary": "List connected WhatsApp numbers",
        "description": "The workspace's WhatsApp numbers with their status, Meta quality rating, and the\n`waba_id` / `phone_number_id` that Meta support asks for. `webhook_url` is the\ninbound callback URL Marsal has registered with Meta for this deployment — it is\nwhere WhatsApp delivers messages to Marsal, not where Marsal calls you. Access\ntokens are never returned.\n",
        "responses": {
          "200": {
            "description": "The number list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NumberListResponse"
                },
                "example": {
                  "ok": true,
                  "webhook_url": "https://usemarsal.cloud/webhook.php",
                  "numbers": [
                    {
                      "id": 4,
                      "display_number": "+966 55 052 1672",
                      "verified_name": "متجر نجد",
                      "status": "connected",
                      "quality_rating": "GREEN",
                      "waba_id": "102938475610293",
                      "phone_number_id": "109876543210987"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/campaigns": {
      "get": {
        "operationId": "listCampaigns",
        "tags": [
          "Reference"
        ],
        "summary": "List broadcast campaigns",
        "description": "The 100 most recent campaigns with their per-recipient counters. Read-only over\nthe API; campaigns are created in the workspace panel. `can_manage` is always\n`false` for an API key.\n",
        "responses": {
          "200": {
            "description": "The campaign list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignListResponse"
                },
                "example": {
                  "ok": true,
                  "can_manage": false,
                  "items": [
                    {
                      "id": 311,
                      "name": "عرض رمضان",
                      "status": "sent",
                      "total": 1200,
                      "sent": 1187,
                      "delivered": 1102,
                      "read": 840,
                      "failed": 13,
                      "scheduled_at": "2026-09-15 09:00:00",
                      "started_at": "2026-09-15 09:00:07",
                      "finished_at": "2026-09-15 09:41:22",
                      "created_at": "2026-09-14 22:10:03",
                      "template_id": 77,
                      "template_name": "order_confirmation",
                      "segment_id": 9,
                      "segment_name": "عملاء الخليج",
                      "wa_number_id": 4
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/dashboard": {
      "get": {
        "operationId": "getDashboard",
        "tags": [
          "Reference"
        ],
        "summary": "Workspace counters",
        "description": "Live counts for the workspace: total contacts, open conversations, total unread\nmessages, messages created today (server time) and total campaigns.\n",
        "responses": {
          "200": {
            "description": "The counters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardResponse"
                },
                "example": {
                  "ok": true,
                  "stats": {
                    "contacts": 5240,
                    "open_conversations": 38,
                    "unread": 71,
                    "messages_today": 612,
                    "campaigns": 17
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/updates": {
      "get": {
        "operationId": "pollUpdates",
        "tags": [
          "Realtime"
        ],
        "summary": "Poll for new inbound messages",
        "description": "Returns up to 50 inbound messages with an id greater than `since_id`, oldest first,\nplus `last_id` — the newest inbound message id in the workspace. Store `last_id`\nand pass it as the next `since_id`.\n\nNon-text messages come back with a type placeholder as the body (`[image]`,\n`[audio]`, `[document]`, …) rather than their caption; text bodies are truncated to\n200 characters. For anything longer, or for outbound and status events, use\nwebhooks.\n",
        "parameters": [
          {
            "name": "since_id",
            "in": "query",
            "required": false,
            "description": "Return only inbound messages with an id greater than this.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "New inbound messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdatesResponse"
                },
                "example": {
                  "ok": true,
                  "last_id": 91742,
                  "unread": 71,
                  "messages": [
                    {
                      "id": 91741,
                      "conversation_id": 8421,
                      "from": "أحمد الغامدي",
                      "type": "text",
                      "body": "تمام، مستنيك",
                      "created_at": "2026-09-20 15:02:10"
                    },
                    {
                      "id": 91742,
                      "conversation_id": 8377,
                      "from": "+201001234567",
                      "type": "image",
                      "body": "[image]",
                      "created_at": "2026-09-20 15:03:44"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "webhooks": {
    "message.received": {
      "post": {
        "operationId": "onMessageReceived",
        "tags": [
          "Webhooks"
        ],
        "summary": "A customer sent you a WhatsApp message",
        "description": "Fired for every inbound message, after it has been stored.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "message.received"
                      },
                      "data": {
                        "$ref": "#/components/schemas/MessageReceivedData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "message.received",
                "tenant_id": 12,
                "delivery_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
                "ts": 1758326400,
                "data": {
                  "conversation_id": 8421,
                  "human_reply_at": null,
                  "contact": {
                    "wa_id": "966550521672",
                    "name": "أحمد الغامدي"
                  },
                  "type": "text",
                  "text": "هل المنتج متوفر؟"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx counts; everything else is retried."
          }
        }
      }
    },
    "message.sent": {
      "post": {
        "operationId": "onMessageSent",
        "tags": [
          "Webhooks"
        ],
        "summary": "Your workspace sent a message",
        "description": "Fired when an outbound message reaches its final state — sent by an agent, the API,\nthe bot, an auto-reply or a scheduled send. Broadcast campaigns deliberately do\n**not** fire this per recipient; use `campaign.finished` for totals and\n`message.status` for per-recipient delivery state.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "message.sent"
                      },
                      "data": {
                        "$ref": "#/components/schemas/MessageSentData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "message.sent",
                "tenant_id": 12,
                "delivery_id": "b2c3d4e5f60718293a4b5c6d7e8f9001",
                "ts": 1758326460,
                "data": {
                  "message_id": 91733,
                  "conversation_id": 8421,
                  "contact_id": 5107,
                  "type": "text",
                  "text": "أهلاً بيك، أيوه متوفر ✅",
                  "media_url": null,
                  "media_name": null,
                  "wa_message_id": "wamid.HBgMOTY2NTUwNTIxNjcy",
                  "status": "sent",
                  "sender_user_id": 42,
                  "template_id": null
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "message.status": {
      "post": {
        "operationId": "onMessageStatus",
        "tags": [
          "Webhooks"
        ],
        "summary": "WhatsApp reported a delivery status",
        "description": "Fired when Meta reports `sent`, `delivered`, `read` or `failed` for one of your\nmessages, and only when the state actually moved forward — Meta's duplicate\ncallbacks do not queue the same delivery twice. For a campaign recipient,\n`campaign_id` is set.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "message.status"
                      },
                      "data": {
                        "$ref": "#/components/schemas/MessageStatusData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "message.status",
                "tenant_id": 12,
                "delivery_id": "c3d4e5f60718293a4b5c6d7e8f900112",
                "ts": 1758326475,
                "data": {
                  "wa_message_id": "wamid.HBgMOTY2NTUwNTIxNjcy",
                  "status": "delivered",
                  "error": null,
                  "message_id": 91733,
                  "conversation_id": 8421,
                  "campaign_id": null,
                  "recipient_wa_id": "966550521672"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "conversation.assigned": {
      "post": {
        "operationId": "onConversationAssigned",
        "tags": [
          "Webhooks"
        ],
        "summary": "A chat was assigned, moved or released",
        "description": "Fired on every assignment change — manual, automatic, round-robin reassignment, or\nrelease after silence. `from_user_id` and `to_user_id` are `null` when the chat was\nunassigned on that side.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "conversation.assigned"
                      },
                      "data": {
                        "$ref": "#/components/schemas/ConversationAssignedData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "conversation.assigned",
                "tenant_id": 12,
                "delivery_id": "d4e5f60718293a4b5c6d7e8f90011223",
                "ts": 1758326500,
                "data": {
                  "conversation_id": 8421,
                  "from_user_id": null,
                  "to_user_id": 42,
                  "reason": "manual",
                  "contact_id": 5107
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "conversation.closed": {
      "post": {
        "operationId": "onConversationClosed",
        "tags": [
          "Webhooks"
        ],
        "summary": "A chat was closed",
        "description": "Fired when a chat is closed from the inbox, over the API, or after a service rating.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "conversation.closed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/ConversationClosedData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "conversation.closed",
                "tenant_id": 12,
                "delivery_id": "e5f60718293a4b5c6d7e8f9001122334",
                "ts": 1758326600,
                "data": {
                  "conversation_id": 8421,
                  "contact_id": 5107,
                  "assigned_user_id": 42,
                  "source": "inbox"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "csat.received": {
      "post": {
        "operationId": "onCsatReceived",
        "tags": [
          "Webhooks"
        ],
        "summary": "A customer replied with a satisfaction score",
        "description": "Fired when a customer answers a service-rating prompt with a score from 1 to 5.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "csat.received"
                      },
                      "data": {
                        "$ref": "#/components/schemas/CsatReceivedData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "csat.received",
                "tenant_id": 12,
                "delivery_id": "f60718293a4b5c6d7e8f900112233445",
                "ts": 1758326700,
                "data": {
                  "conversation_id": 8421,
                  "contact_id": 5107,
                  "rating": 5
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "contact.created": {
      "post": {
        "operationId": "onContactCreated",
        "tags": [
          "Webhooks"
        ],
        "summary": "A contact was added for the first time",
        "description": "Fired the first time a contact appears in the workspace, whatever created it.\n`source` names the surface — for example `inbound`, `manual`, `api` or `import`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "contact.created"
                      },
                      "data": {
                        "$ref": "#/components/schemas/ContactCreatedData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "contact.created",
                "tenant_id": 12,
                "delivery_id": "0718293a4b5c6d7e8f90011223344556",
                "ts": 1758326800,
                "data": {
                  "contact_id": 5107,
                  "wa_id": "966550521672",
                  "name": "أحمد الغامدي",
                  "email": null,
                  "source": "inbound"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "campaign.finished": {
      "post": {
        "operationId": "onCampaignFinished",
        "tags": [
          "Webhooks"
        ],
        "summary": "A broadcast campaign stopped sending",
        "description": "Fired once per campaign when it reaches a terminal state, completed or aborted,\nwith the final counters. `total` is the number of recipients enrolled.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "campaign.finished"
                      },
                      "data": {
                        "$ref": "#/components/schemas/CampaignFinishedData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "campaign.finished",
                "tenant_id": 12,
                "delivery_id": "18293a4b5c6d7e8f9001122334455667",
                "ts": 1758326900,
                "data": {
                  "campaign_id": 311,
                  "name": "عرض رمضان",
                  "status": "sent",
                  "total": 1200,
                  "sent": 1187,
                  "delivered": 1102,
                  "read": 840,
                  "failed": 13
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "template.status_changed": {
      "post": {
        "operationId": "onTemplateStatusChanged",
        "tags": [
          "Webhooks"
        ],
        "summary": "Meta changed a template's status",
        "description": "Fired when Meta approves, rejects or deletes one of your message templates. A\ntemplate Meta has removed arrives with `status: DELETED`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureLegacy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "template.status_changed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/TemplateStatusChangedData"
                      }
                    }
                  }
                ]
              },
              "example": {
                "event": "template.status_changed",
                "tenant_id": 12,
                "delivery_id": "293a4b5c6d7e8f900112233445566778",
                "ts": 1758327000,
                "data": {
                  "template_id": 77,
                  "name": "order_confirmation",
                  "language": "ar",
                  "category": "UTILITY",
                  "old_status": "PENDING",
                  "status": "APPROVED"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A workspace API key, created under **Developers · API** in the panel. The value\nlooks like `msk_live_` followed by 40 hexadecimal characters and is shown exactly\nonce, at creation. Device tokens issued by the Marsal mobile apps are accepted on\nthe same endpoints.\n"
      }
    },
    "parameters": {
      "ConversationId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The conversation id, as returned by `GET /conversations`.",
        "schema": {
          "type": "integer",
          "minimum": 1
        },
        "example": 8421
      },
      "WebhookEvent": {
        "name": "X-Marsal-Event",
        "in": "header",
        "required": true,
        "description": "The event name, e.g. `message.received`.",
        "schema": {
          "type": "string"
        },
        "example": "message.received"
      },
      "WebhookDelivery": {
        "name": "X-Marsal-Delivery",
        "in": "header",
        "required": true,
        "description": "A 32-character hex id, stable across retries. Deduplicate on it — delivery is\nat-least-once.\n",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{32}$"
        },
        "example": "a1b2c3d4e5f60718293a4b5c6d7e8f90"
      },
      "WebhookAttempt": {
        "name": "X-Marsal-Attempt",
        "in": "header",
        "required": true,
        "description": "`1` on the first try, then `2`, `3`… up to 6 attempts.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 6
        },
        "example": 1
      },
      "WebhookTimestamp": {
        "name": "X-Marsal-Timestamp",
        "in": "header",
        "required": true,
        "description": "Unix seconds at which this attempt was signed. Matches `t` in the V2 signature.",
        "schema": {
          "type": "integer"
        },
        "example": 1758326400
      },
      "WebhookSignatureV2": {
        "name": "X-Marsal-Signature-V2",
        "in": "header",
        "required": true,
        "description": "**Authoritative.** `t=<unix>,v1=<hex>` where `<hex>` is the HMAC-SHA256 of\n`\"<t>.<raw request body>\"` keyed with your webhook secret. Compare in constant\ntime, use the raw bytes exactly as received, and reject anything where\n`abs(now - t)` is more than 300 seconds.\n",
        "schema": {
          "type": "string",
          "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
        },
        "example": "t=1758326400,v1=3f5c0a9e1d7b4c2a8e6f0b3d5a7c9e1f2b4d6a8c0e2f4b6d8a0c2e4f6b8d0a2c"
      },
      "WebhookSignatureLegacy": {
        "name": "X-Marsal-Signature",
        "in": "header",
        "required": true,
        "description": "Legacy. HMAC-SHA256 of the raw body alone, with no timestamp, so it cannot detect\na replay. Still sent so existing integrations keep working — verify **V2** in\nanything new.\n",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        },
        "example": "9d4c1b8a7f6e5d4c3b2a1908f7e6d5c4b3a29180f7e6d5c4b3a291807f6e5d4c"
      }
    },
    "responses": {
      "Unauthenticated": {
        "description": "The bearer token is missing, malformed, revoked or expired.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "ok": false,
              "error": "unauthenticated"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The caller's role or the workspace's plan does not unlock this feature. `feature`\nnames the capability that was refused.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ForbiddenError"
            },
            "example": {
              "ok": false,
              "error": "forbidden",
              "feature": "templates"
            }
          }
        }
      },
      "ConversationNotFound": {
        "description": "No conversation with that id in this workspace.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "ok": false,
              "error": "conversation_not_found"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many calls. Applies to mobile device tokens, which are capped at 300 calls per\nminute; `Retry-After` carries the seconds left in the current minute.\n",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the counter resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "ok": false,
              "error": "rate_limited"
            }
          }
        }
      }
    },
    "schemas": {
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "ok",
          "error"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "A stable machine-readable error code.",
            "examples": [
              "unauthenticated",
              "to_required",
              "session_closed"
            ]
          }
        }
      },
      "ForbiddenError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "feature": {
                "type": "string",
                "description": "The capability the caller's role or plan does not unlock."
              }
            }
          }
        ]
      },
      "SendFailedError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "detail": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Meta's own error text, passed through unchanged."
              },
              "message_id": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The row Marsal stored for the failed attempt, when one was stored."
              }
            }
          }
        ]
      },
      "PhoneNumber": {
        "type": "string",
        "description": "International format, digits only. Non-digits are stripped by the server.",
        "pattern": "^[0-9]{6,20}$",
        "examples": [
          "966550521672",
          "201001234567"
        ]
      },
      "Timestamp": {
        "type": "string",
        "description": "Server-local `YYYY-MM-DD HH:MM:SS`.",
        "examples": [
          "2026-09-20 14:31:08"
        ]
      },
      "SendTextRequest": {
        "type": "object",
        "required": [
          "to",
          "body"
        ],
        "properties": {
          "to": {
            "$ref": "#/components/schemas/PhoneNumber"
          },
          "type": {
            "type": "string",
            "const": "text",
            "default": "text",
            "description": "Omit it or send `text`."
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "description": "The message text."
          },
          "name": {
            "type": "string",
            "description": "Display name to store on the contact when this number is new to the workspace.\n"
          }
        }
      },
      "SendTemplateRequest": {
        "type": "object",
        "required": [
          "to",
          "type",
          "template"
        ],
        "properties": {
          "to": {
            "$ref": "#/components/schemas/PhoneNumber"
          },
          "type": {
            "type": "string",
            "const": "template"
          },
          "template": {
            "type": "string",
            "minLength": 1,
            "description": "The approved template name, exactly as Meta has it.",
            "examples": [
              "order_confirmation"
            ]
          },
          "language": {
            "type": "string",
            "default": "ar",
            "description": "The template's language code.",
            "examples": [
              "ar",
              "en",
              "en_US"
            ]
          },
          "params": {
            "type": "array",
            "description": "Body variables in order — one entry per `{{n}}` placeholder. Each is coerced\nto a string and sent as a text parameter.\n",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "أحمد",
                "#9402"
              ]
            ]
          },
          "components": {
            "type": "array",
            "description": "A raw Meta `components` array, forwarded unchanged. Use it for header, button\nor media parameters. Only read when `params` is absent or empty.\n",
            "items": {
              "type": "object"
            }
          },
          "name": {
            "type": "string",
            "description": "Display name to store on the contact when this number is new."
          }
        }
      },
      "SendMessageResponse": {
        "type": "object",
        "required": [
          "ok",
          "message_id",
          "to"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "message_id": {
            "type": "integer",
            "description": "The id of the message row stored in Marsal."
          },
          "wamid": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta's own message id, when it returned one."
          },
          "to": {
            "type": "string",
            "description": "The normalised recipient, digits only."
          }
        }
      },
      "OtpSendRequest": {
        "type": "object",
        "required": [
          "to"
        ],
        "properties": {
          "to": {
            "$ref": "#/components/schemas/PhoneNumber"
          },
          "code": {
            "type": "string",
            "description": "Your own code. Digits only; anything else is stripped. Omit it and Marsal\ngenerates a six-digit code. Only its SHA-256 hash is stored.\n",
            "examples": [
              "418302"
            ]
          },
          "ttl": {
            "type": "integer",
            "default": 300,
            "minimum": 60,
            "maximum": 3600,
            "description": "Lifetime in seconds. Values outside the range are clamped."
          },
          "template": {
            "type": "string",
            "description": "An approved template name to deliver the code with — required to reach a\ncustomer outside the 24-hour window. The code is passed as the template's\nsingle body parameter. Omit it to send plain text instead.\n",
            "examples": [
              "otp_code"
            ]
          },
          "language": {
            "type": "string",
            "default": "ar",
            "description": "The template's language code. Ignored when `template` is omitted."
          }
        }
      },
      "OtpSendResponse": {
        "type": "object",
        "required": [
          "ok",
          "to",
          "expires_in"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "to": {
            "type": "string"
          },
          "expires_in": {
            "type": "integer",
            "description": "The TTL actually applied, in seconds."
          },
          "message_id": {
            "type": "integer",
            "description": "The id of the message row stored in Marsal."
          }
        }
      },
      "OtpVerifyRequest": {
        "type": "object",
        "required": [
          "to",
          "code"
        ],
        "properties": {
          "to": {
            "$ref": "#/components/schemas/PhoneNumber"
          },
          "code": {
            "type": "string",
            "description": "The code the customer entered. Digits only; anything else is stripped.",
            "examples": [
              "418302"
            ]
          }
        }
      },
      "OtpVerifyResponse": {
        "type": "object",
        "required": [
          "ok",
          "verified"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "verified": {
            "type": "boolean",
            "description": "`true` only when the code matched a live, unused code for that number. Wrong,\nexpired, already-used and unknown codes all return `false`.\n"
          }
        }
      },
      "Conversation": {
        "type": "object",
        "required": [
          "id",
          "contact_id",
          "status",
          "unread",
          "session_open"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "contact_id": {
            "type": "integer"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact's display name, when known."
          },
          "wa_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact's WhatsApp number, digits only."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "pending",
              "closed"
            ]
          },
          "unread": {
            "type": "integer",
            "minimum": 0
          },
          "last_message_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_inbound_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "session_open": {
            "type": "boolean",
            "description": "True while the customer's last inbound message is less than 24 hours old."
          },
          "preview": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last message's body, truncated to 120 characters."
          }
        }
      },
      "ConversationListResponse": {
        "type": "object",
        "required": [
          "ok",
          "conversations"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "conversations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Conversation"
            }
          }
        }
      },
      "MessageReaction": {
        "type": "object",
        "required": [
          "emoji",
          "mine"
        ],
        "properties": {
          "emoji": {
            "type": "string"
          },
          "mine": {
            "type": "boolean",
            "description": "True when your workspace sent the reaction, false when the customer did."
          }
        }
      },
      "MessageQuote": {
        "type": "object",
        "required": [
          "id",
          "direction",
          "type"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "direction": {
            "type": "string",
            "enum": [
              "in",
              "out"
            ]
          },
          "type": {
            "type": "string"
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Truncated to 140 characters."
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "id",
          "direction",
          "type",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "direction": {
            "type": "string",
            "enum": [
              "in",
              "out"
            ]
          },
          "type": {
            "type": "string",
            "description": "The WhatsApp message type.",
            "examples": [
              "text",
              "image",
              "audio",
              "video",
              "document",
              "template",
              "location",
              "button",
              "interactive"
            ]
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "The text or caption. Empty for media that arrived without a caption.\n"
          },
          "media_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Outbound only. `null` on inbound messages.",
            "enum": [
              "pending",
              "sent",
              "delivered",
              "read",
              "failed",
              null
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "reply_to": {
            "$ref": "#/components/schemas/MessageQuote",
            "description": "The quoted message, when this one is a WhatsApp reply."
          },
          "reactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MessageReaction"
            }
          }
        }
      },
      "MessageListResponse": {
        "type": "object",
        "required": [
          "ok",
          "session_open",
          "ai_enabled",
          "messages"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "session_open": {
            "type": "boolean"
          },
          "ai_enabled": {
            "type": "boolean",
            "description": "Whether the AI bot is currently answering in this chat."
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          }
        }
      },
      "ReplyRequest": {
        "type": "object",
        "required": [
          "body"
        ],
        "properties": {
          "body": {
            "type": "string",
            "minLength": 1
          },
          "reply_to": {
            "type": "integer",
            "description": "Quote a message from this same thread (a WhatsApp reply). An id that is not in\nthis conversation is ignored.\n"
          }
        }
      },
      "ReplyResponse": {
        "type": "object",
        "required": [
          "ok",
          "message_id",
          "status"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "message_id": {
            "type": "integer"
          },
          "wamid": {
            "type": "null",
            "description": "Always null here — the send happens after the response is flushed."
          },
          "status": {
            "type": "string",
            "const": "pending"
          }
        }
      },
      "AiToggleRequest": {
        "type": "object",
        "properties": {
          "enabled": {
            "description": "Truthy turns the bot on, falsy off. Accepts `1`/`0`, `true`/`false` and the\nstrings `\"1\"`, `\"true\"`, `\"on\"`, `\"yes\"`. Omitting it turns the bot on.\n",
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "integer",
                "enum": [
                  0,
                  1
                ]
              },
              {
                "type": "string"
              }
            ]
          }
        }
      },
      "AiToggleResponse": {
        "type": "object",
        "required": [
          "ok",
          "ai_enabled"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "ai_enabled": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          }
        }
      },
      "Tag": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Contact": {
        "type": "object",
        "required": [
          "id",
          "wa_id"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "wa_id": {
            "type": "string",
            "description": "The WhatsApp number, digits only."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "opted_in": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`1` when the contact may receive broadcasts, `0` when they opted out."
          },
          "last_inbound_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tag"
            }
          }
        }
      },
      "ContactListResponse": {
        "type": "object",
        "required": [
          "ok",
          "tags",
          "contacts"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "tags": {
            "type": "array",
            "description": "Every tag defined in the workspace, not only the ones on this page.",
            "items": {
              "$ref": "#/components/schemas/Tag"
            }
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contact"
            }
          }
        }
      },
      "CreateContactRequest": {
        "type": "object",
        "required": [
          "wa_id"
        ],
        "properties": {
          "wa_id": {
            "$ref": "#/components/schemas/PhoneNumber"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "CreateContactResponse": {
        "type": "object",
        "required": [
          "ok",
          "contact_id"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "contact_id": {
            "type": "integer"
          }
        }
      },
      "Template": {
        "type": "object",
        "required": [
          "id",
          "name",
          "language",
          "status"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "examples": [
              "MARKETING",
              "UTILITY",
              "AUTHENTICATION"
            ]
          },
          "status": {
            "type": "string",
            "description": "Meta's own status.",
            "examples": [
              "APPROVED",
              "PENDING",
              "REJECTED",
              "DELETED"
            ]
          },
          "body": {
            "type": "string",
            "description": "The body text, with `{{n}}` placeholders left in."
          },
          "header": {
            "type": "string",
            "description": "The text header, or an empty string when there is none or it is media."
          },
          "footer": {
            "type": "string"
          },
          "buttons": {
            "type": "array",
            "description": "Button labels, in order.",
            "items": {
              "type": "string"
            }
          },
          "vars": {
            "type": "integer",
            "description": "How many distinct `{{n}}` placeholders the body has."
          },
          "wa_number_id": {
            "type": "integer"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "TemplateListResponse": {
        "type": "object",
        "required": [
          "ok",
          "templates"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "can_manage": {
            "type": "boolean",
            "description": "Whether this caller may create or delete templates. Always false for an API key."
          },
          "templates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Template"
            }
          }
        }
      },
      "WhatsAppNumber": {
        "type": "object",
        "required": [
          "id",
          "display_number",
          "status"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "display_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "verified_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "connected",
              "pending",
              "disconnected"
            ]
          },
          "quality_rating": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "GREEN",
              "YELLOW",
              "RED"
            ]
          },
          "waba_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone_number_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "NumberListResponse": {
        "type": "object",
        "required": [
          "ok",
          "numbers"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "Marsal's own inbound callback registered with Meta, for support reference."
          },
          "numbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WhatsAppNumber"
            }
          }
        }
      },
      "Campaign": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "examples": [
              "draft",
              "scheduled",
              "sending",
              "sent",
              "paused",
              "cancelled"
            ]
          },
          "total": {
            "type": "integer"
          },
          "sent": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "read": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "template_id": {
            "type": "integer"
          },
          "template_name": {
            "type": "string"
          },
          "segment_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "segment_name": {
            "type": "string"
          },
          "wa_number_id": {
            "type": "integer"
          }
        }
      },
      "CampaignListResponse": {
        "type": "object",
        "required": [
          "ok",
          "items"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "can_manage": {
            "type": "boolean"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Campaign"
            }
          }
        }
      },
      "DashboardResponse": {
        "type": "object",
        "required": [
          "ok",
          "stats"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "stats": {
            "type": "object",
            "required": [
              "contacts",
              "open_conversations",
              "unread",
              "messages_today",
              "campaigns"
            ],
            "properties": {
              "contacts": {
                "type": "integer"
              },
              "open_conversations": {
                "type": "integer"
              },
              "unread": {
                "type": "integer"
              },
              "messages_today": {
                "type": "integer"
              },
              "campaigns": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Update": {
        "type": "object",
        "required": [
          "id",
          "conversation_id",
          "type",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "conversation_id": {
            "type": "integer"
          },
          "from": {
            "type": "string",
            "description": "The contact's name, or `+` followed by their number when unnamed."
          },
          "type": {
            "type": "string"
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Text truncated to 200 characters. For any other type, a placeholder such as\n`[image]`.\n"
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "UpdatesResponse": {
        "type": "object",
        "required": [
          "ok",
          "last_id",
          "messages"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "last_id": {
            "type": "integer",
            "description": "The newest inbound message id in the workspace. Pass it as the next `since_id`."
          },
          "unread": {
            "type": "integer",
            "description": "Total unread across all conversations."
          },
          "messages": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/Update"
            }
          }
        }
      },
      "WebhookEnvelope": {
        "type": "object",
        "required": [
          "event",
          "tenant_id",
          "delivery_id",
          "ts",
          "data"
        ],
        "description": "Every webhook uses this wrapper; only `data` changes per event.\n\nA payload larger than 256 KB is replaced by its scalar fields plus\n`\"_truncated\": true` inside `data`, rather than dropped — fetch the detail over\nthe REST API when you see that flag.\n",
        "properties": {
          "event": {
            "type": "string",
            "description": "The event name. Matches the `X-Marsal-Event` header."
          },
          "tenant_id": {
            "type": "integer",
            "description": "Your workspace id."
          },
          "delivery_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{32}$",
            "description": "Stable across retries. Matches the `X-Marsal-Delivery` header."
          },
          "ts": {
            "type": "integer",
            "description": "Unix seconds at which the event was queued. Use it for ordering — delivery\norder is not guaranteed.\n"
          },
          "data": {
            "type": "object"
          }
        }
      },
      "MessageReceivedData": {
        "type": "object",
        "required": [
          "conversation_id",
          "contact",
          "type"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer"
          },
          "human_reply_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unix seconds of the last outbound message in this chat that a human agent sent,\nor null if no human has ever replied.\n"
          },
          "contact": {
            "type": "object",
            "required": [
              "wa_id"
            ],
            "properties": {
              "wa_id": {
                "type": "string",
                "description": "The sender's WhatsApp number, digits only."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The WhatsApp profile name, when WhatsApp supplied one."
              }
            }
          },
          "type": {
            "type": "string",
            "examples": [
              "text",
              "image",
              "audio",
              "video",
              "document",
              "location",
              "button",
              "interactive"
            ]
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "description": "The text or caption, when the message carries one."
          }
        }
      },
      "MessageSentData": {
        "type": "object",
        "required": [
          "message_id",
          "conversation_id",
          "contact_id",
          "type",
          "status"
        ],
        "properties": {
          "message_id": {
            "type": "integer"
          },
          "conversation_id": {
            "type": "integer"
          },
          "contact_id": {
            "type": "integer"
          },
          "type": {
            "type": "string"
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "description": "Truncated to 4096 characters."
          },
          "media_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "media_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "wa_message_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "failed"
            ],
            "description": "The terminal state at the moment of firing; `pending` never fires."
          },
          "sender_user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The agent who sent it, or null for the bot, the API or automation."
          },
          "template_id": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "MessageStatusData": {
        "type": "object",
        "required": [
          "wa_message_id",
          "status"
        ],
        "properties": {
          "wa_message_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "delivered",
              "read",
              "played",
              "failed"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta's error text when `status` is `failed`."
          },
          "message_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "conversation_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "campaign_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Set when the message was a campaign recipient."
          },
          "recipient_wa_id": {
            "type": "string"
          }
        }
      },
      "ConversationAssignedData": {
        "type": "object",
        "required": [
          "conversation_id"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer"
          },
          "from_user_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "to_user_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reason": {
            "type": "string",
            "description": "Why the assignment changed. Truncated to 40 characters.",
            "examples": [
              "manual",
              "auto",
              "round_robin",
              "release"
            ]
          },
          "contact_id": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "ConversationClosedData": {
        "type": "object",
        "required": [
          "conversation_id"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer"
          },
          "contact_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "assigned_user_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "examples": [
              "inbox",
              "api",
              "csat"
            ]
          }
        }
      },
      "CsatReceivedData": {
        "type": "object",
        "required": [
          "conversation_id",
          "rating"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer"
          },
          "contact_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          }
        }
      },
      "ContactCreatedData": {
        "type": "object",
        "required": [
          "contact_id",
          "wa_id"
        ],
        "properties": {
          "contact_id": {
            "type": "integer"
          },
          "wa_id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "examples": [
              "inbound",
              "manual",
              "api",
              "import"
            ]
          }
        }
      },
      "CampaignFinishedData": {
        "type": "object",
        "required": [
          "campaign_id",
          "status"
        ],
        "properties": {
          "campaign_id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "total": {
            "type": "integer",
            "description": "Recipients enrolled in the campaign."
          },
          "sent": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "read": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          }
        }
      },
      "TemplateStatusChangedData": {
        "type": "object",
        "required": [
          "template_id",
          "name",
          "status"
        ],
        "properties": {
          "template_id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "old_status": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "examples": [
              "APPROVED",
              "REJECTED",
              "DELETED"
            ]
          }
        }
      }
    }
  }
}
