{"info":{"title":"Warid API","description":"Read and answer your inbox's WhatsApp, Instagram and Messenger conversations from your own code, and get events pushed to your server as they happen.\n\n## Authentication\n\nCreate a key in **Developers → API keys**. Send it as a Bearer token:\n\n```bash\ncurl https://YOUR_API_HOST/api/v1/conversations \\\n  -H \"Authorization: Bearer mik_...\"\n```\n\nA key belongs to one workspace. Keys with the `read` scope can list and fetch; the `write` scope is needed to send messages and verification codes, change conversations and contacts, manage WhatsApp templates, train the AI agent, and manage webhooks. Keys are shown once, so store them as a secret.\n\nBuilding a product that other businesses connect to? Use **Connect with Warid** below instead of asking them for a key.\n\n## Send your first message\n\nReply in a conversation:\n\n```bash\ncurl -X POST https://YOUR_API_HOST/api/v1/conversations/CONVERSATION_ID/messages \\\n  -H \"Authorization: Bearer mik_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\": \"Your order has shipped!\"}'\n```\n\nOr message a contact on any channel (`contactId`), or a phone number on a WhatsApp channel (find channel ids with `GET /api/v1/channels`). WhatsApp only takes a template as the first message:\n\n```bash\ncurl -X POST https://YOUR_API_HOST/api/v1/messages \\\n  -H \"Authorization: Bearer mik_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"channelId\": \"CHANNEL_ID\", \"to\": \"+9647701234567\", \"template\": {\"name\": \"order_ready\", \"language\": \"en_US\", \"params\": [\"Sara\", \"#1042\"]}}'\n```\n\nThe response is the stored message. Its `status` is `sent` when the channel accepted it, or `failed` with an `error`. Text and images only go out within 24 hours of the customer's last message; otherwise you get `422 window_closed` and, on WhatsApp, send a template instead. Messages sent through the API don't pause the AI agent; set `aiEnabled: false` on the conversation to do that.\n\n**Templates and login codes.** Create and list WhatsApp templates under `/api/v1/channels/{channelId}/templates`, and send one-time codes with `POST /api/v1/otp` and `POST /api/v1/otp/verify`. The \"Send messages\" guide in the developer docs walks through both.\n\n## Connect with Warid (OAuth)\n\nLet your users connect their Warid workspace to your app with one button, then call this API on their behalf. It is standard OAuth 2.0 (authorization code with PKCE), and it's free to build on.\n\n1. **Create an app** in the developer console (`/console` on the web app). You get a `client_id` (`mci_...`) and a `client_secret` (`mcs_...`, shown once). Add your redirect URIs (HTTPS; `http://localhost` works for development) and the scopes your app needs.\n2. **Send the user to the consent screen**, in a popup or a full redirect. Generate a random `state` and a PKCE `code_verifier` on your server and keep them in the user's session:\n\n```\nhttps://YOUR_WEB_HOST/oauth/authorize\n  ?response_type=code\n  &client_id=mci_...\n  &redirect_uri=https://yourapp.com/warid/callback\n  &scope=conversations:read conversations:write agent:write\n  &state=RANDOM_STATE\n  &code_challenge=BASE64URL(SHA256(code_verifier))\n  &code_challenge_method=S256\n```\n\nThe user signs in (or signs up), picks the workspace and approves. We redirect back to your `redirect_uri` with `?code=...&state=...` (or `?error=access_denied`).\n\n3. **Exchange the code** from your server. Codes work once, for 60 seconds:\n\n```bash\ncurl -X POST https://YOUR_API_HOST/oauth/token \\\n  -u \"mci_...:mcs_...\" \\\n  -d grant_type=authorization_code \\\n  -d code=mac_... \\\n  -d redirect_uri=https://yourapp.com/warid/callback \\\n  -d code_verifier=THE_VERIFIER\n```\n\n```json\n{ \"access_token\": \"mat_...\", \"token_type\": \"Bearer\", \"expires_in\": 3600, \"refresh_token\": \"mrt_...\", \"scope\": \"conversations:read conversations:write agent:write\", \"workspace_id\": \"...\" }\n```\n\n4. **Call the API** with `Authorization: Bearer mat_...`, exactly like a key. Access tokens last an hour; get a new pair with `grant_type=refresh_token&refresh_token=mrt_...`. Refresh tokens rotate: each one works once, and reusing an old one revokes the connection's tokens. `POST /oauth/revoke` with `token=...` disconnects.\n\n**One app per workspace.** A workspace connects one app at a time, because each app brings its own agent instructions. Connecting another app disconnects the previous one (the user confirms first). The workspace sees your app, its permissions and an activity log of what it did, and can disconnect it at any time; your tokens then return `401`. Webhook endpoints your app created are removed at the same time.\n\n**Scopes**\n\n| Scope | Allows |\n|---|---|\n| `conversations:read` | List conversations and messages |\n| `conversations:write` | Send messages and notes; change status, assignee, tags and the AI switch |\n| `contacts:read` / `contacts:write` | Read / rename contacts |\n| `channels:read` | List connected channels |\n| `agent:read` | Read the AI agent's settings and knowledge; try the agent |\n| `agent:write` | Change the agent's instructions and model; add and remove knowledge |\n| `webhooks:read` / `webhooks:write` | List / manage webhook endpoints |\n| `templates:read` / `templates:write` | List / create and delete WhatsApp message templates |\n| `otp:send` | Send verification codes on WhatsApp and check them |\n\n**Going live.** A new app is in development: it only connects to workspaces you belong to. Submit it for review in the console; once approved, anyone can connect it.\n\n**Partner program.** Apply from the Partner page in the dashboard. Once approved, workspaces created through your app (connected within a day of signing up) count as yours. You earn the share set when you were approved, up to 20%, of what they pay for credit in their first 12 months, and they get 30% off credit for their first 3 months. Payouts can be requested once 5 of them have paid.\n\nServer metadata (RFC 8414) is at `/.well-known/oauth-authorization-server`.\n\n## Train the AI agent\n\nWith `agent:write`, set the agent's instructions and add knowledge it answers from:\n\n```bash\ncurl -X PATCH https://YOUR_API_HOST/api/v1/agent \\\n  -H \"Authorization: Bearer mat_...\" -H \"Content-Type: application/json\" \\\n  -d '{\"systemPrompt\": \"You are the booking assistant for a dental clinic...\", \"enabled\": true}'\n\ncurl -X POST https://YOUR_API_HOST/api/v1/knowledge \\\n  -H \"Authorization: Bearer mat_...\" -H \"Content-Type: application/json\" \\\n  -d '{\"kind\": \"qa\", \"question\": \"Are you open on Fridays?\", \"answer\": \"Yes, 4pm to 10pm.\"}'\n```\n\nKnowledge is indexed in the background; poll `GET /api/v1/knowledge/{id}` until `status` is `ready`. `POST /api/v1/agent/test` runs the agent on a sample chat without messaging anyone.\n\n## Use it from AI assistants (MCP)\n\nWarid is also a [Model Context Protocol](https://modelcontextprotocol.io) server, so Claude, ChatGPT, Cursor and other assistants can read and answer conversations and train the AI agent. Add this address as a custom connector:\n\n```\nhttps://YOUR_API_HOST/mcp\n```\n\nThe assistant opens the Warid sign-in, you pick the workspace and approve, and you're done. Assistants register themselves (RFC 7591 dynamic client registration) and sign in with the same OAuth server as apps, using PKCE. They don't take the workspace's one connected-app slot: they're listed under **Developers → AI assistants**, where each can be disconnected. Clients that can't sign in can send a workspace key instead: `Authorization: Bearer mik_...`.\n\nThe server speaks Streamable HTTP without sessions: `POST /mcp` with one JSON-RPC message, answered with JSON. Its tools map one to one onto this API (`list_conversations`, `get_messages`, `send_message`, `update_conversation`, `list_contacts`, `get_agent`, `update_agent`, `test_agent`, `add_knowledge`, and more), with the same scopes and rate limits. Metadata is at `/.well-known/oauth-protected-resource`.\n\n## Pagination\n\nList endpoints return `{ \"items\": [...], \"nextCursor\": \"...\" }`. Pass `nextCursor` as `before` to get the next page; it is `null` on the last page. `limit` is 1–100.\n\n## Errors\n\nErrors are JSON with a machine-readable code: `{ \"error\": \"not_found\" }`. Common ones:\n\n| Status | error | Meaning |\n|---|---|---|\n| 401 | `invalid_api_key` | The key is missing, wrong or was revoked. |\n| 403 | `insufficient_scope` | The key or token lacks the scope named in `scope`. |\n| 404 | `not_found` | No such object in this workspace. |\n| 422 | (varies) | The request body or query is invalid. |\n| 429 | `rate_limited` | Too many requests; wait `Retry-After` seconds. |\n\n## Rate limits\n\nEach key, and each app connection, can make 120 requests a minute. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds until the window resets).","version":"1.0.0"},"tags":[{"name":"Workspace"},{"name":"Dashboard"},{"name":"Channels"},{"name":"Contacts"},{"name":"Conversations"},{"name":"Messages"},{"name":"Templates"},{"name":"Campaigns"},{"name":"Verification codes"},{"name":"AI agent"},{"name":"Knowledge"},{"name":"Webhooks","description":"Webhooks push events to an HTTPS URL of yours as they happen. Add endpoints in **Developers → Webhooks** or through this API.\n\n**Events**\n\n- `message.created`: a customer wrote, or a message was sent (by a teammate, the AI agent, the API, or from the phone). Check `message.direction` (`inbound` / `outbound`) and `message.sender` (`contact`, `user`, `ai`, `api`, `system`).\n- `message.updated`: a message's delivery `status` changed (`sent`, `delivered`, `read`, `failed`).\n- `conversation.updated`: status, assignee, tags, AI switch or last message changed.\n- `channel.updated`: a channel connected, disconnected or needs reconnecting.\n- `template.updated`: Meta approved, rejected or paused a WhatsApp template. `data.template` has its `name`, `language`, `status` and `reason`.\n- `campaign.updated`: a campaign started sending, paused for credit, finished, was canceled or stopped early. `data.campaign` has its `status` and `stats`.\n- `conversation.handoff`: the AI agent passed a conversation to the team. `data.reason` is the agent's reason, `data.conversation` the conversation (its AI switch is now off).\n- `knowledge.updated`: a knowledge source finished indexing. `data.source.status` is `ready` or `failed` (with `error`).\n\nEach delivery is a POST with a JSON body:\n\n```json\n{\n  \"id\": \"evt_8f2c...\",\n  \"type\": \"message.created\",\n  \"createdAt\": \"2026-10-14T12:00:00.000Z\",\n  \"workspaceId\": \"...\",\n  \"data\": { \"message\": { \"id\": \"...\", \"conversationId\": \"...\", \"direction\": \"inbound\", \"body\": \"Hi!\" } }\n}\n```\n\n**Verifying signatures**\n\nEvery delivery has an `X-Inbox-Signature` header: `t=<unix seconds>,v1=<hex>`, where `v1` is the HMAC-SHA256 of `<t>.<raw body>` keyed with the endpoint's secret (`whsec_...`). Compute it over the raw request body, compare in constant time, and reject timestamps older than five minutes.\n\n```js\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nfunction verify(rawBody, header, secret) {\n  const { t, v1 } = Object.fromEntries(header.split(\",\").map((p) => p.split(\"=\")));\n  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;\n  const expected = createHmac(\"sha256\", secret).update(`${t}.${rawBody}`).digest(\"hex\");\n  return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));\n}\n```\n\n```python\nimport hmac, hashlib, time\n\ndef verify(raw_body: bytes, header: str, secret: str) -> bool:\n    parts = dict(p.split(\"=\", 1) for p in header.split(\",\"))\n    if abs(time.time() - int(parts[\"t\"])) > 300:\n        return False\n    expected = hmac.new(secret.encode(), f\"{parts['t']}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(parts[\"v1\"], expected)\n```\n\n**Retries**\n\nAnswer with any 2xx within 10 seconds. Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 8 hours, then given up. An endpoint whose deliveries are given up 20 times in a row is switched off; turn it back on from the dashboard. Deliveries can arrive more than once and out of order, so use the event `id` to skip duplicates and the objects' timestamps to order them. `X-Inbox-Event` and `X-Inbox-Delivery` headers carry the event type and delivery id."}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"A workspace API key (`mik_...`)."},"oauth2":{"type":"oauth2","description":"An access token (`mat_...`) from Connect with Warid.","flows":{"authorizationCode":{"authorizationUrl":"https://warid.chat/oauth/authorize","tokenUrl":"https://api-production-3dde.up.railway.app/oauth/token","refreshUrl":"https://api-production-3dde.up.railway.app/oauth/token","scopes":{"conversations:read":"conversations:read","conversations:write":"conversations:write","contacts:read":"contacts:read","contacts:write":"contacts:write","channels:read":"channels:read","agent:read":"agent:read","agent:write":"agent:write","webhooks:read":"webhooks:read","webhooks:write":"webhooks:write","templates:read":"templates:read","templates:write":"templates:write","otp:send":"otp:send","campaigns:read":"campaigns:read","campaigns:write":"campaigns:write","dashboard:open":"dashboard:open"}}}}},"schemas":{}},"openapi":"3.1.2","paths":{"/api/v1/sign-in-links":{"post":{"tags":["Dashboard"],"operationId":"postApiV1SignInLinks","summary":"Create a sign-in link","description":"A link that opens the Warid dashboard already signed in as the person who connected your app. It works once and expires after two minutes, so create one each time the user clicks, then redirect them to `url`. Connected apps only. Scope: `dashboard:open`.","security":[{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"maxLength":60,"description":"The dashboard page to open: `/inbox` (default), `/inbox/{conversationId}`, `/agent`, `/channels`, `/team` or `/developers`.","type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"path":{"maxLength":60,"description":"The dashboard page to open: `/inbox` (default), `/inbox/{conversationId}`, `/agent`, `/channels`, `/team` or `/developers`.","type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"path":{"maxLength":60,"description":"The dashboard page to open: `/inbox` (default), `/inbox/{conversationId}`, `/agent`, `/channels`, `/team` or `/developers`.","type":"string"}}}}}}}},"/api/v1/workspace":{"get":{"tags":["Workspace"],"summary":"Get the current workspace","description":"The workspace this key or access token belongs to, and the scopes it carries.","security":[{"apiKey":[]},{"oauth2":[]}],"operationId":"getApiV1Workspace"}},"/api/v1/channels":{"get":{"tags":["Channels"],"summary":"List channels","description":"The WhatsApp numbers, Instagram accounts and Facebook Pages connected to the workspace. Scope: `channels:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"operationId":"getApiV1Channels"}},"/api/v1/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts","description":"Newest first. Scope: `contacts:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"search","in":"query","required":false,"schema":{"maxLength":100,"description":"Matches the contact's name.","type":"string"}},{"name":"before","in":"query","required":false,"schema":{"format":"date-time","description":"Cursor: the `nextCursor` from the previous page.","type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"minimum":1,"maximum":100,"description":"Page size, 1–100.","anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":1,"maximum":100,"description":"Page size, 1–100.","type":"number"}]}}],"operationId":"getApiV1Contacts"}},"/api/v1/contacts/{contactId}":{"get":{"tags":["Contacts"],"summary":"Get a contact","description":"Scope: `contacts:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"contactId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"getApiV1ContactsByContactId"},"patch":{"tags":["Contacts"],"summary":"Update a contact","description":"Rename a contact, or opt them out of (or back into) campaigns. Scope: `contacts:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"contactId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"nullable":true,"maxLength":120,"type":["string","null"]},"optedOut":{"description":"True stops campaigns for this customer; false allows them again.","type":"boolean"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"name":{"nullable":true,"maxLength":120,"type":["string","null"]},"optedOut":{"description":"True stops campaigns for this customer; false allows them again.","type":"boolean"}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"name":{"nullable":true,"maxLength":120,"type":["string","null"]},"optedOut":{"description":"True stops campaigns for this customer; false allows them again.","type":"boolean"}}}}}},"operationId":"patchApiV1ContactsByContactId"}},"/api/v1/conversations":{"get":{"tags":["Conversations"],"summary":"List conversations","description":"Most recent activity first. Scope: `conversations:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["open","pending","closed"]}},{"name":"channelId","in":"query","required":false,"schema":{"format":"uuid","type":"string"}},{"name":"contactId","in":"query","required":false,"schema":{"format":"uuid","type":"string"}},{"name":"before","in":"query","required":false,"schema":{"format":"date-time","description":"Cursor: the `nextCursor` from the previous page.","type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"minimum":1,"maximum":100,"description":"Page size, 1–100.","anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":1,"maximum":100,"description":"Page size, 1–100.","type":"number"}]}}],"operationId":"getApiV1Conversations"}},"/api/v1/conversations/{conversationId}":{"get":{"tags":["Conversations"],"summary":"Get a conversation","description":"Scope: `conversations:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"getApiV1ConversationsByConversationId"},"patch":{"tags":["Conversations"],"summary":"Update a conversation","description":"Change status, assignee, tags or the AI switch. Scope: `conversations:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["open","pending","closed"]},"assigneeId":{"nullable":true,"format":"uuid","type":["string","null"]},"aiEnabled":{"description":"Turn the AI agent on or off for this conversation.","type":"boolean"},"tags":{"maxItems":20,"description":"Replaces the conversation's tags.","type":"array","items":{"maxLength":40,"type":"string"}}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["open","pending","closed"]},"assigneeId":{"nullable":true,"format":"uuid","type":["string","null"]},"aiEnabled":{"description":"Turn the AI agent on or off for this conversation.","type":"boolean"},"tags":{"maxItems":20,"description":"Replaces the conversation's tags.","type":"array","items":{"maxLength":40,"type":"string"}}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["open","pending","closed"]},"assigneeId":{"nullable":true,"format":"uuid","type":["string","null"]},"aiEnabled":{"description":"Turn the AI agent on or off for this conversation.","type":"boolean"},"tags":{"maxItems":20,"description":"Replaces the conversation's tags.","type":"array","items":{"maxLength":40,"type":"string"}}}}}}},"operationId":"patchApiV1ConversationsByConversationId"}},"/api/v1/conversations/{conversationId}/messages":{"get":{"tags":["Messages"],"summary":"List messages","description":"Pages go back in time; the messages within a page are oldest first. Internal notes are included with `isNote: true`. Scope: `conversations:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}},{"name":"before","in":"query","required":false,"schema":{"format":"date-time","description":"Cursor: the `nextCursor` from the previous page.","type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"minimum":1,"maximum":100,"description":"Page size, 1–100.","anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":1,"maximum":100,"description":"Page size, 1–100.","type":"number"}]}}],"operationId":"getApiV1ConversationsByConversationIdMessages"},"post":{"tags":["Messages"],"summary":"Send a message","description":"Replies in a conversation with text, an image, or both. Works on WhatsApp, Instagram and Messenger while the 24-hour window is open (the customer wrote in the last 24 hours); otherwise it returns `422 window_closed`, and on WhatsApp you send a template instead. The response is the stored message: `status` is `sent`, or `failed` with an `error` if the channel refused it. Scope: `conversations:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"minLength":1,"maxLength":4096,"description":"The message, or the image's caption.","type":"string"},"imageUrl":{"maxLength":2000,"description":"A public JPEG or PNG link (up to 5 MB) to send as an image.","type":"string"},"note":{"description":"Add an internal note the customer doesn't see, instead of sending.","type":"boolean"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"text":{"minLength":1,"maxLength":4096,"description":"The message, or the image's caption.","type":"string"},"imageUrl":{"maxLength":2000,"description":"A public JPEG or PNG link (up to 5 MB) to send as an image.","type":"string"},"note":{"description":"Add an internal note the customer doesn't see, instead of sending.","type":"boolean"}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"text":{"minLength":1,"maxLength":4096,"description":"The message, or the image's caption.","type":"string"},"imageUrl":{"maxLength":2000,"description":"A public JPEG or PNG link (up to 5 MB) to send as an image.","type":"string"},"note":{"description":"Add an internal note the customer doesn't see, instead of sending.","type":"boolean"}}}}}},"operationId":"postApiV1ConversationsByConversationIdMessages"}},"/api/v1/conversations/{conversationId}/template":{"post":{"tags":["Messages"],"summary":"Send a WhatsApp template","description":"Sends an approved WhatsApp template, whether or not the 24-hour window is open. List them with `GET /channels/{channelId}/templates`. Scope: `conversations:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"description":"The template's name.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"The template's language code, e.g. `en_US` or `ar`.","type":"string"},"params":{"maxItems":20,"description":"A value for each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"maxLength":1024,"type":"string"}}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"description":"The template's name.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"The template's language code, e.g. `en_US` or `ar`.","type":"string"},"params":{"maxItems":20,"description":"A value for each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"maxLength":1024,"type":"string"}}}}},"multipart/form-data":{"schema":{"type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"description":"The template's name.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"The template's language code, e.g. `en_US` or `ar`.","type":"string"},"params":{"maxItems":20,"description":"A value for each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"maxLength":1024,"type":"string"}}}}}}},"operationId":"postApiV1ConversationsByConversationIdTemplate"}},"/api/v1/messages":{"post":{"tags":["Messages"],"summary":"Send to a phone number or contact","description":"Messages someone without looking up the conversation first: a phone number on a WhatsApp channel (`to` + `channelId`), or a contact on any channel (`contactId`). Text and images need the 24-hour window to be open, so to write first on WhatsApp, send a `template`; anything else returns `422 window_closed`. Scope: `conversations:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"maxLength":32,"description":"A phone number in international format, e.g. +9647701234567 (WhatsApp channels). Needs `channelId`.","type":"string"},"contactId":{"format":"uuid","description":"A contact, on any channel. Instead of `to`.","type":"string"},"channelId":{"format":"uuid","description":"The channel to send from. Required with `to`; with `contactId` it picks that contact's conversation on this channel.","type":"string"},"text":{"minLength":1,"maxLength":4096,"description":"The message, or the image's caption.","type":"string"},"imageUrl":{"maxLength":2000,"description":"A public JPEG or PNG link (up to 5 MB) to send as an image.","type":"string"},"template":{"type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"description":"The template's name.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"The template's language code, e.g. `en_US` or `ar`.","type":"string"},"params":{"maxItems":20,"description":"A value for each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"maxLength":1024,"type":"string"}}}}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"to":{"maxLength":32,"description":"A phone number in international format, e.g. +9647701234567 (WhatsApp channels). Needs `channelId`.","type":"string"},"contactId":{"format":"uuid","description":"A contact, on any channel. Instead of `to`.","type":"string"},"channelId":{"format":"uuid","description":"The channel to send from. Required with `to`; with `contactId` it picks that contact's conversation on this channel.","type":"string"},"text":{"minLength":1,"maxLength":4096,"description":"The message, or the image's caption.","type":"string"},"imageUrl":{"maxLength":2000,"description":"A public JPEG or PNG link (up to 5 MB) to send as an image.","type":"string"},"template":{"type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"description":"The template's name.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"The template's language code, e.g. `en_US` or `ar`.","type":"string"},"params":{"maxItems":20,"description":"A value for each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"maxLength":1024,"type":"string"}}}}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"to":{"maxLength":32,"description":"A phone number in international format, e.g. +9647701234567 (WhatsApp channels). Needs `channelId`.","type":"string"},"contactId":{"format":"uuid","description":"A contact, on any channel. Instead of `to`.","type":"string"},"channelId":{"format":"uuid","description":"The channel to send from. Required with `to`; with `contactId` it picks that contact's conversation on this channel.","type":"string"},"text":{"minLength":1,"maxLength":4096,"description":"The message, or the image's caption.","type":"string"},"imageUrl":{"maxLength":2000,"description":"A public JPEG or PNG link (up to 5 MB) to send as an image.","type":"string"},"template":{"type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"description":"The template's name.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"The template's language code, e.g. `en_US` or `ar`.","type":"string"},"params":{"maxItems":20,"description":"A value for each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"maxLength":1024,"type":"string"}}}}}}}}},"operationId":"postApiV1Messages"}},"/api/v1/agent":{"get":{"tags":["AI agent"],"summary":"Get the AI agent","description":"The agent's instructions, model and reply rules. Scope: `agent:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"operationId":"getApiV1Agent"},"patch":{"tags":["AI agent"],"summary":"Update the AI agent","description":"Set the instructions, model and reply rules. Only the fields you send change. Scope: `agent:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"minLength":1,"maxLength":60,"type":"string"},"model":{"minLength":3,"maxLength":120,"description":"An OpenRouter model id, e.g. `openai/gpt-4o-mini`.","type":"string"},"systemPrompt":{"maxLength":8000,"description":"Instructions: who the agent is, tone, what it may and may not do.","type":"string"},"temperature":{"minimum":0,"maximum":100,"description":"Creativity, 0–100 (divided by 100 when calling the model).","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":0,"maximum":100,"description":"Creativity, 0–100 (divided by 100 when calling the model).","type":"integer"}]},"enabled":{"description":"Reply to customers automatically.","type":"boolean"},"knowledgeOnly":{"description":"Only answer from the knowledge base; hand off to a person otherwise.","type":"boolean"},"handoffMessage":{"maxLength":1000,"description":"Sent to the customer when the agent hands off. Empty sends nothing.","type":"string"},"maxReplies":{"minimum":1,"maximum":100,"description":"Automatic replies per conversation before handing off.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":100,"description":"Automatic replies per conversation before handing off.","type":"integer"}]},"replyDelaySeconds":{"minimum":0,"maximum":120,"description":"Wait after the customer's last message, so a burst gets one reply.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":0,"maximum":120,"description":"Wait after the customer's last message, so a burst gets one reply.","type":"integer"}]}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"name":{"minLength":1,"maxLength":60,"type":"string"},"model":{"minLength":3,"maxLength":120,"description":"An OpenRouter model id, e.g. `openai/gpt-4o-mini`.","type":"string"},"systemPrompt":{"maxLength":8000,"description":"Instructions: who the agent is, tone, what it may and may not do.","type":"string"},"temperature":{"minimum":0,"maximum":100,"description":"Creativity, 0–100 (divided by 100 when calling the model).","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":0,"maximum":100,"description":"Creativity, 0–100 (divided by 100 when calling the model).","type":"integer"}]},"enabled":{"description":"Reply to customers automatically.","type":"boolean"},"knowledgeOnly":{"description":"Only answer from the knowledge base; hand off to a person otherwise.","type":"boolean"},"handoffMessage":{"maxLength":1000,"description":"Sent to the customer when the agent hands off. Empty sends nothing.","type":"string"},"maxReplies":{"minimum":1,"maximum":100,"description":"Automatic replies per conversation before handing off.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":100,"description":"Automatic replies per conversation before handing off.","type":"integer"}]},"replyDelaySeconds":{"minimum":0,"maximum":120,"description":"Wait after the customer's last message, so a burst gets one reply.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":0,"maximum":120,"description":"Wait after the customer's last message, so a burst gets one reply.","type":"integer"}]}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"name":{"minLength":1,"maxLength":60,"type":"string"},"model":{"minLength":3,"maxLength":120,"description":"An OpenRouter model id, e.g. `openai/gpt-4o-mini`.","type":"string"},"systemPrompt":{"maxLength":8000,"description":"Instructions: who the agent is, tone, what it may and may not do.","type":"string"},"temperature":{"minimum":0,"maximum":100,"description":"Creativity, 0–100 (divided by 100 when calling the model).","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":0,"maximum":100,"description":"Creativity, 0–100 (divided by 100 when calling the model).","type":"integer"}]},"enabled":{"description":"Reply to customers automatically.","type":"boolean"},"knowledgeOnly":{"description":"Only answer from the knowledge base; hand off to a person otherwise.","type":"boolean"},"handoffMessage":{"maxLength":1000,"description":"Sent to the customer when the agent hands off. Empty sends nothing.","type":"string"},"maxReplies":{"minimum":1,"maximum":100,"description":"Automatic replies per conversation before handing off.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":100,"description":"Automatic replies per conversation before handing off.","type":"integer"}]},"replyDelaySeconds":{"minimum":0,"maximum":120,"description":"Wait after the customer's last message, so a burst gets one reply.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":0,"maximum":120,"description":"Wait after the customer's last message, so a burst gets one reply.","type":"integer"}]}}}}}},"operationId":"patchApiV1Agent"}},"/api/v1/agent/test":{"post":{"tags":["AI agent"],"summary":"Try the AI agent","description":"Runs the agent on a sample chat without sending anything to a customer. Returns its `reply`, or the `handoff` reason, with the knowledge it used. Scope: `agent:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["messages"],"properties":{"messages":{"minItems":1,"maxItems":30,"type":"array","items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"minLength":1,"maxLength":4000,"type":"string"}}}}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["messages"],"properties":{"messages":{"minItems":1,"maxItems":30,"type":"array","items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"minLength":1,"maxLength":4000,"type":"string"}}}}}}},"multipart/form-data":{"schema":{"type":"object","required":["messages"],"properties":{"messages":{"minItems":1,"maxItems":30,"type":"array","items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"minLength":1,"maxLength":4000,"type":"string"}}}}}}}}},"operationId":"postApiV1AgentTest"}},"/api/v1/knowledge":{"get":{"tags":["Knowledge"],"summary":"List knowledge sources","description":"What the agent answers from, with each source's indexing `status`. Scope: `agent:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"operationId":"getApiV1Knowledge"},"post":{"tags":["Knowledge"],"summary":"Add a knowledge source","description":"Teaches the agent. The source is chunked and embedded in the background: its `status` goes `pending` → `processing` → `ready` (or `failed` with an `error`). Scope: `agent:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"default":"text","description":"`text`: a document; `qa`: one question and its answer; `url`: a web page to fetch.","type":"string","enum":["text","qa","url"]},"title":{"maxLength":200,"type":"string"},"content":{"maxLength":200000,"description":"For `text`.","type":"string"},"question":{"maxLength":1000,"description":"For `qa`.","type":"string"},"answer":{"maxLength":10000,"description":"For `qa`.","type":"string"},"url":{"maxLength":2000,"description":"For `url`.","type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"default":"text","description":"`text`: a document; `qa`: one question and its answer; `url`: a web page to fetch.","type":"string","enum":["text","qa","url"]},"title":{"maxLength":200,"type":"string"},"content":{"maxLength":200000,"description":"For `text`.","type":"string"},"question":{"maxLength":1000,"description":"For `qa`.","type":"string"},"answer":{"maxLength":10000,"description":"For `qa`.","type":"string"},"url":{"maxLength":2000,"description":"For `url`.","type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"default":"text","description":"`text`: a document; `qa`: one question and its answer; `url`: a web page to fetch.","type":"string","enum":["text","qa","url"]},"title":{"maxLength":200,"type":"string"},"content":{"maxLength":200000,"description":"For `text`.","type":"string"},"question":{"maxLength":1000,"description":"For `qa`.","type":"string"},"answer":{"maxLength":10000,"description":"For `qa`.","type":"string"},"url":{"maxLength":2000,"description":"For `url`.","type":"string"}}}}}},"operationId":"postApiV1Knowledge"}},"/api/v1/knowledge/{sourceId}":{"get":{"tags":["Knowledge"],"summary":"Get a knowledge source","description":"Includes the extracted text. Scope: `agent:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"getApiV1KnowledgeBySourceId"},"delete":{"tags":["Knowledge"],"summary":"Delete a knowledge source","description":"Scope: `agent:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"deleteApiV1KnowledgeBySourceId"}},"/api/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook endpoints","description":"Scope: `webhooks:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"operationId":"getApiV1Webhooks"},"post":{"tags":["Webhooks"],"summary":"Create a webhook endpoint","description":"The response includes the endpoint's signing `secret`; it isn't returned again by the API. Up to 10 endpoints per workspace. Endpoints a connected app creates are removed when the workspace disconnects it. Scope: `webhooks:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"maxLength":2000,"description":"An HTTPS URL.","type":"string"},"events":{"minItems":1,"type":"array","items":{"default":"message.created","type":"string","enum":["message.created","message.updated","conversation.updated","channel.updated","template.updated","campaign.updated","conversation.handoff","knowledge.updated"]}},"description":{"maxLength":200,"type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"maxLength":2000,"description":"An HTTPS URL.","type":"string"},"events":{"minItems":1,"type":"array","items":{"default":"message.created","type":"string","enum":["message.created","message.updated","conversation.updated","channel.updated","template.updated","campaign.updated","conversation.handoff","knowledge.updated"]}},"description":{"maxLength":200,"type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"maxLength":2000,"description":"An HTTPS URL.","type":"string"},"events":{"minItems":1,"type":"array","items":{"default":"message.created","type":"string","enum":["message.created","message.updated","conversation.updated","channel.updated","template.updated","campaign.updated","conversation.handoff","knowledge.updated"]}},"description":{"maxLength":200,"type":"string"}}}}}},"operationId":"postApiV1Webhooks"}},"/api/v1/webhooks/{endpointId}":{"patch":{"tags":["Webhooks"],"summary":"Update a webhook endpoint","description":"Scope: `webhooks:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"endpointId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"maxLength":2000,"type":"string"},"events":{"minItems":1,"type":"array","items":{"default":"message.created","type":"string","enum":["message.created","message.updated","conversation.updated","channel.updated","template.updated","campaign.updated","conversation.handoff","knowledge.updated"]}},"description":{"nullable":true,"maxLength":200,"type":["string","null"]},"enabled":{"type":"boolean"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"url":{"maxLength":2000,"type":"string"},"events":{"minItems":1,"type":"array","items":{"default":"message.created","type":"string","enum":["message.created","message.updated","conversation.updated","channel.updated","template.updated","campaign.updated","conversation.handoff","knowledge.updated"]}},"description":{"nullable":true,"maxLength":200,"type":["string","null"]},"enabled":{"type":"boolean"}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"url":{"maxLength":2000,"type":"string"},"events":{"minItems":1,"type":"array","items":{"default":"message.created","type":"string","enum":["message.created","message.updated","conversation.updated","channel.updated","template.updated","campaign.updated","conversation.handoff","knowledge.updated"]}},"description":{"nullable":true,"maxLength":200,"type":["string","null"]},"enabled":{"type":"boolean"}}}}}},"operationId":"patchApiV1WebhooksByEndpointId"},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook endpoint","description":"Scope: `webhooks:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"endpointId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"deleteApiV1WebhooksByEndpointId"}},"/api/v1/channels/{channelId}/templates":{"get":{"tags":["Templates"],"summary":"List templates","description":"The message templates on the WhatsApp account behind an official WhatsApp channel, with their review `status`. Only `APPROVED` ones can be sent; `params` is how many values a send needs, one for each entry in `vars` (the body's blanks, then the title's, then each link button's). Numbers on the same WhatsApp account share their templates. Scope: `templates:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"channelId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["APPROVED","PENDING","REJECTED","PAUSED","DISABLED"]}},{"name":"name","in":"query","required":false,"schema":{"maxLength":512,"description":"Only the template with this name (in each of its languages).","type":"string"}}],"operationId":"getApiV1ChannelsByChannelIdTemplates"},"post":{"tags":["Templates"],"summary":"Create a template","description":"Submits a template to Meta for review. It starts `PENDING` and usually becomes `APPROVED` or `REJECTED` within minutes; check with `GET /channels/{channelId}/templates?name=...` or listen for the `template.updated` webhook. Scope: `templates:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"channelId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","language","category"],"properties":{"name":{"maxLength":512,"description":"Lowercase letters, numbers and underscores, e.g. `order_shipped`.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"A WhatsApp language code, e.g. `en`, `en_US` or `ar`.","type":"string"},"category":{"default":"UTILITY","description":"`UTILITY`: updates about an order or account; `MARKETING`: offers and news; `AUTHENTICATION`: verification codes (Meta writes the text).","type":"string","enum":["UTILITY","MARKETING","AUTHENTICATION"]},"body":{"maxLength":1024,"description":"The text, with `{{1}}`, `{{2}}`, … where values go. Not for `AUTHENTICATION`.","type":"string"},"examples":{"maxItems":20,"description":"A sample value for each `{{n}}`, in order. Meta reviews the template with them.","type":"array","items":{"maxLength":200,"type":"string"}},"header":{"maxLength":60,"description":"A bold first line.","type":"string"},"footer":{"maxLength":60,"description":"A small grey last line.","type":"string"},"buttons":{"maxItems":10,"type":"array","items":{"type":"object","required":["type","text"],"properties":{"type":{"default":"quick_reply","description":"`quick_reply`: the customer taps it to answer; `url`: opens a link; `phone`: calls a number.","type":"string","enum":["quick_reply","url","phone"]},"text":{"minLength":1,"maxLength":25,"type":"string"},"url":{"maxLength":2000,"description":"For `url`: an https link.","type":"string"},"phone":{"maxLength":20,"description":"For `phone`: in international format.","type":"string"}}}},"securityNotice":{"description":"`AUTHENTICATION` only: add \"For your security, do not share this code.\" Default true.","type":"boolean"},"codeExpiresMinutes":{"minimum":1,"maximum":90,"description":"`AUTHENTICATION` only: show \"This code expires in N minutes.\"","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":90,"description":"`AUTHENTICATION` only: show \"This code expires in N minutes.\"","type":"integer"}]},"copyButtonText":{"maxLength":25,"description":"`AUTHENTICATION` only: the copy button's label. Default \"Copy code\".","type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["name","language","category"],"properties":{"name":{"maxLength":512,"description":"Lowercase letters, numbers and underscores, e.g. `order_shipped`.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"A WhatsApp language code, e.g. `en`, `en_US` or `ar`.","type":"string"},"category":{"default":"UTILITY","description":"`UTILITY`: updates about an order or account; `MARKETING`: offers and news; `AUTHENTICATION`: verification codes (Meta writes the text).","type":"string","enum":["UTILITY","MARKETING","AUTHENTICATION"]},"body":{"maxLength":1024,"description":"The text, with `{{1}}`, `{{2}}`, … where values go. Not for `AUTHENTICATION`.","type":"string"},"examples":{"maxItems":20,"description":"A sample value for each `{{n}}`, in order. Meta reviews the template with them.","type":"array","items":{"maxLength":200,"type":"string"}},"header":{"maxLength":60,"description":"A bold first line.","type":"string"},"footer":{"maxLength":60,"description":"A small grey last line.","type":"string"},"buttons":{"maxItems":10,"type":"array","items":{"type":"object","required":["type","text"],"properties":{"type":{"default":"quick_reply","description":"`quick_reply`: the customer taps it to answer; `url`: opens a link; `phone`: calls a number.","type":"string","enum":["quick_reply","url","phone"]},"text":{"minLength":1,"maxLength":25,"type":"string"},"url":{"maxLength":2000,"description":"For `url`: an https link.","type":"string"},"phone":{"maxLength":20,"description":"For `phone`: in international format.","type":"string"}}}},"securityNotice":{"description":"`AUTHENTICATION` only: add \"For your security, do not share this code.\" Default true.","type":"boolean"},"codeExpiresMinutes":{"minimum":1,"maximum":90,"description":"`AUTHENTICATION` only: show \"This code expires in N minutes.\"","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":90,"description":"`AUTHENTICATION` only: show \"This code expires in N minutes.\"","type":"integer"}]},"copyButtonText":{"maxLength":25,"description":"`AUTHENTICATION` only: the copy button's label. Default \"Copy code\".","type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["name","language","category"],"properties":{"name":{"maxLength":512,"description":"Lowercase letters, numbers and underscores, e.g. `order_shipped`.","type":"string"},"language":{"minLength":2,"maxLength":15,"description":"A WhatsApp language code, e.g. `en`, `en_US` or `ar`.","type":"string"},"category":{"default":"UTILITY","description":"`UTILITY`: updates about an order or account; `MARKETING`: offers and news; `AUTHENTICATION`: verification codes (Meta writes the text).","type":"string","enum":["UTILITY","MARKETING","AUTHENTICATION"]},"body":{"maxLength":1024,"description":"The text, with `{{1}}`, `{{2}}`, … where values go. Not for `AUTHENTICATION`.","type":"string"},"examples":{"maxItems":20,"description":"A sample value for each `{{n}}`, in order. Meta reviews the template with them.","type":"array","items":{"maxLength":200,"type":"string"}},"header":{"maxLength":60,"description":"A bold first line.","type":"string"},"footer":{"maxLength":60,"description":"A small grey last line.","type":"string"},"buttons":{"maxItems":10,"type":"array","items":{"type":"object","required":["type","text"],"properties":{"type":{"default":"quick_reply","description":"`quick_reply`: the customer taps it to answer; `url`: opens a link; `phone`: calls a number.","type":"string","enum":["quick_reply","url","phone"]},"text":{"minLength":1,"maxLength":25,"type":"string"},"url":{"maxLength":2000,"description":"For `url`: an https link.","type":"string"},"phone":{"maxLength":20,"description":"For `phone`: in international format.","type":"string"}}}},"securityNotice":{"description":"`AUTHENTICATION` only: add \"For your security, do not share this code.\" Default true.","type":"boolean"},"codeExpiresMinutes":{"minimum":1,"maximum":90,"description":"`AUTHENTICATION` only: show \"This code expires in N minutes.\"","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":90,"description":"`AUTHENTICATION` only: show \"This code expires in N minutes.\"","type":"integer"}]},"copyButtonText":{"maxLength":25,"description":"`AUTHENTICATION` only: the copy button's label. Default \"Copy code\".","type":"string"}}}}}},"operationId":"postApiV1ChannelsByChannelIdTemplates"}},"/api/v1/channels/{channelId}/templates/{name}":{"delete":{"tags":["Templates"],"summary":"Delete a template","description":"Deletes the template in every language. Meta doesn't allow a new template with the same name for 30 days. Scope: `templates:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"channelId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}},{"name":"name","in":"path","required":true,"schema":{"maxLength":512,"type":"string"}}],"operationId":"deleteApiV1ChannelsByChannelIdTemplatesByName"}},"/api/v1/otp":{"post":{"tags":["Verification codes"],"summary":"Send a code","description":"Makes a one-time code and sends it on WhatsApp with an approved `AUTHENTICATION` template (the customer gets a \"Copy code\" button). We keep only a hash; check what the user types with `POST /otp/verify`. A new code replaces the previous one for that number. Limits per number: one every 30 seconds and 5 an hour (`429` with `Retry-After`). Scope: `otp:send`.","security":[{"apiKey":[]},{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["channelId","to"],"properties":{"channelId":{"format":"uuid","type":"string"},"to":{"maxLength":32,"description":"The phone number, in international format, e.g. +9647701234567.","type":"string"},"length":{"minimum":4,"maximum":8,"description":"Digits in the code. Default 6.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":4,"maximum":8,"description":"Digits in the code. Default 6.","type":"integer"}]},"expiresInMinutes":{"minimum":1,"maximum":30,"description":"How long the code works. Default 10.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":30,"description":"How long the code works. Default 10.","type":"integer"}]},"template":{"description":"Which authentication template to use. By default, the first approved one.","type":"object","required":["name"],"properties":{"name":{"maxLength":512,"type":"string"},"language":{"maxLength":15,"type":"string"}}}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["channelId","to"],"properties":{"channelId":{"format":"uuid","type":"string"},"to":{"maxLength":32,"description":"The phone number, in international format, e.g. +9647701234567.","type":"string"},"length":{"minimum":4,"maximum":8,"description":"Digits in the code. Default 6.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":4,"maximum":8,"description":"Digits in the code. Default 6.","type":"integer"}]},"expiresInMinutes":{"minimum":1,"maximum":30,"description":"How long the code works. Default 10.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":30,"description":"How long the code works. Default 10.","type":"integer"}]},"template":{"description":"Which authentication template to use. By default, the first approved one.","type":"object","required":["name"],"properties":{"name":{"maxLength":512,"type":"string"},"language":{"maxLength":15,"type":"string"}}}}}},"multipart/form-data":{"schema":{"type":"object","required":["channelId","to"],"properties":{"channelId":{"format":"uuid","type":"string"},"to":{"maxLength":32,"description":"The phone number, in international format, e.g. +9647701234567.","type":"string"},"length":{"minimum":4,"maximum":8,"description":"Digits in the code. Default 6.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":4,"maximum":8,"description":"Digits in the code. Default 6.","type":"integer"}]},"expiresInMinutes":{"minimum":1,"maximum":30,"description":"How long the code works. Default 10.","anyOf":[{"format":"integer","default":0,"type":"string"},{"minimum":1,"maximum":30,"description":"How long the code works. Default 10.","type":"integer"}]},"template":{"description":"Which authentication template to use. By default, the first approved one.","type":"object","required":["name"],"properties":{"name":{"maxLength":512,"type":"string"},"language":{"maxLength":15,"type":"string"}}}}}}}},"operationId":"postApiV1Otp"}},"/api/v1/otp/verify":{"post":{"tags":["Verification codes"],"summary":"Check a code","description":"Returns `{ \"valid\": true }` once for the right code. Otherwise `valid` is false with a `reason`: `wrong_code` (with `attemptsLeft`), `expired`, `already_used`, `too_many_attempts` (after 5 wrong tries; send a new code) or `not_found`. Scope: `otp:send`.","security":[{"apiKey":[]},{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"to":{"maxLength":32,"description":"The phone number the code was sent to.","type":"string"},"id":{"format":"uuid","description":"Or the `id` from `POST /otp`.","type":"string"},"code":{"minLength":4,"maxLength":8,"description":"What the user typed.","type":"string"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["code"],"properties":{"to":{"maxLength":32,"description":"The phone number the code was sent to.","type":"string"},"id":{"format":"uuid","description":"Or the `id` from `POST /otp`.","type":"string"},"code":{"minLength":4,"maxLength":8,"description":"What the user typed.","type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["code"],"properties":{"to":{"maxLength":32,"description":"The phone number the code was sent to.","type":"string"},"id":{"format":"uuid","description":"Or the `id` from `POST /otp`.","type":"string"},"code":{"minLength":4,"maxLength":8,"description":"What the user typed.","type":"string"}}}}}},"operationId":"postApiV1OtpVerify"}},"/api/v1/campaigns":{"get":{"tags":["Campaigns"],"summary":"List campaigns","description":"Newest first, each with its `stats`. `stats` counts recipients: `queued` (not sent yet), `sent` (accepted by WhatsApp, including delivered and read), `delivered` (including read), `read`, `replied` (wrote back after it), `failed` and `skipped` (opted out, an invalid number, a missing value, or canceled). Scope: `campaigns:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"channelId","in":"query","required":false,"schema":{"format":"uuid","type":"string"}},{"name":"before","in":"query","required":false,"schema":{"format":"date-time","description":"Cursor: the `nextCursor` from the previous page.","type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"minimum":1,"maximum":100,"anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":1,"maximum":100,"type":"number"}]}}],"operationId":"getApiV1Campaigns"},"post":{"tags":["Campaigns"],"summary":"Create a campaign","description":"Sends an approved WhatsApp template to up to 10,000 people, now or at `scheduledAt`. Pick them by conversation tag, everyone on the number, or your own list. Each of the template's `vars` is fixed `text` or a `field` of the recipient (`name`, or a key of their `values`). Customers who replied STOP (or opted out with `PATCH /contacts/{id}`) are skipped. Messages go out at about 20 a second and land in each customer's conversation, so replies show up in the inbox. A template with a picture, video or file header (`headerFormat` in the template list) also needs `headerMedia.link`. Errors: `template_not_found`, `header_media_required`, `authentication_template_not_allowed`, `template_params_mismatch`, `no_recipients`, `too_many_recipients`, `invalid_schedule`, `campaigns_need_whatsapp`. Scope: `campaigns:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["channelId","name","template","audience"],"properties":{"channelId":{"format":"uuid","description":"An official WhatsApp channel.","type":"string"},"name":{"minLength":1,"maxLength":80,"description":"For you; customers don't see it.","type":"string"},"template":{"description":"An approved `MARKETING` or `UTILITY` template.","type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"type":"string"},"language":{"minLength":2,"maxLength":15,"type":"string"}}},"params":{"maxItems":20,"description":"What fills each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"anyOf":[{"type":"object","required":["text"],"properties":{"text":{"minLength":1,"maxLength":1024,"description":"The same text for everyone.","type":"string"}}},{"type":"object","required":["field"],"properties":{"field":{"minLength":1,"maxLength":60,"description":"`name` for the customer's name, or a key of each recipient's `values`.","type":"string"},"fallback":{"maxLength":1024,"description":"Used when the field is empty for someone. Without one, that person is skipped.","type":"string"}}}]}},"headerMedia":{"nullable":true,"description":"For a template whose header is a picture, video or file (`headerFormat` in the template list): what everyone gets above the text.","type":["object","null"],"required":["link"],"properties":{"link":{"maxLength":2000,"description":"A public https link to the picture, video or PDF.","type":"string"}}},"audience":{"description":"Who gets it. People who opted out are always skipped.","anyOf":[{"description":"Everyone who has a conversation on this number.","type":"object","required":["type"],"properties":{"type":{"const":"everyone","type":"string"}}},{"description":"Customers whose conversation on this number has one of the tags.","type":"object","required":["type","tags"],"properties":{"type":{"const":"tags","type":"string"},"tags":{"minItems":1,"maxItems":20,"description":"Conversations with any of these tags.","type":"array","items":{"minLength":1,"maxLength":40,"type":"string"}}}},{"description":"Your own list of phone numbers (for example from a CSV file).","type":"object","required":["type","recipients"],"properties":{"type":{"const":"list","type":"string"},"recipients":{"minItems":1,"maxItems":10000,"type":"array","items":{"type":"object","required":["phone"],"properties":{"phone":{"maxLength":32,"description":"In international format, e.g. +9647701234567.","type":"string"},"name":{"maxLength":120,"type":"string"},"values":{"description":"Per-person values for `params` fields, e.g. `{ \"code\": \"EID20\" }`.","type":"object","patternProperties":{"^(.*)$":{"maxLength":1024,"type":"string"}}}}}}}}]},"scheduledAt":{"nullable":true,"format":"date-time","description":"When to send, up to 30 days ahead. Leave out to send now.","type":["string","null"]}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["channelId","name","template","audience"],"properties":{"channelId":{"format":"uuid","description":"An official WhatsApp channel.","type":"string"},"name":{"minLength":1,"maxLength":80,"description":"For you; customers don't see it.","type":"string"},"template":{"description":"An approved `MARKETING` or `UTILITY` template.","type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"type":"string"},"language":{"minLength":2,"maxLength":15,"type":"string"}}},"params":{"maxItems":20,"description":"What fills each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"anyOf":[{"type":"object","required":["text"],"properties":{"text":{"minLength":1,"maxLength":1024,"description":"The same text for everyone.","type":"string"}}},{"type":"object","required":["field"],"properties":{"field":{"minLength":1,"maxLength":60,"description":"`name` for the customer's name, or a key of each recipient's `values`.","type":"string"},"fallback":{"maxLength":1024,"description":"Used when the field is empty for someone. Without one, that person is skipped.","type":"string"}}}]}},"headerMedia":{"nullable":true,"description":"For a template whose header is a picture, video or file (`headerFormat` in the template list): what everyone gets above the text.","type":["object","null"],"required":["link"],"properties":{"link":{"maxLength":2000,"description":"A public https link to the picture, video or PDF.","type":"string"}}},"audience":{"description":"Who gets it. People who opted out are always skipped.","anyOf":[{"description":"Everyone who has a conversation on this number.","type":"object","required":["type"],"properties":{"type":{"const":"everyone","type":"string"}}},{"description":"Customers whose conversation on this number has one of the tags.","type":"object","required":["type","tags"],"properties":{"type":{"const":"tags","type":"string"},"tags":{"minItems":1,"maxItems":20,"description":"Conversations with any of these tags.","type":"array","items":{"minLength":1,"maxLength":40,"type":"string"}}}},{"description":"Your own list of phone numbers (for example from a CSV file).","type":"object","required":["type","recipients"],"properties":{"type":{"const":"list","type":"string"},"recipients":{"minItems":1,"maxItems":10000,"type":"array","items":{"type":"object","required":["phone"],"properties":{"phone":{"maxLength":32,"description":"In international format, e.g. +9647701234567.","type":"string"},"name":{"maxLength":120,"type":"string"},"values":{"description":"Per-person values for `params` fields, e.g. `{ \"code\": \"EID20\" }`.","type":"object","patternProperties":{"^(.*)$":{"maxLength":1024,"type":"string"}}}}}}}}]},"scheduledAt":{"nullable":true,"format":"date-time","description":"When to send, up to 30 days ahead. Leave out to send now.","type":["string","null"]}}}},"multipart/form-data":{"schema":{"type":"object","required":["channelId","name","template","audience"],"properties":{"channelId":{"format":"uuid","description":"An official WhatsApp channel.","type":"string"},"name":{"minLength":1,"maxLength":80,"description":"For you; customers don't see it.","type":"string"},"template":{"description":"An approved `MARKETING` or `UTILITY` template.","type":"object","required":["name","language"],"properties":{"name":{"minLength":1,"maxLength":512,"type":"string"},"language":{"minLength":2,"maxLength":15,"type":"string"}}},"params":{"maxItems":20,"description":"What fills each of the template's `vars`, in order: the body's blanks, then the title's, then each link button's.","type":"array","items":{"anyOf":[{"type":"object","required":["text"],"properties":{"text":{"minLength":1,"maxLength":1024,"description":"The same text for everyone.","type":"string"}}},{"type":"object","required":["field"],"properties":{"field":{"minLength":1,"maxLength":60,"description":"`name` for the customer's name, or a key of each recipient's `values`.","type":"string"},"fallback":{"maxLength":1024,"description":"Used when the field is empty for someone. Without one, that person is skipped.","type":"string"}}}]}},"headerMedia":{"nullable":true,"description":"For a template whose header is a picture, video or file (`headerFormat` in the template list): what everyone gets above the text.","type":["object","null"],"required":["link"],"properties":{"link":{"maxLength":2000,"description":"A public https link to the picture, video or PDF.","type":"string"}}},"audience":{"description":"Who gets it. People who opted out are always skipped.","anyOf":[{"description":"Everyone who has a conversation on this number.","type":"object","required":["type"],"properties":{"type":{"const":"everyone","type":"string"}}},{"description":"Customers whose conversation on this number has one of the tags.","type":"object","required":["type","tags"],"properties":{"type":{"const":"tags","type":"string"},"tags":{"minItems":1,"maxItems":20,"description":"Conversations with any of these tags.","type":"array","items":{"minLength":1,"maxLength":40,"type":"string"}}}},{"description":"Your own list of phone numbers (for example from a CSV file).","type":"object","required":["type","recipients"],"properties":{"type":{"const":"list","type":"string"},"recipients":{"minItems":1,"maxItems":10000,"type":"array","items":{"type":"object","required":["phone"],"properties":{"phone":{"maxLength":32,"description":"In international format, e.g. +9647701234567.","type":"string"},"name":{"maxLength":120,"type":"string"},"values":{"description":"Per-person values for `params` fields, e.g. `{ \"code\": \"EID20\" }`.","type":"object","patternProperties":{"^(.*)$":{"maxLength":1024,"type":"string"}}}}}}}}]},"scheduledAt":{"nullable":true,"format":"date-time","description":"When to send, up to 30 days ahead. Leave out to send now.","type":["string","null"]}}}}}},"operationId":"postApiV1Campaigns"}},"/api/v1/campaigns/{campaignId}":{"get":{"tags":["Campaigns"],"summary":"Get a campaign","description":"`status` is `scheduled`, `sending`, `paused` (waiting for credit, `error` is `no_credit`; it carries on by itself after a top-up), `sent`, `canceled` or `failed` (stopped early, with WhatsApp's code in `error`, e.g. 132015 when the template was paused). `stats` counts recipients: `queued` (not sent yet), `sent` (accepted by WhatsApp, including delivered and read), `delivered` (including read), `read`, `replied` (wrote back after it), `failed` and `skipped` (opted out, an invalid number, a missing value, or canceled). Listen for the `campaign.updated` webhook to hear when it starts and finishes. Scope: `campaigns:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"campaignId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"getApiV1CampaignsByCampaignId"}},"/api/v1/campaigns/{campaignId}/recipients":{"get":{"tags":["Campaigns"],"summary":"List recipients","description":"Everyone in the campaign with how far their message got: `queued`, `sent`, `delivered`, `read`, `failed` or `skipped`. `reason` explains a failed or skipped one: `opted_out`, `invalid_phone`, `missing_value`, `canceled`, or WhatsApp's error code (131049: WhatsApp held back a marketing message to keep the customer's experience good; 131026: the number can't receive it). Scope: `campaigns:read`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"campaignId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["queued","sent","delivered","read","failed","skipped"]}},{"name":"after","in":"query","required":false,"schema":{"minimum":0,"description":"Cursor: the `nextCursor` from the previous page.","anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":0,"description":"Cursor: the `nextCursor` from the previous page.","type":"number"}]}},{"name":"limit","in":"query","required":false,"schema":{"minimum":1,"maximum":100,"anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":1,"maximum":100,"type":"number"}]}}],"operationId":"getApiV1CampaignsByCampaignIdRecipients"}},"/api/v1/campaigns/{campaignId}/cancel":{"post":{"tags":["Campaigns"],"summary":"Cancel a campaign","description":"Stops a scheduled, sending or paused campaign. Messages already sent stay sent; everyone else is `skipped` with reason `canceled`. `409 campaign_finished` once it's done. Scope: `campaigns:write`.","security":[{"apiKey":[]},{"oauth2":[]}],"parameters":[{"name":"campaignId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}],"operationId":"postApiV1CampaignsByCampaignIdCancel"}}}}