{"openapi":"3.0.0","paths":{"/v1/sessions/{id}":{"get":{"description":"Returns the current state of a WhatsApp session — its phone number, connection status, health score, and metadata.\n\n**Status values:**\n| Status | Meaning |\n|--------|---------|\n| `NEW` | Session created, not yet started |\n| `QR_PENDING` | Waiting for a QR code scan |\n| `CONNECTED` | Fully linked and ready to send messages |\n| `DISCONNECTED` | Lost connection — will attempt automatic reconnection |\n| `RECONNECTING` | Actively trying to reconnect |\n| `BANNED` | WhatsApp has flagged this number |\n| `LOGGED_OUT` | The WhatsApp app was logged out on the phone |","operationId":"SessionController_findOne","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}}],"responses":{"200":{"description":"Session details","content":{"application/json":{"schema":{"example":{"success":true,"data":{"id":"sess_a1b2c3d4","tenantId":"ten_xyz789","phone":"2348012345678","displayName":"Customer Support","status":"CONNECTED","webhooksEnabled":true,"healthScore":92,"reconnectAttempts":0,"lastSeenAt":"2026-06-14T11:58:00.000Z","createdAt":"2026-06-01T09:00:00.000Z"},"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Session not found"}},"security":[{"bearer":[]}],"summary":"Get session details","tags":["sessions"]}},"/v1/sessions/{id}/health":{"get":{"description":"Returns a 0–100 health score and a per-indicator breakdown. Use this to proactively detect degraded sessions before they affect message delivery.\n\n**Score thresholds:**\n- `healthy` — 70–100\n- `warning` — 40–69\n- `critical` — 0–39\n\n**What affects the score:**\n- Reconnect attempts in the last hour\n- Minutes since the session was last seen active\n- Session age (new sessions score lower until warm-up is complete at 7 days)","operationId":"SessionController_getHealth","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}}],"responses":{"200":{"description":"Health report","content":{"application/json":{"schema":{"example":{"success":true,"data":{"score":85,"status":"healthy","indicators":{"reconnectAttempts":1,"lastSeenMinutesAgo":3,"sessionAgeDays":14,"warmUpComplete":true}},"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Session not found"}},"security":[{"bearer":[]}],"summary":"Get session health score","tags":["sessions"]}},"/v1/sessions/{id}/reconnect":{"post":{"description":"Forces an immediate reconnection attempt outside of the automatic retry cycle.\n\nUse this when `status` is `DISCONNECTED` and you need the session back online urgently, \nrather than waiting for the next scheduled retry (which backs off up to 60 seconds between attempts).\n\nReturns `204 No Content` once the reconnection has been initiated. \nPoll `GET /sessions/{id}` or listen to the `session.connected` webhook to confirm the session is back online.","operationId":"SessionController_reconnect","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}}],"responses":{"204":{"description":"Reconnection initiated"},"401":{"description":"Invalid or missing API key"},"404":{"description":"Session not found"}},"security":[{"bearer":[]}],"summary":"Reconnect a disconnected session","tags":["sessions"]}},"/v1/sessions/{id}/send":{"post":{"description":"Send a WhatsApp message through this session. Supports 8 message types — pick the one that matches your use case.\n\n**Offline queueing**: If the session is temporarily disconnected, the message is queued in Redis (TTL: 1 hour) and delivered automatically when the connection is restored. The response will include `\"queued\": true` in this case.\n\n**Message types:**\n| Type | Best for |\n|------|---------|\n| `text` | Notifications, alerts, plain messages |\n| `image` | Receipts, product photos, confirmations |\n| `video` | Tutorials, demos |\n| `document` | PDFs, invoices, reports |\n| `audio` | Voice notes |\n| `buttons` | Yes/No prompts, quick choices (max 3 options) |\n| `list` | Menu navigation, multi-option pickers |\n| `location` | Pickup points, delivery addresses, store locations |","operationId":"SessionController_send","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageDto"},"examples":{"text":{"summary":"Text message","value":{"to":"2348012345678","type":"text","text":"Hello! Your order #1234 has been confirmed and is being prepared."}},"image":{"summary":"Image with caption","value":{"to":"2348012345678","type":"image","mediaUrl":"https://cdn.example.com/receipt.jpg","caption":"Your payment receipt — June 2026"}},"document":{"summary":"PDF document","value":{"to":"2348012345678","type":"document","mediaUrl":"https://cdn.example.com/invoice-1234.pdf","fileName":"invoice-1234.pdf","mimeType":"application/pdf","caption":"Invoice #1234"}},"audio":{"summary":"Audio message","value":{"to":"2348012345678","type":"audio","mediaUrl":"https://cdn.example.com/voicenote.mp3","mimeType":"audio/mpeg"}},"buttons":{"summary":"Button message (up to 3 buttons)","value":{"to":"2348012345678","type":"buttons","text":"Have you received your package?","buttons":[{"id":"yes","label":"Yes, received it"},{"id":"no","label":"Not yet"},{"id":"issue","label":"There's a problem"}]}},"list":{"summary":"List picker","value":{"to":"2348012345678","type":"list","text":"How would you like your order delivered?","listButtonText":"Choose option","sections":[{"title":"Delivery Options","rows":[{"id":"standard","title":"Standard Delivery","description":"3–5 business days — Free"},{"id":"express","title":"Express Delivery","description":"Next business day — ₦1,500"}]}]}},"location":{"summary":"Location pin","value":{"to":"2348012345678","type":"location","latitude":6.5244,"longitude":3.3792,"locationName":"Uno HQ","locationAddress":"1 Finance Crescent, Oniru Estate, Lagos"}}}}}},"responses":{"201":{"description":"Message sent (or queued if session is offline)","content":{"application/json":{"schema":{"example":{"success":true,"data":{"queued":false},"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"402":{"description":"No active subscription or daily message limit reached"},"404":{"description":"Session not found"},"429":{"description":"Rate limit or daily message limit exceeded"}},"security":[{"bearer":[]}],"summary":"Send a message","tags":["sessions"]}},"/v1/sessions/{id}/keys":{"get":{"description":"Returns all active API keys that are scoped to this specific session.\n\nSession-scoped keys have a narrower scope than master keys — they can only be used to send messages, \nmanage webhooks, and read health for *this* session. They cannot list or create other sessions.\n\nThis makes them safe to embed in third-party integrations or hand to external developers without \nexposing your full account access.","operationId":"SessionController_listKeys","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}}],"responses":{"200":{"description":"List of active session-scoped API keys","content":{"application/json":{"schema":{"example":{"success":true,"data":[{"id":"key_abc123","name":"backend-service","prefix":"sk_live_ab12","scopes":["*"],"lastUsedAt":"2026-06-14T11:30:00.000Z","createdAt":"2026-06-01T09:00:00.000Z"}],"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Session not found"}},"security":[{"bearer":[]}],"summary":"List session API keys","tags":["sessions"]},"post":{"description":"Creates a new API key scoped to this session.\n\n**Important:** The full raw key (`sk_live_...`) is returned **only once** at creation time. \nStore it securely — it cannot be retrieved again. Only the prefix (first ~16 characters) is stored.","operationId":"SessionController_createKey","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"name":{"type":"string","example":"backend-service","description":"A human-readable label to identify this key (e.g. the service that will use it)"}}}}}},"responses":{"201":{"description":"Newly created key — raw value only returned once","content":{"application/json":{"schema":{"example":{"success":true,"data":{"id":"key_abc123","key":"sk_live_ab12cd34ef56gh78ij90kl12mn34op56qr78st90uv12wx34yz56","prefix":"sk_live_ab12"},"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Session not found"}},"security":[{"bearer":[]}],"summary":"Create a session API key","tags":["sessions"]}},"/v1/sessions/{id}/keys/{keyId}":{"delete":{"description":"Immediately invalidates the specified API key.\n\nAny subsequent request using this key will receive a `401 Unauthorized` response. \nThis action is **irreversible** — you will need to create a new key to replace it.","operationId":"SessionController_revokeKey","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}},{"name":"keyId","required":true,"in":"path","description":"API Key ID to revoke","schema":{"example":"key_abc123","type":"string"}}],"responses":{"204":{"description":"Key revoked"},"401":{"description":"Invalid or missing API key"},"404":{"description":"Key not found or already revoked"}},"security":[{"bearer":[]}],"summary":"Revoke a session API key","tags":["sessions"]}},"/v1/sessions/{id}/webhooks":{"get":{"description":"Returns all active webhook endpoints registered to this session, along with their event filter and failure count.","operationId":"SessionController_listWebhooks","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}}],"responses":{"200":{"description":"List of registered webhooks","content":{"application/json":{"schema":{"example":{"success":true,"data":[{"id":"wh_xyz789","url":"https://your-api.example.com/webhooks/uno-sap","events":["message.received","session.disconnected"],"enabled":true,"failureCount":0,"createdAt":"2026-06-01T09:00:00.000Z"}],"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Session not found"}},"security":[{"bearer":[]}],"summary":"List session webhooks","tags":["sessions"]},"post":{"description":"Subscribe your server to events from this session. Uno-SAP will send a signed HTTP POST to your URL whenever a matching event occurs.\n\n**Signature verification (strongly recommended)**\n\nEach delivery includes an `X-Uno-Signature` header containing the HMAC-SHA256 hex digest of the raw request body, signed with your `secret`. Verify this on your server before processing the payload:\n\n```js\nconst crypto = require('crypto')\nconst sig = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')\nif (sig !== req.headers['x-uno-signature']) throw new Error('Invalid signature')\n```\n\n**Event filter**\n\nLeave `events` empty to receive all events. Provide a subset to reduce noise:\n\n- `message.received`\n- `message.sent`\n- `message.delivered`\n- `message.read`\n- `message.failed`\n- `message.updated`\n- `message.deleted`\n- `message.reaction`\n- `session.connected`\n- `session.disconnected`\n- `session.reconnected`\n- `session.banned`\n- `session.qr_updated`\n- `session.health_changed`\n- `chats.upsert`\n- `chats.update`\n- `chats.delete`\n- `groups.upsert`\n- `groups.update`\n- `group-participants.update`\n- `call.received`\n- `flow.started`\n- `flow.completed`\n- `flow.error`\n- `flow.human_handoff`\n- `contact.created`\n- `contact.opted_out`\n\n**Retry policy**: Failed deliveries are retried at 0s → 1 min → 5 min → 30 min → 2 h. All attempts are logged and visible via the deliveries endpoint.","operationId":"SessionController_createWebhook","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"required":["url","secret"],"properties":{"url":{"type":"string","format":"uri","example":"https://your-api.example.com/webhooks/uno-sap","description":"HTTPS endpoint that will receive event payloads. Must be publicly reachable — localhost and private IPs are not allowed."},"secret":{"type":"string","example":"my-super-secret-signing-key","description":"Signing secret used to compute the HMAC-SHA256 signature sent in the `X-Uno-Signature` header."},"events":{"type":"array","items":{"type":"string","enum":["message.received","message.sent","message.delivered","message.read","message.failed","message.updated","message.deleted","message.reaction","session.connected","session.disconnected","session.reconnected","session.banned","session.qr_updated","session.health_changed","chats.upsert","chats.update","chats.delete","groups.upsert","groups.update","group-participants.update","call.received","flow.started","flow.completed","flow.error","flow.human_handoff","contact.created","contact.opted_out"]},"example":["message.received","session.disconnected"],"description":"Event types to subscribe to. Omit or pass an empty array to receive all events."}}},"examples":{"all_events":{"summary":"Subscribe to all events","value":{"url":"https://your-api.example.com/webhooks/uno-sap","secret":"my-super-secret-signing-key","events":[]}},"messages_only":{"summary":"Messages only","value":{"url":"https://your-api.example.com/webhooks/uno-sap","secret":"my-super-secret-signing-key","events":["message.received","message.delivered","message.read"]}}}}}},"responses":{"201":{"description":"Webhook registered","content":{"application/json":{"schema":{"example":{"success":true,"data":{"id":"wh_xyz789","url":"https://your-api.example.com/webhooks/uno-sap","events":["message.received"],"enabled":true,"secret":"my-super-secret-signing-key","createdAt":"2026-06-14T12:00:00.000Z"},"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"400":{"description":"Invalid URL or private/internal address rejected"},"401":{"description":"Invalid or missing API key"}},"security":[{"bearer":[]}],"summary":"Register a webhook","tags":["sessions"]}},"/v1/sessions/{id}/webhooks/{webhookId}":{"patch":{"description":"Update the URL, event filter, or enabled state of a registered webhook. Only the fields you include are changed.","operationId":"SessionController_updateWebhook","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}},{"name":"webhookId","required":true,"in":"path","description":"Webhook ID","schema":{"example":"wh_xyz789","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"url":{"type":"string","format":"uri","example":"https://your-api.example.com/webhooks/v2","description":"New delivery URL"},"events":{"type":"array","items":{"type":"string","enum":["message.received","message.sent","message.delivered","message.read","message.failed","message.updated","message.deleted","message.reaction","session.connected","session.disconnected","session.reconnected","session.banned","session.qr_updated","session.health_changed","chats.upsert","chats.update","chats.delete","groups.upsert","groups.update","group-participants.update","call.received","flow.started","flow.completed","flow.error","flow.human_handoff","contact.created","contact.opted_out"]},"example":["message.received"],"description":"Replace the event filter. Pass `[]` to receive all events."},"enabled":{"type":"boolean","example":false,"description":"Disable without deleting — useful for maintenance windows."}}}}}},"responses":{"200":{"description":"Updated webhook","content":{"application/json":{"schema":{"example":{"success":true,"data":{"id":"wh_xyz789","url":"https://your-api.example.com/webhooks/v2","events":["message.received"],"enabled":true,"failureCount":0},"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Webhook not found"}},"security":[{"bearer":[]}],"summary":"Update a webhook","tags":["sessions"]},"delete":{"description":"Permanently removes the webhook. No further events will be delivered to this URL. This cannot be undone.","operationId":"SessionController_deleteWebhook","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}},{"name":"webhookId","required":true,"in":"path","description":"Webhook ID","schema":{"example":"wh_xyz789","type":"string"}}],"responses":{"204":{"description":"Webhook deleted"},"401":{"description":"Invalid or missing API key"},"404":{"description":"Webhook not found"}},"security":[{"bearer":[]}],"summary":"Delete a webhook","tags":["sessions"]}},"/v1/sessions/{id}/webhooks/{webhookId}/deliveries":{"get":{"description":"Returns the 50 most recent delivery attempts for a webhook, ordered newest-first.\n\n**Delivery status:**\n| Status | Meaning |\n|--------|---------|\n| `pending` | Queued, not yet attempted |\n| `success` | Your server responded with 2xx |\n| `failed` | All retry attempts exhausted without a 2xx response |\n\nUse this to debug missed events or investigate webhook failures.","operationId":"SessionController_getWebhookDeliveries","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}},{"name":"webhookId","required":true,"in":"path","description":"Webhook ID","schema":{"example":"wh_xyz789","type":"string"}}],"responses":{"200":{"description":"Delivery log (latest 50)","content":{"application/json":{"schema":{"example":{"success":true,"data":[{"id":"del_aaa111","eventType":"message.received","status":"success","attempts":1,"responseCode":200,"lastAttemptAt":"2026-06-14T11:59:00.000Z","createdAt":"2026-06-14T11:59:00.000Z"},{"id":"del_bbb222","eventType":"session.disconnected","status":"failed","attempts":5,"responseCode":503,"lastAttemptAt":"2026-06-14T10:02:00.000Z","createdAt":"2026-06-14T08:00:00.000Z"}],"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Webhook not found"}},"security":[{"bearer":[]}],"summary":"Get webhook delivery log","tags":["sessions"]}},"/v1/sessions/{id}/webhooks/{webhookId}/test":{"post":{"description":"Sends a synthetic `message.received` payload to your webhook URL immediately.\n\nUse this to verify your endpoint is reachable and your signature verification logic is correct \nbefore going to production. The test delivery appears in the delivery log like any real event.","operationId":"SessionController_testWebhook","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"example":"sess_a1b2c3d4","type":"string"}},{"name":"webhookId","required":true,"in":"path","description":"Webhook ID to test","schema":{"example":"wh_xyz789","type":"string"}}],"responses":{"201":{"description":"Test event queued for delivery","content":{"application/json":{"schema":{"example":{"success":true,"data":{"queued":true},"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"Webhook not found"}},"security":[{"bearer":[]}],"summary":"Send a test event to a webhook","tags":["sessions"]}},"/v1/media/upload":{"post":{"description":"Upload an image, video, document, or audio file and receive a URL you can use in a `POST /sessions/{id}/send` request.\n\n**Accepted formats:**\n| Type | Common formats |\n|------|---------------|\n| Image | JPEG, PNG, WEBP |\n| Video | MP4, 3GPP |\n| Document | PDF, DOCX, XLSX, PPTX, TXT |\n| Audio | MP3, OGG, AAC, M4A |\n\n**Size limit:** 100 MB per file.\n\nThe returned URL is already publicly accessible — pass it directly as `mediaUrl` in your send request.","operationId":"MediaController_upload","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"The file to upload (max 100 MB)"}}}}}},"responses":{"201":{"description":"File uploaded — use the returned URL in send requests","content":{"application/json":{"schema":{"example":{"success":true,"data":{"url":"https://cdn.uno-sap.com/media/ten_xyz789/invoice-1234.pdf","key":"ten_xyz789/invoice-1234.pdf","size":204800,"mimeType":"application/pdf"},"meta":{"request_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2026-06-14T12:00:00.000Z"}}}}}},"400":{"description":"File missing or exceeds 100 MB limit"},"401":{"description":"Invalid or missing API key"}},"security":[{"bearer":[]}],"summary":"Upload a file","tags":["media"]}}},"info":{"title":"Uno-SAP Developer API","description":"WhatsApp messaging API for developers. Authenticate with your session API key: `Bearer sk_live_...`","version":"1.0","contact":{}},"tags":[{"name":"auth","description":"Tenant management and API key operations"},{"name":"sessions","description":"WhatsApp session lifecycle"},{"name":"messages","description":"Outbound message history"},{"name":"webhooks","description":"Webhook endpoints and delivery"},{"name":"media","description":"File uploads"},{"name":"billing","description":"Subscription and payment management"},{"name":"usage","description":"API and message usage metrics"},{"name":"sandbox","description":"Test environment simulation"}],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"InitTenantDto":{"type":"object","properties":{}},"ProvisionDto":{"type":"object","properties":{"email":{"type":"string","example":"user@example.com"},"name":{"type":"string","example":"John Doe"},"provider":{"type":"string","enum":["google","credentials"]},"googleId":{"type":"string","example":"1234567890"},"password":{"type":"string"},"mode":{"type":"string","enum":["login","signup"]},"emailVerified":{"type":"boolean"}},"required":["email","name","provider"]},"CreateKeyDto":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable label for the key","maxLength":100},"environment":{"type":"string","description":"Environment for the key","enum":["live","test"],"default":"live"},"scopes":{"description":"Permission scopes. Use [\"*\"] for full access.","example":["messages:send","contacts:read"],"type":"array","items":{"type":"string"}}},"required":["name"]},"CreateContactDto":{"type":"object","properties":{}},"UpdateContactDto":{"type":"object","properties":{}},"CreateSessionDto":{"type":"object","properties":{}},"ButtonDto":{"type":"object","properties":{"id":{"type":"string","example":"btn_confirm","description":"Unique button identifier returned in the webhook when the user taps this button"},"label":{"type":"string","example":"Confirm Order","maxLength":20,"description":"Button label shown to the user (max 20 characters)"}},"required":["id","label"]},"ListRowDto":{"type":"object","properties":{"id":{"type":"string","example":"row_express","description":"Unique row identifier returned in the webhook when the user selects this row"},"title":{"type":"string","example":"Express Delivery","maxLength":24,"description":"Row title (max 24 characters)"},"description":{"type":"string","example":"Arrives next business day","maxLength":72,"description":"Optional row subtitle (max 72 characters)"}},"required":["id","title"]},"ListSectionDto":{"type":"object","properties":{"title":{"type":"string","example":"Delivery Options","maxLength":24,"description":"Optional section header (max 24 characters)"},"rows":{"description":"Rows inside this section","type":"array","items":{"$ref":"#/components/schemas/ListRowDto"}}},"required":["rows"]},"SendMessageDto":{"type":"object","properties":{"to":{"type":"string","example":"2348012345678","description":"Recipient phone number — digits only, no `+` prefix, in E.164 format without the leading `+`. Example: Nigerian number `+234 801 234 5678` → `2348012345678`."},"type":{"type":"string","enum":["text","image","video","document","audio","buttons","list","location"],"example":"text","description":"Message type. Each type requires specific fields:\n- `text` — requires `text`\n- `image` — requires `mediaUrl`; optional `caption`\n- `video` — requires `mediaUrl`; optional `caption`\n- `document` — requires `mediaUrl`; optional `fileName`, `mimeType`, `caption`\n- `audio` — requires `mediaUrl`; optional `mimeType`\n- `buttons` — requires `text` and `buttons` (max 3)\n- `list` — requires `text`, `listButtonText`, and `sections`\n- `location` — requires `latitude` and `longitude`"},"text":{"type":"string","example":"Hello! Your order #1234 has been confirmed.","maxLength":4096,"description":"Message body text. Required for `text`, `buttons`, and `list` types."},"mediaUrl":{"type":"string","example":"https://cdn.example.com/receipt.jpg","description":"Publicly accessible URL of the media file. Required for `image`, `video`, `document`, and `audio` types. The URL must be reachable by WhatsApp servers."},"caption":{"type":"string","example":"Order receipt — June 2026","description":"Optional caption shown below the media. Supported for `image`, `video`, and `document` types."},"mimeType":{"type":"string","example":"application/pdf","description":"MIME type of the media file. Defaults to `application/octet-stream` for documents and `audio/mpeg` for audio. Providing this helps WhatsApp render the file correctly."},"ptt":{"type":"boolean","description":"For `audio` only. `true` sends a WhatsApp voice note (the round waveform bubble); `false` sends a playable audio file.","example":true},"fileName":{"type":"string","example":"invoice-jun-2026.pdf","description":"File name displayed to the recipient for `document` messages."},"buttons":{"maxItems":3,"description":"Up to 3 reply buttons for `buttons` type messages. Each button has an `id` (returned in the webhook) and a `label` shown to the user.","type":"array","items":{"$ref":"#/components/schemas/ButtonDto"}},"listButtonText":{"type":"string","example":"Choose an option","maxLength":20,"description":"Label of the button that opens the list picker. Required for `list` type messages."},"sections":{"description":"One or more sections, each containing rows the user can pick from. Required for `list` type messages.","type":"array","items":{"$ref":"#/components/schemas/ListSectionDto"}},"latitude":{"type":"number","example":6.5244,"description":"Latitude in decimal degrees. Required for `location` type messages."},"longitude":{"type":"number","example":3.3792,"description":"Longitude in decimal degrees. Required for `location` type messages."},"locationName":{"type":"string","example":"Uno HQ, Lagos","description":"Optional location name shown in the message pin."},"locationAddress":{"type":"string","example":"1 Finance Crescent, Oniru Estate, Lagos","description":"Optional address line shown below the location name."}},"required":["to","type"]},"CreateWebhookDto":{"type":"object","properties":{"url":{"type":"string","description":"URL to deliver events to (HTTPS in production)","maxLength":2048},"secret":{"type":"string","description":"HMAC-SHA256 signing secret","maxLength":64},"events":{"type":"array","description":"Event types to subscribe to. Empty array = all events.","items":{"type":"string","enum":["message.received","message.sent","message.delivered","message.read","message.failed","message.updated","message.deleted","message.reaction","session.connected","session.disconnected","session.reconnected","session.banned","session.qr_updated","session.health_changed","chats.upsert","chats.update","chats.delete","groups.upsert","groups.update","group-participants.update","call.received","flow.started","flow.completed","flow.error","flow.human_handoff","contact.created","contact.opted_out"]}}},"required":["url","secret"]},"UpdateWebhookDto":{"type":"object","properties":{"url":{"type":"string","description":"New URL for the webhook","maxLength":2048},"events":{"type":"array","description":"Event types to subscribe to","items":{"type":"string","enum":["message.received","message.sent","message.delivered","message.read","message.failed","message.updated","message.deleted","message.reaction","session.connected","session.disconnected","session.reconnected","session.banned","session.qr_updated","session.health_changed","chats.upsert","chats.update","chats.delete","groups.upsert","groups.update","group-participants.update","call.received","flow.started","flow.completed","flow.error","flow.human_handoff","contact.created","contact.opted_out"]}},"enabled":{"type":"boolean","description":"Enable or disable the webhook"}}},"CreateFlowDto":{"type":"object","properties":{}},"UpdateFlowDto":{"type":"object","properties":{}},"TriggerFlowDto":{"type":"object","properties":{}},"CreateFeedbackDto":{"type":"object","properties":{"type":{"type":"string","enum":["feature","bug","other"],"default":"feature"},"title":{"type":"string","maxLength":200},"message":{"type":"string","maxLength":4000,"description":"What the user is asking for"}},"required":["message"]}}}}